Next.js 在生產環境
適用於任何 Next.js 16 App Router 應用程式中的程式碼。安全性相關規則(actions 中的驗證、CSP、標頭、taint)在 nextjs-security 中;容器規則在 docker-nextjs 中。Claude Code 內建的 next-best-practices 技能涵蓋了一般檔案慣例、metadata、圖片和字型優化的詳細內容;此技能僅補充我們建置和發佈時的特定內容。
基準:Next 16.4、React 19.3、Turbopack(dev 和 build 的預設值)、cacheComponents: true 搭配 partialPrefetching: true、output: "standalone"、typedRoutes: true。範本的 next.config.ts 是唯一真相來源。
規則
-
預設使用伺服器元件;僅在需要狀態、效果、瀏覽器 API 或事件處理常式時才使用
"use client"。 將指令放在需要它的最小葉子元件上。不要因為檔案匯入 nuvui 就添加它:nuvui 的狀態相關部分會自帶指令(請參閱nuvui技能中的rules/server-components.md)。// 錯誤:因為一個處理常式導致整個頁面變成客戶端元件 "use client"; export default function Page() { … <Select onValueChange={…} /> … } // 正確:頁面保留在伺服器端,處理常式位於葉子元件中 export default async function Page() { const people = await getPeople(); return <PeopleFilter people={people} />; // people-filter.tsx 有 "use client" }傳入客戶端元件的 props 必須是可序列化的:不能有函式(Server Actions 除外),不能有類別實例。
-
在需要資料的伺服器元件上進行 fetch。 不要使用
useEffect進行 fetch,不要為了提供資料給自己的頁面而使用路由處理常式。在同一個請求中使用 React 的cache()進行去重;跨請求的快取則使用規則 3。 -
使用
"use cache"+cacheLife+cacheTag進行快取;其他所有內容都是動態的。 使用 Cache Components,除非你明確指定,否則沒有任何內容會被快取,且fetch沒有隱含快取。import { cacheLife, cacheTag } from "next/cache"; export async function getInvoice(id: InvoiceId) { "use cache"; cacheLife("minutes"); cacheTag(`invoice:${id}`); return db.invoice.findUnique({ where: { id } }); }不要使用舊的區段配置(
export const dynamic、revalidate、fetchCache):在 Cache Components 下會報錯。引數和閉包捕獲的值會成為快取鍵的一部分,因此請傳遞 id,而不是整個請求物件。詳細資訊、設定檔和"use cache: private":請參閱 references/caching.md。 -
在 Server Actions 中使用
updateTag進行失效處理,在其他地方使用revalidateTag(tag, "max")。updateTag(tag)提供「讀取自己寫入」的能力,且僅能在 Server Action 內部運作。從 webhook 或路由處理常式使用revalidateTag(tag, "max")(stale-while-revalidate)或revalidateTag(tag, { expire: 0 })。單一引數的revalidateTag(tag)已被棄用。 -
將請求時期的資料包裹在
<Suspense>中,否則建置會失敗。 在 Suspense 邊界之外的cookies()、headers()、searchParams、connection()和未快取的 I/O 會阻止預先渲染,並顯示「在預先渲染期間使用了未快取或執行時期資料」。為其提供帶有骨架圖 fallback 的邊界、進行快取,或(僅限根佈局,請參閱規則 14)使用export const instant = false將路由標記為阻塞。所有params、searchParams、cookies()、headers()都是非同步的:請await它們。 -
每個 Server Action 都必須自行驗證其輸入和檢查授權。 Server Action 是一個公開的 POST 端點,無論它寫在哪個頁面上。使用
typescript-production中的 schema(valibot)解析FormData,然後進行授權檢查,接著執行變更,最後使用updateTag。對於預期中的失敗,回傳一個類型標記的結果;僅在出現錯誤時才拋出例外。完整的規則集在nextjs-security中。"use server"; export async function renameProject(_: State, form: FormData): Promise<State> { const session = await requireSession(); // 如果不存在則回傳 401 const input = v.safeParse(RenameProject, Object.fromEntries(form)); if (!input.success) return { ok: false, errors: flatten(input.issues) }; await assertCanEdit(session, input.output.projectId); // 如果不允許則回傳 403 await db.project.update(…); updateTag(`project:${input.output.projectId}`); return { ok: true }; } -
路由處理常式是為其他客戶端(webhooks、行動裝置、健康檢查)設計的,而不是為你自己的頁面。 將它們放在
app/api/下。像 action 一樣驗證請求主體,回傳Response.json(...),並有目的地設定狀態碼。 -
每個應用程式都有
GET /api/health,動態且無依賴。 DockerHEALTHCHECK和 Kubernetes 探針會呼叫它。它必須在不接觸資料庫的情況下回應,這樣一個慢速的依賴項就不會重新啟動每個 pod。await connection()可防止它被預先渲染成靜態檔案。它位於範本中的src/app/api/health/route.ts;proxy.ts會從其匹配器中排除/api/health。 -
在根目錄以及使用者體驗不同的每個區段中放置
error.tsx、loading.tsx和not-found.tsx。error.tsx是一個客戶端元件;透過instrumentation.ts的onRequestError記錄日誌,顯示一個帶有reset的重試按鈕,但絕不顯示錯誤訊息。對於找不到的記錄,應呼叫notFound(),而不是渲染空白狀態。 -
NEXT_PUBLIC_*在建置時就已嵌入;其他所有內容都在執行時期於伺服器端讀取。 一個映像檔從 main 分支提升到生產環境時保持不變,因此NEXT_PUBLIC_的值在所有環境中都相同。僅將建置時常量放在那裡(應用程式名稱、一個在所有環境中都相同的公開 Sentry DSN)。每個環境的配置(API URL、功能開關、機密)是伺服器端的process.env.X,在 Server Component 或 action 中讀取,並在需要時作為 props 傳遞給客戶端。在src/env.ts(valibot)中驗證 env 一次,然後從那裡匯入。不要在next.config.ts中使用env金鑰:它會被內聯到客戶端 bundle 中。 -
使用
import "server-only"標記僅伺服器模組。 任何讀取機密、與資料庫通訊或匯入帶有憑證的 SDK 的模組。如果客戶端匯入它,建置將會失敗,而不是洩漏資訊。 -
使用
after()處理不應延遲回應的工作(稽核日誌、分析、快取預熱)。在呼叫after之前讀取cookies()/headers(),而不是在它內部。它在同一個行程中執行,因此 pod 需要其 SIGTERM 寬限期(圖表設定了terminationGracePeriodSeconds)。 -
使用
src/instrumentation.ts進行儀器化:registerOTel+onRequestError。 透過@vercel/otel的 OpenTelemetry,匯出器由執行時期的標準OTEL_EXPORTER_OTLP_*環境變數配置。請參閱 references/observability.md。 -
保留
output: "standalone",並且永遠不要從工作區外部匯入。 Docker runner 僅複製.next/standalone、.next/static和public。在 monorepo 中,server.js位於.next/standalone/apps/<app>/server.js;工作區根目錄是從pnpm-lock.yaml中找到的,因此除非建置發出警告,否則不要設定outputFileTracingRoot或turbopack.root。自有主機的詳細資訊(多副本、Server Action 金鑰、deploymentId):請參閱 references/self-hosting.md。 -
根佈局會讀取 CSP 隨機數,因此應用程式會根據每個請求進行渲染。 範本的
proxy.ts設定了一個基於隨機數的 CSP,而src/app/layout.tsx有export const instant = false。這是一個有意的權衡:隨機數和預先渲染的靜態 shell 無法共存,而對於需要登入的商業應用程式來說,嚴格的 CSP 勝出。使用"use cache"的資料快取仍然有效。在未閱讀nextjs-security規則 4 的情況下,不要移除任何一半。 -
樣式:全域 BEM SCSS,由元件匯入。 Turbopack 使用
sass套件編譯 Sass;sassOptions.loadPaths: ["src/styles"]使得@use "abstracts"可以從任何深度運作。不要使用~匯入,不要使用sassOptions.functions。規則在scss-bem中。 -
圖片透過
next/image,字型透過next/font。sharp隨 Next 一同提供,用作最佳化器;runner 映像檔不需要額外套件。遠端圖片需要帶有確切主機名稱的images.remotePatterns。 -
使用
pnpm next analyze檢查 bundle 大小(Turbopack 的分析器;@next/bundle-analyzer僅支援 webpack)。使用next/dynamic對重型客戶端 widget(nuvui 編輯器、地圖、折疊線以下的圖表)進行延遲載入。 -
使用類型路由和生成的類型。
typedRoutes: true會檢查<Link href>;在next typegen之後(由next dev/next build自動執行),PageProps<"/invoices/[id]">和LayoutProps成為全域的。不要手寫params類型。 -
不要使用
next lint。 它在 16 版中被移除了。Biome 進行 lint(請參閱biome);TypeScript 透過每個套件的tsc --noEmit進行檢查。
驗證
pnpm typecheck
pnpm lint
pnpm build # turbo run build: 在預先渲染錯誤、型別錯誤、Sass 錯誤時失敗
pnpm --filter <app> start & # 然後:
curl -fsS localhost:3000/api/health
來源
- https://nextjs.org/docs/app/guides/upgrading/version-16
- https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents
- https://nextjs.org/docs/app/guides/migrating-to-cache-components
- https://nextjs.org/docs/app/api-reference/directives/use-cache
- https://nextjs.org/docs/app/api-reference/functions/revalidateTag
- https://nextjs.org/docs/app/api-reference/functions/updateTag
- https://nextjs.org/docs/messages/blocking-prerender-dynamic
- https://nextjs.org/docs/app/api-reference/functions/after
- https://nextjs.org/docs/app/guides/open-telemetry
- https://nextjs.org/docs/app/api-reference/config/next-config-js/output
- https://nextjs.org/docs/app/guides/self-hosting
- https://nextjs.org/docs/app/guides/environment-variables
- https://nextjs.org/docs/app/guides/sass
- https://nextjs.org/docs/app/guides/package-bundling
- https://nextjs.org/docs/app/api-reference/file-conventions/proxy