Next.js 的渲染模式是怎麼運作的:SSR、CSR、SSG、ISR、PPR…

看了一下 next.js ,感覺只是將原本 react 提供的 js 檔案先轉成 html 再輸出,還是獨立伺服器處理,不像 express-handlebars 可以跟 node.js 整合在一台伺服器內 [MozTW] 我不太喜歡把 Next.js 的渲染模式歸結成任何形式 (SSR or CSR or SSG),但想和你分享一下為什麼 Next.js 不是另一個 express-handlebars,以及 Next.js 團隊到底在渲染下做了多少事情。 express-handlebars 通常是純粹的 SSR,不考慮你在頁面中插入的 JavaScript,handlebars 不用往產物增加 JavaScript 恢復互動元素(也就是 Hydration)。 Hydration 是指 React 接管由伺服器產生的靜態 HTML、並為其附加事件監聽器與狀態,使其從無生命的骨架變成可互動應用的關鍵步驟。 純粹的 React,就以搭配 Vite 來說的話,是 CSR。因為背後不用有配合的伺服器,放在可以 host 靜態網站的 web server 上就可以(比如 NGINX、Caddy 這種)。 Next.js 的 Pages Router 有哪些渲染方式? 接下來回頭看 Next.js。如果只看 Pages Router,會相對簡單一點,可以分成 SSG、ISR、SSR、CSR 的部分: 如果頁面不用在伺服器端請求資訊,而且要產生的頁面是可以在編譯期推斷出來的,Next.js 會在編譯期 (build time) 產生好 HTML,使用者瀏覽時就會直接回傳,這叫 SSG (Static Site Generation)1。 ...

September 28, 2025 · pan93412

如何繞過 Vercel 的 Function 逾時?

執行逾時了,Vercel 的免費方案 只給你 60 秒回應。假如你的 /upload 等待回應的時間超過 60 秒,那就會被 Vercel 殺掉哦。 有三種解法: 如果不想花錢,就想辦法把任務放到背景,如圖一。不過我翻了一下 Vercel 的文件,它沒給你機會建立一個超過 60 秒的任務,所以你大概得在 Cloudflare Workers 用 JavaScript 重寫你 Gemini 等待回應那段邏輯,用 waitUntil 把任務移到背景執行,接著將回應寫進資料庫,你的前端再不停呼叫 response 等回應。注意 Cloudflare Workers 的免費方案有 100,000 次的觸發限制,而一輪會至少觸發 2 次。 升級到 20 美元的 Vercel Pro,他會慷慨的多給你 12 分鐘回應。 試試看 5 美元的 Zeabur,不限制回應時間,你想處理多久就處理多久。

September 21, 2025 · pan93412

「目錄」不是支語

今天吃飯聽到隔壁桌剛好在討論 vibe coding,然後就在講『跟 AI 說要建立「資料夾」但沒想到 AI 竟然說「目錄」???目錄不是支語嗎?』 嚇到我了,我以前也講目錄欸,目錄真的是支語嗎?當然後來都說是資料夾了 🤔 Post folder(面向使用者的用詞)和 directory(檔案系統概念)的不同。無論是「資料夾」還是「目錄」,這兩個都是老資訊用詞了,都不是支語 ⚠️ btw folder 在 macOS 上會翻譯成檔案夾,雖然和微軟系的「資料夾」差得有點多,但這真的不是支語 ⚠️⚠️ 補上 Apple、Microsoft 和 GNU coreutils 對 folder / directory 的詞彙表。GNU coreutils 基本上就是常見 Linux 發行版內建指令列工具對應的翻譯。 其他公司和軟體的對應翻譯也可以自己找,但不要把不認識的詞當成支語 QQ

September 21, 2025 · pan93412

分頁應該在用戶端還是伺服器端進行?

