Cheatsheet
app/folderA/folderB/page.js
, app路由,該router為domain/folderA/folderB
app/folderA/[id]/page.js
,動態路由,可以匹配domain/folderA/{any id}
app/folderA/[...id]/...
catch all下級路由,獲取任意層級的路由,等於可以匹配domain/folderA/{any id}/.../...
,但如果進入domain/folderA
,會得到404app/folderA/[[...id]]/...
,除了catch all下級路由,也包含該級的路由(domain/folderA
),不過參數 params 會為空(folderA)
,單純加個 folderA 目錄,不包括在路由裡。@name
平行路由,可以讓 layout.js 渲染多個 page.js。(.)name
,攔截路由,讓router和直接access的路由展示不一樣。而前面括號中的是代表相對路徑
各自舉個🌰
Basic App Routes
在./app/...
目錄下,每個目錄就是子route,所以可以直接用目錄、子目錄的形式直接創建嵌套路由。
Dynamic Routes-general
而在許多實際情況下,路由可能不是預先定義好、而是動態生成的,例如blog中的文章ID。如果是以https://{domain}/blog/{blogID}
來進入blog文章頁面,那麼在NextJS中的App Routing便是:
.
└── app
└── blog
└── [blogID]
└── page.tsx
Dynamic Routes-catch all
catch all 對動態路由提供了很大的靈活性,例如在一個電商網站,用戶可以訪問/products/electronics/phones
或者更深一層的/products/electronics/phones/smartphones
,都可以用單一組件、然後以catch all來捕捉到、然後根據路由來動態展示。以上述為例,如果app routing為:
.
└── app
└── products
└── [...data]
└── page.tsx
那麼在/products/electronics/phones
路由中,params便為
{ data: ["electronics", "phones"] }
如果是products/electronics/phones/smartphones
,那麼params則是:
{ data: ["electronics", "phones", "smartphones"] }
但是,如果是以這個目錄來說,直接在browser鍵入{demain}/products
,會是 404
Dynamic Routes-optional catch all
動態路由中的 Optional Catch-all,就是為了解決這個問題。如果把目錄改為:
.
└── app
└── products
└── [[...data]]
└── page.tsx
那麼就可以直接進入 {demain}/products
,只不過此時的params為空對象{}
。
Routes Group
這算是方便專案folder組織管理。假設現在是有一個需要登入的dashboard專案,sign-in、sign-up是用同一個layout,而dashboard、services、settings是用同一個layout,在不用新增主路由的要求下(都是一級路由),那麼目錄結構應該如下:
.
└── app
├── dashboard
├── services
├── settings
├── sign-in
└── sign-up
然而,如果再加上 layout 的要求,就會有很多重複程式碼:
.
└── app
├── dashboard
│ ├── layout.tsx
│ └── page.tsx
├── services
│ ├── layout.tsx
│ └── page.tsx
├── settings
│ ├── layout.tsx
│ └── page.tsx
├── sign-in
│ ├── layout.tsx
│ └── page.tsx
└── sign-up
├── layout.tsx
└── page.tsx
在以上情境下,如果使用Routes Group的方式來寫的話,可讀性就會改善很多,同時也可以減少重複的 layout.tsx
.
└── app
├── (auth)
│ ├── layout.tsx
│ ├── sign-in
│ │ └── page.tsx
│ └── sign-up
│ └── page.tsx
└── (main)
├── dashboard
│ └── page.tsx
├── layout.tsx
├── services
│ └── page.tsx
└── settings
└── page.tsx
Parallel Routes
我覺得平行路由這個部分,寫起來很像是Vue中的<slot/>
,是可以在同級的layout.tsx
(最近的父layout)中獲取從目錄中定義好的@folderName
,然後從props中解構獲取到,以下面的目錄結構為例:
.
└── app
├── @auth
│ └── page.tsx
├── @user
│ └── page.tsx
├── layout.tsx
└── page.tsx
然後便可以在 layout.tsx
中獲取到該命名插槽:
export default function Layout({
children,
auth,
user,
}: {
children: ReactNode
auth: ReactNode
user: ReactNode
}) {
return (
<>
{children}
{auth}
{user}
</>
)
}
這樣一來,auth
和user
如果有自己的loading.tsx
,就可以各自執行各自的stream ssr;另外,也可以在layout.tsx
中進行條件渲染(假設 auth 和 user需要不同的佈局)
export default function Layout({
children,
auth,
user,
}: {
children: ReactNode
auth: ReactNode
user: ReactNode
}) {
return (
<>
{children}
{ condition? auth : user}
</>
)
}
不過我認為,這算是要熟悉NextJS的寫法才會覺得比較方便,不然其實直接寫 Suspense 和 條件渲染,其實也不是不行吧?例如把目錄變為
.
└── app
├── _components
│ ├── auth-loding.tsx
│ ├── auth.tsx
│ ├── user-loading.tsx
│ ├── user.tsx
│ └── index.tsx
├── layout.tsx
└── page.tsx
然後在layout.tsx
中自己寫<Suspense/>
import { Suspense } from 'react'
import { Auth, AuthLoading, User, UserLoading } from './_components'
export default function Layout({children}:{children:ReactNode}) {
return (
<>
{children}
<Suspense fallback={<AuthLoading/>}>
<Auth />
</Suspense>
<Suspense fallback={<UserLoading/>}>
<User />
</Suspense>
</>
)
}
應該也是可以的吧?而條件渲染就更不用說了。這個部分可能要再多研究一下。
Intercepting Routes
目前筆者還沒有使用到過,就先簡單demo一下:
目錄結構:
. └── app └── folderA ├── (.)folderB │ └── page.tsx ├── folderB │ └── page.tsx └── page.tsx
folderA/page.tsx
:"use client"; import { useRouter } from "next/navigation"; export default function Page() { const router = useRouter(); const goToFolderB = () => { router.push("folderA/folderB"); }; return ( <div> <h1>FolderA</h1> <button onClick={goToFolderB}>route to folderB</button> </div> ); }
folderA/folderB/page.tsx
export default function Page() { return <h1>Original FolderB</h1>; }
folderA/(.)folderB/page.tsx
export default function Page() { return <h1>(.) Intercepted FolderB</h1>; }
在上述demo專案中,如果是在{domain}/folderA
的頁面中,點擊按鈕跳轉到{domain}/folderA/folderB
頁面到話,內文會顯示 (.) Intercepted FolderB;但如果是直接從瀏覽器中輸入{domain}/folderA/folderB
而進入該頁面的話,則會顯示 Original FolderB,代表在有上下文的情況下(路由跳轉之類的),路由會被攔截而渲染 (.)folder/page.tsx
。
結語
使用NextJS的App Routing,前期需要點時間習慣一下該邏輯(如果照自己的邏輯來寫的話,可能會有些重複性程式碼),習慣過後會發現NextJS真的為工程師使用者體驗、前端頁面情境等等做了很多設想及優化。希望可以用NextJS來寫更多有趣的專案:)
Ref
ChangeLog
- 20240405-初稿