Nuxt 的 SSR 與 Nitro:方便,但別讓它替你劃架構邊界

Nuxt 把 Vue 應用需要的 routing、SSR、static generation 與 server engine 整合在一起,今年七月也發布了 Nuxt 4。整合得太方便,原本責任不同的邏輯就容易被放進同一個專案。我最常在 SSR 資料抓取與 Nitro server 這兩處看到這個問題。

SSR、static、hybrid:先想清楚每條 route 該怎麼長

Nuxt 可以在同一個專案混用多種 rendering 模式:行銷頁 prerender 成純靜態內容後放到 CDN 邊緣,需要即時資料的 dashboard 走 SSR,幾乎不變的內容則設 cache、定期重新生成。這套 hybrid rendering 透過 routeRules 逐條指定,值得先搞懂;不少效能問題源自整站直接開 SSR,連原本可以靜態產生的頁面也在每次請求時交給 server 重算。

useAsyncDatauseFetch 要先搞懂執行流程:首次請求在 server 端跑,抓到的資料會序列化進 HTML 一起送下來,client 端 hydrate 時直接接手那份 payload,不會再抓一次。

最常見的兩個坑。一是 double fetch,你在元件裡用了 useFetch,又在某個 onMounted 或 watcher 裡自己再 $fetch 一次相同的資料,server 抓一次、client 又抓一次,使用者就看你白白多打一輪。二是更危險的洩漏:useAsyncData 的 handler 是會在 server 跑,但寫法不對也會被帶去 client。如果你在那個 callback 裡直接讀環境變數裡的 API key、直接連內部服務的位址,而這段邏輯沒有被正確關在 server-only 的範圍,它就有機會被打包進送給瀏覽器的 bundle。這怪不了 Nuxt,是你把 server-only 的東西寫在了會 hydrate 的路徑上。

判斷原則其實很簡單:那份資料的「來源」能不能見光。能見光的(公開 API、CDN 上的 JSON)放哪都行;不能見光的(內部位址、密鑰、需要特權的查詢)就只能待在 server 端,前端只該拿到 already 裁好的結果。

Nitro 當 BFF 很方便,但方便不是邊界

Nitro 是 Nuxt 底下的 server engine,你在 server/api/ 底下開的那些 server route,就是跑在 Nitro 上。它能做的事比想像多:同一層既是 SSR 的資料來源,也能當一個輕量的 BFF gateway。前端要資料改打自己的 Nitro route,不直接打第三方,那層去聚合下游、補上認證、決定回傳什麼,外部請求不直接碰資料層。這個配置我跑過,是真的省力,密鑰留在 server、call chain 集中、前端只拿到裁好的東西。

問題不在它做不到,而在它太容易做到,於是邊界開始糊。框架的 server 那層有個結構性的誘惑:它同時看得到「給瀏覽器的聚合」跟「面向後端的風險」,兩件事寫在同一個目錄、同一種 defineEventHandler 裡,久了你會分不清哪個 route 是在替前端裁資料、哪個是在扛內部服務的存取。一個 server/api/ 底下混著「給 lobby 用的欄位拼裝」跟「直接帶著 DB 憑證查表」,code review 的人很難一眼看出風險落在哪。

我仍然偏向獨立 BFF。小專案人少、責任線守得住時,讓 Nitro 同時扛 BFF 可以少養一個服務;系統長大後,我會把面向後端、承擔風險的邏輯拉出去,讓 Nitro 專心處理 SSR 與前端需要的輕量聚合。這樣誰面向瀏覽器、誰面向後端會由架構決定,不會落到「寫在這裡最快」的臨時選擇。

Nuxt 的價值在於簡化許多工作,但聚合、密鑰與下游存取仍需要清楚的邊界。server route 容得下這些邏輯,不代表它們長期都適合留在那裡;工具負責提供方便,架構則要避免責任慢慢混在一起。程式跑得起來,仍然不足以判斷設計是否合理。

補充筆記

  • Nuxt 的 hybrid rendering 用 routeRules 逐 route 指定 SSR / static / cache,多數效能問題來自全站無腦開 SSR。
  • useAsyncData / useFetch 首次在 server 抓、hydrate 後 client 接手,搞錯會 double fetch 或把 server-only 的密鑰帶進 client bundle。
  • 判斷資料放哪只問一句:它的來源能不能見光,不能見光的就只能待 server。
  • Nitro server route 可同時當 SSR 來源與輕量 BFF,外部請求不直接碰資料層,這配置很方便。
  • 但方便不該劃架構邊界,系統一大就把「面向後端、扛風險」那層獨立出去,別讓它跟給瀏覽器的聚合混在同一個目錄。

觀點 Sheng,內文 Claude 協助 · 列入 20260613 blog 翻新計劃,新漆未乾。