我有看過一種做法是 api 直接在 server side 直接吐完整包列表資料,前端在自己做 pagination, 我不知道這樣做跟 api 做 pagination 比有沒有優勢 Post 原則上,如果抓資料的開銷和筆數有關,我會設計 API 層級的分頁:API 比較不需要讓資料庫查太多東西 (SELECT all) 而造成資料庫壓力,也不會一次下載太多沒必要的資料回來。 如果資料量沒這麼大(比如一些 Tags),我會讓 client 分頁,純粹只是為了改善前端體驗。如果資料量大一點(比如 1K),我就會傾向在 API 設計基礎的分頁功能(即便對後端來說,取多少筆資料都不會影響速度),來減少需要回傳的內容。 By the way 後端分頁也有他的學問,傳統的 offset-based pagination 在資料筆數非常大的情況下,可能會有效能問題。可以看看比如 Cursor-based pagination 的知識,或許對以後設計超大資料的分頁 API 會有幫助。

September 21, 2025 · pan93412

大量渲染不應該做太多無意義的最佳化

renderList ……?討論串很多人針對這篇評論給出很多不同建議了,我想給幾個我還沒看到的 point。 關於「元件最佳化」這點:React 背後有 Virtual DOM,所以你 render 100K 遍一樣的 list,相同 key、相同內容的 element 是不會在實體 DOM 層被重建的;useMemo 如果 list 的內容會一直變動,反而可能會造成 memory leak。如果是新專案,我通常推薦不手寫 useMemo/useCallback/memo 等等的記憶函式,直接讓 React Compiler 分析哪些東西該被 memorized。 既然 React 不會一直重建那 100K 個 DOM 元素,那為什麼在非常大的 list 下還是會卡?因為瀏覽器不擅長處理過多 DOM 元素。這種情況下應該要用 Virtual List 來只 render 可見元素,壞消息是他有 trade off——無法選取超出 viewport 的內容、瀏覽器內建搜尋不能搜尋全文。所以就算 GitHub 那種程式碼 view 很適合 Virtual List,但實際上都沒有實作。

September 21, 2025 · pan93412

弱 ETag 可以送到 If-Match 嗎?

如果伺服器傳送 Etag: 1233,過了 CDN Client 收到 Etag: W/"1233" 那麼 Client 回送 If-Match 應該傳送 W/"1233" 還是 1233? Original Post If-Match 的用意是在於防止「更新遺失」或「中途編輯衝突」(mid-air edit collision)。它的核心用途是在執行條件式請求時,確保用戶端操作的資源版本與伺服器上的目前版本完全一致1。 如果 CDN 傳送 Etag: W/"1233"(weak validator),因為 If-Match 標頭要求使用強比較函式1(即 E/1 ≠ E/12),因此用戶端無法使用這個值來建立 If-Match 條件式請求1。因此,如果伺服器傳送的是 Etag: W/"1233",則不應該在 If-Match 中傳送任何值來進行條件式更新。 如果要做快取的話,直接記錄 ETag,日後用 If-None-Match header 讓伺服器比較版本就行3。通常來說 If-Match 都是防止狀態改變的方法(POST 這些)1,CDN 通常不會去快取 POST 這類主要做 mutation 的方法,也就沒必要把 ETag 改成 weak representation,也就不太會遇到這個問題。 RFC 7232 – Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests – If-Match: https://datatracker.ietf.org/doc/html/rfc7232#autoid-15 ↩︎ ↩︎ ↩︎ ↩︎ RFC 7232 – Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests – ETag Comparsion: https://datatracker.ietf.org/doc/html/rfc7232#autoid-11 ↩︎ ...

September 21, 2025 · pan93412

聊聊微服務是什麼 – 服務通訊

