嗨嗨嗨!昨天看完怎麼抓取資料的方式會影響到網頁的渲染模式之後,今天的主題是 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