[Next.js] Next.js 新手教學筆記 — Dynamic Routes

施慶銓
10 min readMay 28, 2023

--

使用情境

當在某一個路徑底下, 有共用的Layout, 透過不同的route path 渲染出不同的資料內容時, 可以透過Dynamic Routes 來處理這樣的情境.

範例路徑

單一子路徑

Route Example URL params

Router | Example URL | params
-------------------------------------------------
pages/blog/[slug].js | /blog/a | { slug: 'a' }
pages/blog/[slug].js | /blog/b | { slug: 'b' }
pages/blog/[slug].js | /blog/c | { slug: 'c' }

多階層子路徑

Catch-all Segment

Router | Example | URL params
-----------------------------------------------------------------
pages/shop/[...slug].js | /shop/a | { slug: ['a'] }
pages/shop/[...slug].js | /shop/a/b | { slug: ['a', 'b'] }
pages/shop/[...slug].js | /shop/a/b/c | { slug: ['a', 'b', 'c'] }

情境圖

Source from: https://nextjs.org/learn/basics/dynamic-routes/page-path-external-data

程式碼範例

// pages/posts/[id].js
//
// Possible paths:
// - xxx/posts/1
// - xxx/posts/2

export async function getStaticPaths() {
// Handle the route paths to render the correspoding comopnents

return {
paths: [
{
params: { id: 1 },
},
{
params: { id: 2 },
},
],
fallback: false,
}
}


export function getStaticProps(id) {
// Handle the external data to be the props passed into the component

return {
props: { postData }
}
}

// Post component
function Post({ postData }) {
// Render Post component
}

Route param 關係鏈

當使用者在瀏覽器上輸入一段網址: https://xxxx/posts/1 , 該路徑中會對應到上方範例得檔案路徑: pages/posts/[id].js , 其中路徑上的 1 就是對應到[id]

[id].js 檔案中有匯出一個function 名為getStaticPaths , 這個function 將會定義有哪些路徑可以匹配, 讓使用者可以使用該檔案的component.

如果getStaticPaths function 有設定id: 1 的路徑, 那麼瀏覽器上將會渲染 [id].js 內的component. 反之, 將會依照getStaticPaths 內設定的 fallback 機制決定要如何渲染無法匹配路徑的畫面

該如何在getStaticPaths設定動態路徑的機制? 在Node.js 內, 如果URL路徑上有動態的參數, 那麼在伺服器上處理路徑的變數方式是透過 req.params 找到目前URL 上參數的數值. 在這裏也似有類似的方式, 只是我們反過來定義params 有哪些.

所以, 如果要在getStaticPaths 定義有哪些可匹配的路徑可以用這樣方式定義

[
// xxx/1
{
params: { id: 1 }
},

// xxx/2
{
params: { id: 2 }
}
]

最後, 因為getStaticPaths 會回傳給Next.js 的回傳值, 不只是需要路徑, 也需要回傳當找不到路徑時, 應該如何處理的機制: fallback , 所以基本上的回傳內容會是

{
paths: [ ... ],
fallback: false // or true/'blocking'
}

Fallback 機制

Fallback 機制有三種

  • fallback: false
  • fallback: true
  • fallback: ‘blocking’

fallback: false

在這個機制下, 使用者輸入一個錯誤的路徑: xxx/posts/unknown_path, 當Next.js 發現可以對應的路徑時, 就會顯示404的頁面

Next.js 有預設的404頁面, 也可以自己建立一個404頁面, 只要pages資料夾底下建立404.js檔案: /pages/404.js 即可實作客製化的404頁面

預設404頁面

預設的404頁面

客製化的404頁面

客製化404頁面

fallback: true

在這個機制下, 如果Next.js無法從getStaticPaths馬上找到匹配的路徑, 頁面不會 導向404頁面, 而是會導向fallback頁面,

# fallback 頁面的用途