這個 Tag 的第六個主題是「服務通訊」,講白話些就是「我要怎麼呼叫另一個服務的功能?」我們在第三個主題講了「服務通訊的大致邏輯」,以及在第四個和第五個主題花了很大的篇幅講了「服務怎麼 連線 到服務」,但好像從來都沒有真正說「服務怎麼和服務 互動」、「RPC 是什麼」,以及具體要怎麼通訊、有哪些通訊方法,以及有哪些通訊手段。這裡就要開始介紹了! 這篇是之前 SITCON 2025 的草稿,不是後面寫的,也還沒有做過 peer reviewing!如果有任何問題的話,也歡迎到 Threads 或 X 上標我指正。 「通訊」是什麼? 在單體服務的世界,你要呼叫一個方法,比如「選課」,你通常是用函式或 HTTP API 呼叫完成的。舉例來說: 你的前端往你的 API 發出了 POST /api/v1/course/:course/selection。我們會稱這個網址叫做端點 (endpoint)。 你的 API 有一個「選課」Controller,裡面有一個處理選課端點的「方法」(method),這裡叫做 SelectCourse 吧! 接著你的 SelectCourse 方法會透過呼叫 GetStudentInfo 方法來取得這個學生的資訊,以及透過 GetCourseContraints 方法取得課程的限修資訊 接著 SelectCourse 方法會根據這些資訊決定允不允許學生選這堂課,然後將選課結果插入資料庫當中。 📚 小提醒:在 MVCS 架構下,我們通常會叫這種功能性質的 class 為 Service。不過為了防止跟微服務的 Service 混淆(雖然其實概念是一樣的),這裡就不會用 Service 這個名詞。 如果你能理解上面這張圖,那我們把它換成微服務,你就很清楚我們要解決什麼「通訊」問題了: 上面這張圖的青色線段,就是我們這次要探討的問題了:這些服務都不在一起,我們要怎麼呼叫對方的方法——也就是「青色」這個線條,究竟怎麼實作? 🫨 選課服務是怎麼連線到使用者服務的?你可以往回看看「服務探索」這個章節!從這裡開始,我們都假定我們已經知道這些服務的 IP 和連線方式了,要處理的只是應用層的溝通問題。 用 HTTP 設計自己的 RPC 首先,最直覺的方法,就是你給每個服務開一個 HTTP 的 API,用 HTTP 的呼叫邏輯和其他服務溝通。因為這些 HTTP 方法只是內部使用的,你不用像 REST API 一樣,需要認真思考每個端點的命名,只需要讓每個服務看得懂就好。因此,你這樣設計你的微服務溝通方法: ...

September 5, 2025 · pan93412

聊聊微服務是什麼 – 擴縮與負載平衡

這個 Tag 的第五個主題是水平擴縮跟負載平衡。還記得我們昨天提到的「服務探索」嗎?到目前來講,我們只「探索」了一個服務,但是微服務的其中一個目標,就是在不擴展機器的情況下,盡可能把請求負載分擔到各個服務上。我們要怎麼做到這個「分擔」呢? 這篇是之前 SITCON 2025 的草稿,不是後面寫的,也還沒有做過 peer reviewing!如果有任何問題的話,也歡迎到 Threads 或 X 上標我指正。 什麼是擴縮和負載平衡? 答案意外得簡單:我們不就多開幾個服務,然後按一套規則平均把請求分擔到這些服務就好了嗎?恭喜你發明了水平擴縮 (horizontal scaling) 跟負載平衡 (load balancing) 了!這兩件事情的邏輯很簡單:「我們要在不動單一個節點的情況下,盡量把服務『擴縮』到各個地方,並且平均地『分配』請求到這些服務上?」 只是在實務上會更複雜,舉例來說,你會有 N 個 Gateway,連線到 M 個選課服務,這 M 個選課服務還會分別連線到 X 個課程服務,以及 Y 個使用者服務。我們接下來要解決的議題,就是如何在更複雜的場景,可以將連線分配到服務上、如何在單一服務下線時讓其他服務繼續工作,以及 HTTP 的世界,負載平衡通常該怎麼做。 要怎麼擴縮服務? 首先,我們先從比較簡單的問題開始:「怎麼擴縮?」它本質上就是多開幾個服務,你在好幾個終端機視窗,重複啟動你的服務,其實就是一種「擴展」(scaling up);你把過剩的服務用 Ctrl-C 殺掉,其實就是一種「縮減」(scaling down)。不過你假如真的這樣做,會發現到「重複用相同的參數啟動服務會遇到 port 衝突的問題」(比如說不能有兩個服務開在 3000 port 上),所以你就需要將另一個服務開在不同的 port 上(比如說,3001 port),這樣一來,你就起了有兩個 replicas 的服務了! 📚 「Replicas」是什麼?其實就是「重複的實例」,比如說上圖中的 course-service(課程服務),我們可以稱他有 6 個 Replicas。 要怎麼分配請求(負載平衡)? 下一個問題,就是「怎麼分配請求?」這個問題跟「服務探索」一樣,可以從用戶端和伺服器端下手。就以上面的圖來說: 用戶端解法:Gateway 在知道 6 個課程服務的位址之後,用分配演算法(隨機挑選、循環調度 (round robin),或者是比較複雜的根據負載分配)來決定要連線到哪台機器。 伺服器端解法:我們可以在前面掛一個代理伺服器(用 NGINX、Caddy),由代理伺服器透過分配演算法來決定要代理到哪台機器,而 Gateway 直接連線到這個代理伺服器就好。 其中,「伺服器端解法」中那個「代理伺服器」的別稱就是「負載分配器」(Load Balancer)。你應該在 AWS、GCP、Cloudflare 等 IaaS 上看過這個名詞,實際上他的原理就是透過一套演算法,將請求分配、代理到指定的服務。 ...

