# Nuxt 的 SSR 與 Nitro：方便，但別讓它替你劃架構邊界

> Nuxt 的 SSR 跟 Nitro server route 好用，但方便不等於架構，那層很容易把給瀏覽器的聚合跟面向後端的風險混在一起。

原文：https://sheng.page/posts/nuxt-ssr-and-nitro/ · 發布：2025-12-14 · 作者：Sheng

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 重算。

`useAsyncData` 與 `useFetch` 要先搞懂執行流程：首次請求在 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 端，前端只該拿到已經裁好的結果。

## 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 翻新計劃，新漆未乾。