嗨嗨嗨!昨天看完怎麼抓取資料的方式會影響到網頁的渲染模式之後,今天的主題是 Next.js 的 Pages 與 Routing。Next.js 的 routing 方式跟一般 React 使用的 react-router 不一樣,它採用 file-based routing。

File-based routing 的意思是每個檔案代表一個 route,而檔案名稱 (file name) 會變成 route 的 path name (路徑名稱)。Next.js 會把 pages 資料夾 / 目錄裡面的所有檔案看成 route (路徑) 的一部分,還不是很懂嗎?我們看看下面的例子~
pages/index.js 代表 / routepages/about.js 代表 /about route每個 index 檔案代表根目錄,所以
pages/index.js 代表 / routepages/posts/index.js 代表 /posts route每一層資料夾每一層目錄會是 route 的一部分,所以
pages/posts/index.js 代表 /posts routepages/posts/first.js 代表 /posts/first routepages/categories/lifestyle/fashion.js 代表 /categories/lifestyle/fashion route如果上面的 routes 都要我們自己定義每一個 file name 當 path name,那當我們有一系列的頁面怎麼辦?或是如果我們想要把資料庫裡的每一條資料寫個頁面怎麼辦?或是像iT邦幫忙的網址 https://ithelp.ithome.com.tw/users/20141378 應該不會是我們要新增 20141378 個頁面檔案吧?這時候我們需要 dynamic routes 來幫助我們!
pages/posts/[pid]/index.js 代表 /posts/1,/posts/2,···pages/posts/[pid]/comments/index.js 代表 /posts/1/comments,/posts/2/comments,···pages/posts/[pid]/comments/[cid].js 代表 /posts/1/comments/1-1,/posts/2/comments/2-1,···或是iT邦幫忙的網址 https://ithelp.ithome.com.tw/users/20141378 需要一個檔案 pages/users/[uid].js。這些動態的資訊可以在 route.query 物件裡面看到,/posts/a/comments/a-1?utm_source=ithome 的 query 物件會長這樣:
{ "pid": "a", "cid": "a-1", "utm_source": "ithome" }
屬於 dynamic routes 的一種,不過 catch all 的意思是不需要一個一個定義動態的部分,把所有動態的資訊抓起來,讓 route 比較彈性,寫法跟其餘參數(rest parameter)一樣。
pages/posts/[...slug].js 可以代表 /posts/a 也可以是 /posts/a/b,/posts/a/b/c 等slug 可以是 param 或是其他東西都可以,這些動態資訊也會出現在 query 物件裡面,例如 /posts/a/b/c 的 query 會是:
{ "slug": ["a", "b", "c"] }
知道了 catch all routes 之後,還有另一種更有彈性的 dynamic routes,只要把 catch all 的 [...slug] 改成 [[...slug]] 就會變成 optional catch all。意思是 slug 這個 array (陣列) 可以是不存在的,
pages/posts/[[...slug]].js 可以代表 /posts/a 也可以是 /posts/a/b,/posts/a/b/c 等,不過也可以代表 /posts
所以 query 物件會是空物件 {}。
注意:
query在 static (靜態) 頁面在第一次渲染的時候不會有東西,就是一個空物件{},等到 hydration 完成之後,query才會被更新
繼續到下一個章節之前,我們複習一下~
雖然 routing 方式不太一樣,不過 Next.js 還是跟 SPA (Single-page Application) 一樣可以做 client-side routing,意思是換頁的時候瀏覽器不用重新 reload,使得整個體驗非常順。Next.js 提供兩種方法:
<Link> (next/link)useRouter (next/router)next/link 提供 Link component 可以直接用在我們的 react components 裡面,跟 react-router-dom 裡的 Link 差不多的用法。當我們想要在畫面上顯示一個連結的時候,可以用 Link,他底層用 HTML 的 a 實作出來的。比較特別的是,當使用者畫面上有顯示 <Link /> components, Next.js 會把所有採用 SSG / ISR 的頁面先做 prefetch (事先抓取) 這些頁面的檔案與資料,所以當使用者點下其中一個連結,瀏覽器可以馬上顯示新的畫面不需要等待,提升使用者換頁的體驗。想要用 Link 有幾種方法:
import Link from "next/link"
function Home() {
return (
<div>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/about">
<a>About</a>
</Link>
</div>
)
}
export default Home
用 string (字串) 當 href,"/" 會連到 pages/index.js 的檔案,然後 "/about" 會連到 "pages/about.js" 檔案。
import Link from "next/link"
function Posts({ posts }) {
return (
<div>
{posts.map(post => (
<Link href={`/posts/${post.id}?utm_source=ithome`}>
<a>Post #{post.id}</a>
</Link>
))}
</div>
)
}
export default Posts
想要導頁去 dynamic routes 一樣可以把 path 組成 string 給 href,例如 /posts/${post.id} 然後 post.id 是動態的。或是可以把 path 拆成 object (物件) 給 href,像這樣~
{
pathname: "/posts/[pid]", // 跟 file name 一樣
query: { pid: post.id }, // path 中動態的部分,在這例子是 pid
}
query 不只可以包含 path 中的動態部分,也可以包含 URL 中的 query string,像 /posts/1?utm_source=ithome 這個 path 可以寫成:
{
pathname: "/posts/[pid]",
query: { pid: post.id, utm_source: "ithome" },
}
除了把連結顯示在畫面上讓使用者點下去以外,我們也可以用程式碼做導頁,就是用 useRouter。useRouter 回傳 router 物件,裡面包含各式各樣與 routing 有關的資訊,例如 pathname (當下的路徑), query (該路徑的 query string), isFallback (伺服器是否正在顯示備份版本的頁面), isReady (瀏覽器是否已經拿到 route 相關資訊) 等資訊。不過 router 還包含一些 methods,有幾個跟導頁有關的:
什麼是 shallow routing 或是淺層換頁 (淺層路由)?換頁的過程中伺服器不會重跑 data fetching 的 functions,也不會 re-render (重新渲染) 整個頁面保持該頁的狀態。雖然頁面不會被換掉,可是 router 中的 pathname 和 query 會拿到最新值。
router.push("/?id=1", undefined, { shallow: true })
不過要注意,shallow routing 只能用在同一個頁面,/?id=1 換去 /about?id=1 是不同的頁面,所以 Next.js 會跟平常一樣去下載新的頁面。

File-based routing 是不是看起來很棒呢?~ 不過還是要注意小細節喔,避免遇到 routing 相關問題。明天跟大家分享讓 Next.js 變成一個全端框架的原因,就是 API routes!
祝大家中秋節快樂!
晚安 <3