September 5, 2025 · pan93412

聊聊微服務是什麼 – 服務探索

今年我 SITCON 有投一篇微服務的議程:「選課卡成狗?微服務架構帶你翻轉校園系統」。在 SITCON 之前,我打算每天在 Blog 上寫一篇和 Cloud Native 相關的短文,來當作議程的前導內容。當然針對每一篇短文的意見回饋(看不懂也是一種意見反饋 🥺),最終都有助於我產出更好的議程內容~ 這個 Tag 的第四個主題是「服務探索」。從這個主題開始,我們要開始探討前三章說的微服務架構會有什麼問題,以及可以怎麼克服了。 考慮到 Blog 本身不是一個很好的互動平台,我在每篇文章的底下都會留「💬 互動區塊」,連結到和這篇文章相關的社交媒體上。你可以在社交媒體上和這篇文章互動~ 同台機器上,服務如何互相連線? 前文的結尾我有留一個問題:「如何讓服務之間可以互相連線?」你或許會覺得這問題非常基本,因此我們不妨拿之前的微服務架構模擬看看。假如你在本機端而且不想配置網路的話,最直接的做法可能是每個服務都開不同的 port,讓 Gateway 直接用 port 連線。比如說你把使用者服務安排在 port 3001、把選課服務安排在 port 3002,然後 Gateway 用 localhost:3001 連線使用者服務、用 localhost:3002 連線選課服務;selection-service 也是用 localhost:3001 連線使用者服務、用 localhost:3002 連線選課服務。 事實上假如你只在一台機器上運作的話,這種方法真的沒有什麼問題,甚至可以說是行之有年了。 多台機器上,服務如何互相連線? 不過假如我們進入到分散式的世界:把這堆服務打散到各個機器上,這樣的做法會碰到什麼問題呢?我們在第一章有分析出選課相關的功能相對耗資源,所以我們把「選課系統」獨立到不同的機器(這裡叫他機器 2)上。首先不同機器肯定會有不同的 IP,所以「機器 1」要連線到「選課服務」,肯定就不能用 localhost 連線了,我們肯定是要用「機器 2」的 IP 搭配上之前設定的 port 進行連線,也就是下面這張圖。 問題來了:IP 是永恆不變的嗎?機器的 IP 可能會因為區域變動(比如從新北機房移到高雄機房)而變化,我們也不一定能保證「選課服務」一定只在機器 2 上面。甚至說假如其他服務也開始有壓力,我們想要把它分出機器 1,我們也同樣需要關心到 IP 地址的議題。既然 IP 不是永恆不變的,我們就要關心起「如果 IP 改動時,會發生什麼樣的事情。」試想一下,我們把「選課服務」的 IP 換掉,我們需要改掉多少個服務連線位址呢? ...

