Next.js是一個用于構建全棧web應用的React框架。App Router 是 nextjs 的基于文件系統的路由器,它使用了React的最新特性,比如 Server Components, Suspense, 和 Server Functions。
術語
- 樹(Tree): 一種用于可視化的層次結構。例如,包含父組件和子組件的組件樹,文件夾結構等。
- 子樹(Subtree): 樹的一部分,從新的根節點(第一個)開始,到葉子節點(最后一個)結束。
- 根節點(Root): 樹或子樹中的第一個節點
- 葉子節點(Leaf): 子樹中沒有子節點的節點,例如 URL 路徑的最后一段。
- URL 段(URL Segment): 用斜杠分隔的 URL 路徑的一部分
- URL 路徑(URL Path): 域名之后的URL的一部分(由段組成)。
1. 路由段 Route Segments
在app目錄中,嵌套的文件夾定義了路由結構,路由中的每個文件夾代表一個路由段。每個路由段都映射到 URL 路徑中的對應段。文件(如page.jsx和布局layout.jsx)用于創建所在段的UI。
- 嵌套路由 (Nested Routes), 如
/dashboard/settings
路由可以通過在app
目錄下添加兩級目錄實現。 - 組件在嵌套路由中遞歸呈現,這意味著路由段的組件將嵌套在其父段的組件中。
- 約定只有 page.js 或 route.js 的內容會被發送到客戶端。這意味著其他文件可以安全地放在app目錄的路由段中,但不會被路由。
2. 組件層次 Component Hierarchy
- 在同一路由段的文件中定義的組件會在特定的層次結構中呈現
- 在一個嵌套路由中,一個段的組件將被嵌套在它的父段的組件中
- 除了特殊文件之外,您還可以選擇將自己的文件放在文件夾中。例如,樣式、測試、組件等等。
可以按特性或路由拆分項目文件:
3. 文件夾和文件約定
頂級文件夾用于組織應用程序的代碼和靜態資產,頂級文件用于配置應用程序、管理依賴關系、運行中間件、集成監視工具和定義環境變量。
layout.jsx, 定義布局組件
- 默認情況下,文件夾層次結構中的布局組件也是嵌套的。
- 應用頂級布局必須包含
<html>
和<body>
標簽。 - 不要直接添加
<head>
標簽到 layout.jsx, 而是使用Metadata APIs
。定義并導出Metadata
對象 、通過generateMetadata
方法動態生成、或使用約定的文件如sitemap.xml
、robots.txt
等。 - 可以使用 React的緩存函數
cache function
只獲取一次數據。 - 通過 ImageResponse 可以使用 JSX 和 CSS 動態生成 Open Graph 圖像。
page.jsx, 定義一個具體路由的UI
- 必須有一個 page.jsx 才能使路由段可公開訪問。
- 兩個 Promise 類型的可選參數:params(動態路由)、searchParams(查詢參數)
app/shop/[category]/[item]/page.js
, 訪問/shop/1/2
, 獲得參數:Promise<{ category: '1', item: '2' }>
/shop?a=1&b=2
, 獲得參數:Promise<{ a: '1', b: '2' }>
- 客戶端組件中通過 React 的 use 方法獲取 Promise 參數值
loading.jsx, 為路由段及其子段創建加載界面
- loading.jsx 將被嵌套在 layout.jsx 中。它會自動將 page.jsx 文件及其下的所有子文件包裝在
<Suspense>
中 <Suspense>
用于顯示一個臨時UI,直到它的子節點完成加載。在頁面中使用Suspense
可以讓服務端渲染流式傳輸Streaming
流允許您將頁面的HTML分解為更小的塊,并逐步將這些塊從服務器發送到客戶端
not-found.jsx, 拋出 notFound
異常時顯示的界面
- 頂級
app/not-found.jsx
文件可處理整個應用程序未捕獲的notFound
異常 - 如果需要使用客戶端鉤子(比如usePathname)來顯示基于路徑的內容,須使用客戶端組件
error.jsx, 當發生運行時錯誤時顯示的備用UI
error.jsx
把路由段和它的嵌套子段包裝在一個React Error Boundary
中- 可以使用位于應用程序根目錄中的
global-error.js
來處理根布局或模板中的錯誤 - 全局錯誤UI必須定義自己的
<html>
和<body>
標簽。該文件在活動時替換根布局或模板
route.jsx, 創建自定義 api 請求處理程序
- 支持 GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS.
- 參數 request:
NextRequest
對象, Web請求的擴展。提供了對傳入請求的進一步控制,可訪問cookie
和擴展的URL對象nextUrl
。 - 參數 context (optional)
context.params
包含當前路由的動態路由參數
template.jsx, 類似 layout.jsx,當需要創建一個新的組件實例時使用
- 與跨路由持久化和維護狀態的
layout
布局不同,template
模板被賦予了一個唯一的鍵,這意味著在導航時會刷新子客戶端組件的狀態。 - layout 內部的
<Suspense>
僅在布局第一次加載時顯示,而在切換頁面時則不顯示。對于模板,每次導航都會重新渲染<Suspense>
的回退UI。
default.jsx, 在并行路由中呈現回退UI
- 對于硬導航(整個頁面重新加載),Next.js無法恢復部分UI插槽的活動狀態。可以為與當前URL不匹配的子頁面呈現 default UI。
4. 路由:以服務器路由為中心的客戶端導航
與使用客戶端路由的 pages 目錄不同,app 目錄中的新路由器使用以服務器為中心的路由,以便與服務器上的服務端組件和數據獲取保持一致。客戶端不必下載路由圖,并且可以使用對服務端組件的相同請求來查找路由。這種優化對所有應用都有用,對路由多的應用影響更大。
雖然路由是以服務器為中心的,但使用 Link 組件的客戶端導航具有類似于單頁應用程序的行為。這意味著當用戶導航到新路由時,瀏覽器不會重新加載頁面。但 URL 將被更新,Next.js 將只呈現更改的部分。
此外,當用戶瀏覽應用程序時,路由器會將服務端組件數據存儲在客戶端緩存(內存)中。緩存按路由段分割,允許在任何級別失效,并確保并發渲染的一致性。這意味著在某些情況下,可以重用先前獲取的段的緩存,從而進一步提高性能。
局部渲染 Partial Prerendering
部分預呈現(PPR)是一種呈現策略,它允許您在同一路由中組合靜態和動態內容。這提高了初始頁面的性能,同時仍然支持個性化的動態數據。
如果一個組件使用了以下api,它就會變成動態的:
cookies // 讀寫 cookies 的方法headers //一個async函數,它允許你從服務端組件讀取HTTP傳入請求的 headersconnection // 指示強制變為動態,希望它在運行時動態呈現,而不是在構建時靜態呈現。draftModesearchParams // 路由查詢參數fetch with { cache: 'no-store' } // 不使用緩存每次請求時都從遠程服務器獲取資源
- 包裹在
<Suspense>
中的動態組件開始從服務器并行流式傳輸到客戶端。 export const experimental_ppr = true
可以將路由段的所有子節點開啟PPR,不需要把它添加到每個文件中,只需要把它添加到路由的頂部段- 組件只在訪問動態屬性時才會變成動態渲染。例如,從
<Page />
組件中讀取searchParams
,可以將這個值作為prop轉發給另一個組件,進行隔離 - 當前只能在使用最新的
nextjs canary
版本時啟用,未來會成為 Next.js 的默認構建方式
靜態渲染及動態渲染組件示例:
流式傳輸
通過流式傳輸,您可以防止緩慢的數據請求阻塞整個頁面。這允許用戶查看頁面的部分內容并與之交互,而無需等待所有數據加載完畢,然后才能向用戶顯示任何UI。
在Next.js中有兩種實現流的方法:
- 在頁面級別,通過
loading.jsx
文件(自動創建<Suspense>
)。 - 在組件級別,使用
<Suspense>
進行更細粒度的控制,將阻塞頁面的組件單獨封裝進行流式傳輸。
5. 路由模式
- 動態路由:如果事先不知道確切的段名,并希望根據動態數據創建路由,則可以使用在請求時填充或在構建時預呈現的動態路由。
- 并行路徑: 允許您在同一視圖中同時顯示兩個或多個可以獨立導航的頁面。用于具有自己的子導航的分屏視圖,例如儀表板。
- 攔截路由: 允許你攔截一條路由,并在另一條路由的上下文中顯示它。在保持當前頁面的上下文很重要時使用。例如,在編輯一個任務時查看所有任務,或在 Feed 中查看圖片。
- 條件路由: 允許您根據條件有條件地呈現路由。例如:只在用戶登錄后才顯示。
路由組和私有文件夾
(folder)
:在不參與路由的情況下對路由進行分組,可實現為子路由分別定義布局UI layout.jsx_folder
:讓子目錄不參與路由, 可用于將UI邏輯與路由邏輯分離。
動態路由
[folder]
, 動態路由段,可作為params
參數傳遞給layout、page、route 和 generateMetadata函數。例如,一個博客的路由app/blog/[slug]/page.js
,其中[slug]
是博客文章的動態段。[...folder]
, 捕獲全部路由,例如,app/shop/[…slug]/page.js
會匹配/shop/clothes
,還會匹配/shop/clothes/tops
,/shop/clothes/tops/t-shirts
等。[[...folder]]
, 全捕獲,除了本級及子路由,也捕獲父路由段,例如,app/shop/[[…slug]]/page.js
也會匹配/shop
并行路由
@folder,并行路由是使用命名槽創建的。槽作為參數傳遞給共享的父布局,并可以與 children 并行呈現
- 在客戶端導航期間,Next.js 將執行部分渲染,更改槽內的UI,同時保持另一個槽的當前UI,即使它們與當前URL不匹配。
- 整個頁面刷新重載,無法確定與當前URL不匹配的插槽的活動狀態。不匹配的槽呈現 default.js 界面,如果default.js不存在,則呈現404。
- 實現條件路由,根據登錄用戶的角色展示不同的槽。
- 可以在插槽中添加布局 layout.jsx,以允許用戶獨立地導航插槽。這對于創建選項卡很有用。
- 并行路由可以和攔截路由一起使用來創建支持深度鏈接的對話框。
- 并行路由可以獨立流化,允許你為每條路由定義獨立的錯誤UI error.jsx 和加載UI loading.jsx
攔截路由
(.)folder
, 匹配在同一級別上路由段(..)folder
, 匹配上一級路由段(..)(..)folder
, 上兩級中的路由段(...)folder
, 匹配頂級路由段
用戶可以使用客戶端導航從圖庫打開照片對話框,也可以通過URL導航到照片頁面:
6. 組件
默認情況下,布局和頁面是服務端組件,它允許您在服務器上獲取數據并呈現UI的部分,可選地緩存結果,并將其流式傳輸到客戶端。當您需要交互性或調用瀏覽器api時,可以使用客戶端組件來實現。
- Next.js默認使用服務端組件
- 在服務器端服務端組件被渲染為一種特殊的數據格式(RSC Payload), 客戶端組件和
RSC Payload
用于呈現HTML Hydration
是React將事件處理程序附加到DOM的過程,以使靜態HTML具有交互性- 為了減少客戶端JavaScript包的大小,在特定的交互組件中添加“use client”,而不是將UI的大部分標記為客戶端組件
- 當使用依賴于客戶端特性的第三方組件時,可以將其包裝在客戶端組件中,以確保其按預期工作
- 通過
import 'server-only'
防止在客戶端組件中意外使用服務端方法。
使用客戶端組件的場景:
- 包含狀態管理及事件處理程序
- 處理了生命周期鉤子,如useEffect
- 需要調用瀏覽器API. 如 localStorage, window, Navigator.geolocation 等
- 自定義 hooks
使用服務端組件的場景:
- 從服務端靠近數據源的數據庫或api中獲取數據
- 使用API密鑰、令牌和其他秘密,而客戶端不可見
- 減少發送到瀏覽器的JavaScript數量
- 加速首頁渲染,流式傳輸到客戶端
Link 導航組件
<Link>
是一個React組件,它擴展了HTML <a>
元素,在路由之間提供預加載和客戶端導航。這是 Next.js 中導航路由的主要方式。
Image 圖片組件
<Image>
組件擴展了HTML <img>
元素,提供以下功能:
- 圖像大小優化:自動為每個設備提供正確大小的圖像,使用現代圖像格式,如WebP
- 視覺穩定性:防止加載圖像時的布局抖動
- 提升頁面加載速度:僅在圖片進入視窗時使用本地瀏覽器延遲加載加載,并帶有可選的模糊占位符
- 資產靈活性:按需調整圖像大小,甚至是存儲在遠程服務器上的圖像。
fonts 字體模塊
next/font
模塊通過內置自托管自動優化字體并減少網絡請求。
- 字體的作用域是使用它們的組件。要將字體應用于整個應用程序,將其添加到根布局中
- 字體作為靜態資產存儲,并從部署的位置獲取,這意味瀏覽器不會向谷歌發送請求
- 通過
next/font/local
加載一個或多個本地字體
7. CSS 樣式
提供了多種加載樣式的方法:
-
CSS Modules:模塊化導入
import styles from './blog.module.css'
使用:<main className={styles.blog}></main>
-
Global CSS:全局樣式,創建一個
app/global.css
文件,并將其導入到根布局中,這些樣式應用到應用中的每個路由視圖。外部包發布的樣式表也可以被導入 -
CSS的順序取決于你在代碼中導入樣式的順序。
-
使用 Tailwind CSS, Tailwind v4 之后版本不再通過
tailwind.config.js
配置,可以在全局導入的地方通過CSS自定義配置,并采用OKLCH
色彩空間作為調色板。 -
使用 CSS-in-JS UI庫
-
使用 Tailwind CSS
建議:
- 通過一個如可js文件導入css 模塊
- 在應用程序的根目錄中導入全局樣式和 Tailwind 樣式
- 對嵌套組件使用CSS模塊而不是全局樣式
- 將共享樣式提取到共享組件中,以避免重復導入
- 關閉編輯器的自動排序格式功能
- CSS順序在開發及生產環境中可能表現不同,始終檢查構建結果以驗證
獲取數據
- 在服務端組件中直接通過
fetch
或 db 客戶端獲取數據 - 在客戶端組件需要配合 React 的
use()
函數 或 其他庫(SWR、 React Query)
更新數據
- 通過 React 的
Server Functions
服務器函數來更新數據 - 在客戶端組件中可以通過表單動作或事件響應函數來調用服務器函數
- 通過
useActionState
hook 可以監控調用狀態
8. Middleware 中間件
中間件允許您在請求完成之前運行代碼。然后,根據傳入的請求,您可以通過重寫、重定向、修改請求或響應頭或直接修改響應。
中間件有效的一些常見場景包括:
- 在讀取部分傳入請求后快速重定向
- 基于A/B測試重定向到不同的頁面
- 修改所有頁面或頁面子集的標頭 headers
- 可以通過配置或條件判斷來指定哪些請求運行。
matcher: ['/about/:path*', '/dashboard/:path*']
- 設置CORS頭以允許跨域請求
- 訪問或設置 cookies
- 中間件不應用于獲取數據或會話管理
9. 懶加載
通過懶加載 Lazy loading
可以減少渲染路由所需的JavaScript量,有助于提高應用程序的初始加載性能。
- 懶加載適用于客戶端組件,
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })
- 動態導入一個服務端組件,只有作為服務器組件的子組件的客戶端組件才會被懶加載
- 通過
import()
可以按需加載外部庫const Fuse = (await import('fuse.js')).default
確保最佳性能和用戶體驗的建議
- 使用布局來跨頁面共享UI,并在導航上啟用部分呈現
- 使用
<Link>
組件進行客戶端導航和預取 - 通過創建自定義錯誤頁面,優雅地處理生產中的所有錯誤和404錯誤
- 遵循服務器和客戶端組件的推薦組合模式,并檢查
"use client"
邊界的位置,以避免不必要地增加客戶端Js包 - 像cookie和searchParams這樣的動態api會導致整個路由動態渲染。確保動態API的使用是有意的,并將它們通過
<Suspense>
包裝起來 - 利用服務端組件在服務器上獲取數據
- 使用
Route Handlers
路由處理程序從客戶端組件訪問后端資源。但不要從服務器組件調用路由處理程序,以避免額外的服務器請求 - 使用 Loading UI 和 React Suspense 來逐步將UI從服務器發送到客戶端,并防止在獲取數據時阻塞路由
- 通過并行獲取數據來減少網絡瀑布。此外,考慮在適當的地方預加載數據
- 驗證您的數據請求是否被緩存,并在適當的情況下多使用緩存。確保不使用fetch的請求被緩存
- 使用 Server Actions 處理表單提交、服務器端驗證和錯誤處理
- 通過使用字體模塊將字體文件與其他靜態資產一起托管,以減少外部網絡請求,及布局抖動
- 通過使用
<Script>
組件來優化第三方腳本,該組件可以自動延遲腳本并防止它們阻塞主線程 - 使用內置的
eslint-plugin-jsx-a11y
插件來盡早捕獲可訪問性問題 - 確保你的
.env.*
文件被添加到.gitignore
中,并且只公共變量添加NEXT_PUBLIC_
前綴 - 使用元數據API通過添加頁面標題、描述等來改進應用程序的搜索引擎優化(SEO)
官方開發示例
dashboard-app: https://nextjs.org/learn/dashboard-app
一個基于 App Router 的全棧web應用,訪問 PostgresSQL 數據庫,儀表板展示,流式加載,賬單的分頁增刪改查,用戶登錄等功能全面展示 nextjs 基礎功能特性。
END
如果這篇文章對您有所幫助,歡迎點贊、分享和留言,讓更多的人受益。感謝您的細心閱讀,如果發現了任何錯誤或需要補充的地方,請隨時告訴我,我會盡快處理
^_^