當fallback: true 時, Next.js 會在build time 的時候, 根據getStaticPaths回傳的路徑, 產生對應的HTML. 但當User 輸入一個URL無法在getStaticPaths回傳的路徑找到匹配的路徑, 那麼畫面就會進入到 fallback 頁面(通常顯示載入中或是表達等待的頁面), 此時Next.js 認為該頁面是需要動態產生的, 所以不會顯示404頁面, 而是等待 getStaticProps function 回傳資料後才將fallback 頁面改成產生出來的HTML

大致的程式碼如下

// pages/posts/[id].js
//
// Possible paths:
// - xxx/posts/1
// - xxx/posts/2
import { useRouter } from 'next/router'

export async function getStaticPaths() {
// Handle the route paths to render the correspoding comopnents

return {
...
fallback: true, // <---- 修改上方的範例, 將fallback從false改成true
}
}

export function getStaticProps(id) {
// Handle the external data to be the props passed into the component

return {
props: { postData }
}
}

// Post component
function Post({ postData }) {
const { isFallback } = useRouter()

if(isFallback){
// Fallback page
return <div>Loading...</div>
}

// Render Post component
}

以上面的程式碼為例, 現在在build time 的時候, Next.js 會產生post 1與post 2 的HTML, 當使用者拜訪/posts/1 時, server 可以馬上回傳HTML給使用者.

如果使用者在URL輸入 /posts/3 , 此時因為Server 在編譯出來的檔案內找不到post 3的檔案, 所以畫面就會出現fallback 畫面, 並等待getStaticProps回傳props資料. getStaticProps 成功回傳資料後, 即會顯示post 3 頁面內容

不過也有些情況不會顯示fallback 畫面

  1. 當使用者拜訪的URL 未在getStaticPaths 回傳的路徑內時, 只有首次 拜訪該頁面的時候才會出現fallback 畫面, 該頁面被產生後, 會被儲存在server 內. 所以不需要再顯示fallback畫面
  2. 當使用者是透過 next/link , next/rotuer 來拜訪這些頁面, 也不會出現fallback畫面

# 如何導向404頁面

還是有可能會遇到一種情況是, getStaticProps 無法回傳props 資料或是在取得props資料時發生錯誤, 那麼該情況下, 新的HTML頁面無法產生, 就無法顯示新的頁面內容.

此時我們需要用404頁面來輔助我們網站顯示錯誤頁面的訊息.

可能的做法: 我們可以讓 getStaticProps 回傳notFound: true , 那麼當真的無法顯示畫面時, 可以導向404頁面給使用者而不是顯示程式錯誤的訊息

範例如下

function getStaticProps() {
try {
// Handle the external data to be the props passed into the component
}
catch {
return {
notFound: true
}
}
}

# fallback設計的好處

如果沒有這樣的設計, 當網站內容越來越大時, 我們必須要在 getStaticPaths 加入所有頁面的路徑, 並且在build time時需要編譯/打包所有的頁面, 導致server 需要花費大量的時候來編譯出所有的頁面.

# 注意事項

fallback 等待時所產生的頁面只會產生一次, 所以頁面內的內容不會再更新, 如果需要更新需要參考Incremental Static Regeneration的做法

fallback: blocking

其流程跟fallback: true的流程類似:

  • Build time 會根據getStaticPaths 回傳的路徑產生對應的HTML檔案
  • 不會導向404 頁面
  • 新產生的HTML只會執行一次, 產生後的HTML會被server 快取, 之後再有相同的路徑直接使用快取內的HTML
  • 不會更新已經產生過的HTML 的內容, 需要參考Incremental Static Regeneration的做法

唯一不一樣的地方是, fallback: blocking不會出現fallbak頁面. 他就跟一般網頁轉址一樣, 在等待新的HTML產生出來的期間, 只會停留在目前的頁面,等到新的頁面產生出來後, 才會轉跳到新的頁面

--

--

施慶銓
施慶銓

No responses yet