February 6, 2025 · pan93412

聊聊微服務是什麼 – 閘道和服務通訊基礎

今年我 SITCON 有投一篇微服務的議程:「選課卡成狗?微服務架構帶你翻轉校園系統」。在 SITCON 之前,我打算每天在 Blog 上寫一篇和 Cloud Native 相關的短文,來當作議程的前導內容。當然針對每一篇短文的意見回饋(看不懂也是一種意見反饋 🥺),最終都有助於我產出更好的議程內容~ 這個 Tag 的第三個主題,就來補充我們昨天微服務中一直沒有提到的部分:「Gateway」吧!我們昨天聊了服務的拆法(領域拆分),以及 RPC 分查詢和更動的目的(CQRS),但是你會發現到有個服務長得和其他服務不太一樣:Gateway。Gateway 沒有 RPC 方法,而且是唯一一個連接到前端的服務。為什麼需要 Gateway,以及 Gateway 是怎麼和其他服務通訊的呢?這篇文章就來詳細解釋這個問題。最後,我們也會簡單說明微服務之間的通訊流程。 考慮到 Blog 本身不是一個很好的互動平台,我在每篇文章的底下都會留「💬 互動區塊」,連結到和這篇文章相關的社交媒體上。你可以在社交媒體上和這篇文章互動~ 什麼是 Gateway? 「Gateway」(閘道), 基本上就是你那些微服務的前台,負責給前端一組好用、封裝自 RPC 的 REST 或 GraphQL API,同時在轉送給微服務前進行正確的權限檢查(比如說「加入課程」只有管理員能執行)。這樣一來,前端就不需要知道所有微服務的位置、了解服務切分的細節,只需要和 Gateway 進行互動就好。 拆出 Gateway 的意義 你或許會疑惑「為什麼我們不要一步到位,讓前端直接連線這些微服務呢?」首先我們先看看讓前端連線微服務會發生什麼事情: 現在前端會直接連線到這些微服務上。假如沒有 Gateway,如你所見,「認證」和「授權」的重責大任就落在每個微服務身上了。身分認證的目的是判斷請求者持有的 token 是否有效,確定是對應使用者登入的;授權的目的是檢查對應使用者是否可以取用這個方法,防止使用者進行越權的操作。 因為每個微服務都需要做一次認證和授權,單個微服務會需要處理自身領域外的事情,增加複雜度1:首先,每個微服務都需要有自己一套進行認證和授權的程式碼,除了拖慢呼叫一個 RPC 方法的速度(現代微服務一個 Endpoint 可能會需要呼叫好幾十個 RPC 方法),微服務本身也會因為這些驗證而變得臃腫。接著,認證和授權都需要依賴「使用者服務」,如果「使用者服務」故障了,幾乎所有微服務都會停擺。最後,服務之間的通訊該怎麼認證呢?如果你的認證方式是 OAuth 的話,你很需要給所有服務組合產生一組 M2M token⋯⋯ 除了後端一團糟外,前端也會一團亂。你的前端需要先知道「我需要的方法在哪個服務上」,然後還需要知道這個服務的連線網址。你的 API 呼叫時需要指向各種不同的網址,當你要撤換服務時,需要一一檢查前端有沒有地方引用到這個服務,增加你撤換和更新這些服務時的困難度。同時你的這些服務因為都跟使用者的前端(也就是用戶端)扯上關係了,所以設計上又需要考慮用戶端會怎麼搞壞你的服務,並且需要花上更多時間把你的 RPC 方法封裝得更適合讓任意用戶端呼叫(比如因此捨棄簡單的 gRPC,轉為使用 REST API,去提升各個用戶端的相容性)。 ...

February 5, 2025 · pan93412