Next.js 網頁應用程式編碼慣例
將這些慣例應用於所要求的工作。使用者的明確指令優先於此技能中的任何規則或預設值。保留現有的產品需求與整合;應用此技能並不代表授權不相關的遷移或安裝每個可選的依賴項。
在實作之前,請檢查專案指示、已安裝的版本以及現有的慣例。當可用時,請閱讀相關已安裝的 Next.js 文件,特別是 node_modules/next/dist/docs/;否則請查閱與版本相符的官方文件。請勿假設來自其他版本的框架 API。
預設值與條件式功能
| 領域 | 預設值 | 覆寫或啟用規則 |
|---|---|---|
| 框架 | Next.js App Router 與嚴格的 TypeScript | 遵循專案選擇的版本與使用者的明確選擇。 |
| 元件 | 使用 Tailwind CSS 的 Base UI,並包裝在本地元件中 | 支援 Radix、shadcn/ui 或其他要求的函式庫。保留相同的元件組織方式。 |
| 變體 | class-variance-authority 以及一個支援 Tailwind 的 class 合併輔助函式 | 重複使用已建立的等效工具。 |
| API 傳輸 | 在自訂的 APIClient 和 ServerAPIClient 抽象層之後使用 Axios | 現有的自訂客戶端、fetch 或其他要求的傳輸方式皆可。保持合約與傳輸方式無關。 |
| 驗證 | 使用 Zod schema 與推斷出的 TypeScript 型別 | 預設在外部資料邊界需要;僅在使用者明確指示時才更改。 |
| 遠端客戶端資料 | 使用 TanStack Query 與一個共用的查詢包裝函式 | 重複使用要求的等效工具。 |
| 共享互動狀態 | Zustand | 對於單一元件擁有的狀態,使用本地 React 狀態。尊重明確的替代方案。 |
| 驗證 | 當要求驗證時,提供與提供者無關的電子郵件 OTP 流程 | 遵循選擇的提供者與要求的登入方法;請勿隱含地選擇或佈建提供者。 |
| 跨頁籤同步 | 為相關的共享突變事件啟用 | 可根據要求選擇性啟用或移除。僅在跨頁籤一致性有用的地方加入事件。 |
| 日誌記錄與追蹤 | 遵循現有的日誌系統 | 如果不存在,除非被要求,否則請勿引入、提議或提及日誌/追蹤基礎設施。 |
| 模擬資料與模擬 | 關閉 | 僅在使用者要求模擬資料或開發/測試模擬時加入。 |
| 開發工具 | 獨立的開發專用模組 | 僅在相關或被要求時加入工具;沒有規定的工具或覆蓋層。 |
當需要做出決定時選擇預設值;不要詢問每個預設值。請勿將驗證、狀態儲存、同步或基礎設施引入不需要這些功能的特性中。
必要的資料夾結構
為新程式碼使用此組織方式。在責任變得必要時才建立目錄,而不是用空的佔位符填滿整個專案。在現有專案中,如果沒有遷移要求,請避免移動不相關的檔案。
src/
├── app/ 路由、佈局、API 端點
├── components/
│ ├── ui/ 共享 UI 基本元件
│ ├── standalone/ 可重複使用的領域元件
│ ├── pages/<page>/ 特定頁面的元件
│ └── <feature>/ 功能子系統
├── controllers/
│ ├── client/<domain>/ 瀏覽器 API 呼叫
│ └── server/<domain>/ 伺服器端 API 呼叫
├── providers/ React Context 提供者
├── stores/ Zustand 儲存
├── hooks/ 共享的 React Hook
├── helpers/ 驗證/存取與其他工作流程
├── lib/ API 客戶端、快取、日誌記錄、同步
├── db/ 資料庫/Redis 客戶端
├── utils/ 小型工具與常數
└── assets/icons/ 來源 SVG
基礎設施的資料夾名稱描述的是放置位置,而非必要的服務。此樹狀結構不暗示任何資料庫、Redis、快取或日誌記錄堆疊。
- 將領域程式碼組織在這些責任資料夾之下;避免用不同的頂層架構取代它們。
- 使用 PascalCase 命名元件檔案,
useSomething.ts命名 Hook(僅在包含 JSX 時使用.tsx),以及使用 camelCase 命名操作檔案,例如updateName.ts。 - 使用描述性的領域和功能目錄。保留框架檔案名稱不變,例如
page.tsx、layout.tsx和route.ts。 - 使用
index.ts來組合控制器命名空間,並使用index.tsx作為多檔案元件的入口點。 - 使用
@/進行根目錄在src的匯入;對於緊密的內部同層級檔案,使用相對路徑匯入。對於僅型別依賴,請使用import type。 - 避免臨時副本、備份資料夾、重複實作以及責任不明的萬用檔案。
路由與元件組合
讓 app/ 專注於路由、佈局組合、請求處理和初始資料載入。在其佈局邊界有用時,使用路由群組,例如 (auth)、(no-auth) 和 (settings)。路由群組名稱本身不提供任何存取保護。
保持頁面精簡:載入初始資料、解決頁面層級的決策,並從 components/pages/<page>/ 或相關的功能目錄渲染元件。預設使用伺服器元件;為瀏覽器狀態、事件和 API 引入客戶端邊界。
元件放置:
components/ui/:與領域無關的基本元件以及所選 UI 函式庫的包裝器。components/standalone/:跨頁面或功能使用的可重複使用領域控制元件。components/pages/<page>/:特定於單一頁面的元件。components/<feature>/:跨路由共享的大型子系統,例如導航或指令介面。
將複雜的元件拆分到專注的檔案中。 這是一項核心慣例。一個大型元件應該有一個目錄,其中包含一個 index.tsx 組合入口點,以及根據需要命名的支援元件、頁籤、行、Hook 和型別。請勿在一個檔案中累積多個大型子元件和不相關的副作用。根據責任而非任意的行數限制來拆分;避免將瑣碎的標記分割得過於零碎。
將互動機制保留在功能當地的 Hook 或專注的處理元件中:鍵盤導航、選擇、事件訂閱、拖拽行為和檢視協調。當一個 Hook 被共享時,將其移到 src/hooks/。保持輸入快捷鍵的範圍界定,以免干擾可編輯的控制項;在卸載時清理監聽器。
在本地包裝基本函式庫,並公開可組合的部分,例如 Dialog、DialogTrigger、DialogContent 和 DialogTitle。保留無障礙語意、焦點行為以及實際函式庫的組合 API。在未檢查相容性的情況下,請勿將 Radix 專有的 props 複製到 Base UI 包裝器中。
從底層基本元件或原生元素中定義包裝器 props 的型別,並僅加入需要的自訂 props。使用具型別的變體來處理大小、強調和狀態。以可預測的方式合併消費者的 class,並根據已安裝的 React/函式庫 API 保留 ref 支援。
使用連接到 Tailwind 工具的語意化 CSS 變數。保持主題值集中化,元件變體本地化。此技能規定的是樣式組織方式,而非視覺主題、間距尺度或產品設計。
在 app/providers.tsx 中組合相關的 Context 提供者;將其實作保留在 providers/ 中。將僅限瀏覽器的提供者放在明確的客戶端邊界之後,並避免將開發工具初始化與正常的提供者責任混在一起。
控制器與 HTTP 客戶端
在 controllers/client/<domain>/ 或 controllers/server/<domain>/ 下,每個檔案一個操作。透過 index.ts 檔案組裝巢狀的命名空間物件,例如:
ClientController.org.members.list()
ClientController.profile.updateName(input)
ServerController.org.info()
控制器提供具型別的操作和端點建構。元件應呼叫控制器,而不是組合 URL 或傳輸配置。控制器保持精簡;將可重複使用的工作流程邏輯放在 helpers/ 中,將基礎設施放在 lib/ 中。
保持瀏覽器和伺服器 HTTP 客戶端分離:
lib/APIClient.ts:瀏覽器安全的傳輸、查詢/主體序列化、回應處理和取消支援。lib/ServerAPIClient.ts:僅限伺服器的傳輸、受信任的基礎 URL 以及請求範圍的憑證/工作階段上下文。- 將伺服器機密和僅限伺服器的依賴項保留在瀏覽器匯入之外。切勿將每位使用者的憑證保存在共享的可變單例中。
- 當伺服器控制器呼叫應用程式端點時,僅將所需的憑證轉發到受信任的目的地。請勿信任呼叫者提供的身份標頭作為驗證。
- 一致地標準化傳輸錯誤、非 2xx 回應和應用程式錯誤。保留有意義的狀態/程式碼資訊。請勿將失敗的請求變成成功的空資料。
- 透過控制器和傳輸層支援
AbortSignal。取消不應觸發錯誤通知或重試。
API 結構、合約與處理器
Zod schema 是輸入的預設真實來源。為主體、查詢和路徑參數定義特定的 schema,包括格式、允許的值、長度/範圍限制、可選性和故意的強制轉換。在使用外部值之前進行驗證。避免使用 z.any()、寬泛的記錄、型別斷言和非空斷言來代替合約。為每個邊界審慎處理未知鍵。
預設情況下,將特定於方法的路由 schema 和匯出的請求/查詢/回應型別與路由放在一起。使用 z.infer 推斷請求型別;定義明確的回應合約,並在需要執行時期驗證的地方使用回應 schema。在控制器和元件中重複使用這些型別,而不是重新宣告它們。如果執行時期合約必須與瀏覽器程式碼共享,或者路由模組匯出約束要求這樣做,請使用相鄰的 schema.ts 或 types.ts 以及僅型別匯入,這樣伺服器實作永遠不會被拉入客戶端套件。
使用一致的可區分回應信封:
type APIResponse<T> =
| { success: true; data: T; error: null }
| {
success: false;
data: null;
error: { code: string; message: string; details?: unknown };
};
在使用時,將 details 縮小到明確的安全驗證/錯誤詳細資訊型別。保持所有分支的錯誤和 HTTP 狀態一致。請勿在正式環境回應中揭露內部異常或堆疊追蹤。
將多方法的路由組織成特定於方法的 schema/型別區段和函式,例如 get_request 和 post_request。透過 lib/api/APIRequestHandler.ts 中的 withAPIRequestHandler() 匯出處理器。
維護 lib/api/error/,其中包含一個 APIError 類別和一個按領域組織、具型別的錯誤代碼、訊息和狀態目錄。共享處理器將驗證失敗、領域錯誤和意外失敗映射到相同的回應合約。僅在上述日誌記錄條件下整合日誌記錄。
將重複的組織查詢、身分識別解析、查詢解析和存取工作流程提取到 helpers/ 中。將小型純轉換和常數保留在 utils/ 中。將較大的整合拆分成專注的工作流程檔案;路由應協調這些步驟,而不是包含整個整合。
資料擷取與狀態
在適當時透過伺服器控制器載入初始頁面資料,並將具型別、可序列化的 props 傳入互動式元件。對獨立的請求使用 Promise.all;為依賴性操作保留順序。
對遠端客戶端資料使用 TanStack Query。提供共用的預設值和一個 useApiQuery 包裝函式,該函式解開 API 信封、傳播錯誤並轉發查詢取消訊號。查詢鍵必須包含相關的身分識別/資源/篩選器輸入。將伺服器端的查詢客戶端範圍限定於每個請求;在突變後失效或更新受影響的查詢條目。
對共享的互動式狀態(例如選擇、篩選器、面板和命令狀態)使用特定於功能的 Zustand 儲存。訂閱個別的切片或使用淺層選擇來處理分組值。保持 setter/action 明確,並在其擁有的資源變更時重置狀態。避免在 Zustand 中維護第二個獨立的遠端資料副本,因為 Query 已經擁有它。
對元件擁有的輸入、髒狀態、驗證訊息和儲存狀態使用本地狀態。防止重複提交並在使用者輸入失敗時保留它。沒有必要的形式函式庫。
取消被取代的搜尋/篩選請求,並在清理時中止。防止過時的完成結果更新新資源。僅將實際的取消歸類為取消;請勿默默地吞掉網路失敗。
對於大型列表,在有用時應用虛擬化和分頁,並包含載入中、空、錯誤、重試和列表結尾狀態。在資源/篩選器變更時適當地重置分頁和選擇。保持鍵盤導航與虛擬化行一致。
可選的跨頁籤同步
預設為相關的共享突變啟用同步,除非使用者停用它。請勿將其加入沒有同步需求的孤立狀態。
在 lib/local-sync/ 中使用一個具型別的事件註冊表,在 events/ 下使用領域事件 schema,以及一個共享的 hooks/useLocalSync.ts Hook。從 Zod 推斷載荷型別,並驗證傳出和傳入的訊息。
一個儲存事件實作應通知其他頁籤和同頁籤的監聽器,使用短暫的事件載荷而不是持久化的應用程式狀態,並清理監聽器。現有的等效傳輸方式是可接受的。防止回音迴圈和重複處理;請勿同步機密資訊。事件應更新或失效規範的狀態擁有者。跨頁籤事件是 UI 協調,而非授權或保證的傳遞。
驗證與授權
當驗證在範圍內時套用此區段。保持實作與提供者無關,並適應所選的服務或自訂後端。
- 對於電子郵件 OTP 驗證,分離傳送驗證碼、驗證驗證碼和登出處理程序...
(文件已截斷以供翻譯)