之前我們已經看了幾種不同的渲染模式,例如 SSR、CSR、SSG 與 ISR,也了解 RSC 是什麼了。今天我們接著來看看與這些渲染模式有著密不可分的角色「Router」。
Router 是什麼?簡單來說,它的主要作用就是用來「切換頁面」。但在不同的框架中,它能做到的不只如此。以 React 為例,Router 不只是負責導航,還會影響頁面最終的渲染方式。例如:當我們使用 CSR 模式時,Router 會確保頁面在切換時,仍維持在 client 端執行,呈現 SPA 的行為。
這次我們會先從回顧 React 的 Router 寫法,接著再進入 Next.js 中,了解在 Next.js 中該怎麼使用 Router,以及了解 Page Router 與 App Router 的使用及差異。
由於 React 是一個函式庫,不是一個框架,所以 React 自己並沒有提供 Router 的功能,如果想要在 React 中,以維持 CSR 的模式來達到頁面切換的效果,就必須安裝 react-router-dom 這個 Libaray。這樣的 Router 會透過瀏覽器的 history API,來實現頁面切換時不重新載入整個頁面,呈現出單頁應用(SPA)的體驗。
在安裝 react-router-dom 後,並不是直接就能維持 CSR 這個渲染模式的頁面切換功能,而是必須透過 config 檔案去定義 Router。
在 react-router-dom v6 版本中,主要會是以下述的方法去定義 Router。
這裡是定義 Router 的 config 檔案內容。
import { createHashRouter } from 'react-router-dom';
import HomePage from '../HomePage';
import AboutPage from '../AboutPage';
const Router = createHashRouter([
{
path: '/',
element: <Home />
},
{
path: 'about',
element: <About />
},
]);
export default Router;
接著還需要把 config 內容綁定在整個 App 的入口點。
import { RouterProvider } from 'react-router-dom';
import Router from './router';
function App() {
return (
<div className="App">
<RouterProvider router={Router} />
</div>
);
}
export default App;
再透過以上的設定後,才會將可以維持 CSR 這個渲染模式的 Router 套用在 React 專案中。
剛剛提到的是定義一般 Router 的 config 寫法,如果還需要定義動態的路由或是子路由的話,會需要在原本的 config 內容中,加上一些不同的寫法,例如以下這樣:
import { createHashRouter } from "react-router-dom";
import HomePage from "./components/HomePage";
import AboutPage from "./components/AboutPage";
import AboutA from "./components/AboutA";
const Router = createHashRouter([
{
path: "/",
element: <HomePage />,
},
{
path: "about",
children: [
{
path: "a", // 完整路徑為 #/about/a
element: <AboutA />,
},
],
},
]);
export default Router;
如果要設定動態路由的話,在 React 中是透過以下的寫法定義在 Router config 檔案裡面。
例如以下這樣:
const Router = createHashRouter([
{
path: "/",
element: <HomePage />,
},
{
path: "about",
children: [
{
path: ":id",
element: <AboutA />,
},
],
},
]);
這樣設定後當輸入的網址是/about/123 或 /about/abc 都會進入到 AboutA 這一頁。如果不是以動態路由寫的話,就需要依照設定的 path,才能進入到 element 這個 key 指定的 component。而且在 component 中可以透過 useParams 取得網址中的動態路由部分,例如像下面這樣寫的話,就可以取得設定為動態路由的 id 部分。
const { id } = useParams();
以上這個部分就是快速回顧一下 React 搭配 react-router-dom 設定路由系統的基本設定。
react-router-dom 的時候,通常都會使用 hash mode,網址就會是類似這個樣子的形式:http://localhost:3000/#/about 。在切換網址時,# 後面的網址內容不會被送給伺服器做處理,會交給前端路由機制進行畫面的切換及顯示,以達到 SPA 的效果。
如果想要以沒有 # 顯示在網址上的方式實作 React Router,那就需要使用 History mode(也就是使用 BrowserRouter 來建立路由系統)。但若要使用 History mode,就必須有後端的支援,否則在切換頁面或重新整理時,就會出現 404 的狀況。
這是因為在 History mode 下,每次網址改變時,瀏覽器會對該路徑發出 HTTP 請求,而在 CSR(Client-Side Rendering)模式下,實際上伺服器根本沒有對應的頁面檔案,整個前端應用只會有一個入口檔案,那個檔案是 index.html。因此,伺服器在收到任意路徑(例如:/about、/user/123)的請求時,都必須一律回傳 index.html,讓前端(例如 React)透過 router 根據網址渲染對應畫面,才能維持 SPA 的運作邏輯。
Next.js 和 React 在 Router 的使用上,有相當大的差異。首先,Next.js 因為是有支援 Router 系統的框架,所以在使用 Router 時,並不需要像 React 那樣,還要額外安裝 react-router-dom 這樣的第三方套件去呈現 router 的功能。再來是在 Next.js 中下如果想要定義 Router,並不需要寫一個 config 檔案去定義,而是透過 Next.js 規定好的資料夾及檔案結構,讓 Next.js 這套框架自己去解析所有的路由設定。
在正式開始看 Next.js 定義路由的方式之前,我們先來看兩個我們接下來幾天在深入看 Next.js Router 功能時,可能會常常出現的兩個詞彙的定義,這兩個詞彙分別是「Route」和「Route Segment」。
Route 這個詞彙大家應該不陌生,中文通常會翻譯成「路由」。它指的就是 Router 系統底下的「單一路由規則」或「單一路由項目」。
例如:在 React Router 的 config 中,如果我們設定了
{
path: "about",
children: [
{
path: ":id",
element: <AboutA />,
},
],
},
那麼這整條設定就是一個 Route,而其中的 /about 則是這個 Route 對應到的 path。
換句話說,Route 就是透過 Router 系統定義的某一個 URL 的規則。
Route Segment 則和 Route 有所不同,它指的是路由段。
如果有一個 URL 是 /my-website/profile,這整條會是一個 Route;但它是由多個 Route Segments 組成,例如 my-website 與 profile 就是兩個 segment。
在 Next.js (App Router) 中,這樣的 segment 會直接對應到一個獨立的資料夾,像:
了解這兩個基礎名詞定義後,就接著來看看 Next.js 定義路由的方式吧!
在 Next.js 中,不論是 Page Router 模式,還是 App Router 的模式 都不需要寫 Router 的 config 檔案來定義 Router,只需要用資料夾就能定義 Router。
∙ Page Router 模式
在 Page Router 模式下,會透過在 pages 資料夾底下以建立資料夾和檔名為 index 的檔案來設定單個 Route。
實際的寫法也就會例如下圖這樣的方式,這樣寫的話,就是設定了 / 和 /about 這兩個路由。
∙ App Router 模式
在 App Router 的模式下,會透過在 app 資料夾底下以建立資料夾,以及搭配建立名為 page 的檔案,來定義 單個 Route。
例如以下這個寫法的話,就是設定了 / 和 /about 這兩個路由的頁面。
如果需要設定子路由和動態路由,一樣也可以很直覺地使用資料夾結構來呈現,這裡透過幾個實際的範例來看看。
不論是 Page Router 還是 App Router,如果想要在 about 這個路由段底下,再多一層的話,就是很直覺地在底下再用想要加的 path 名稱開一個資料夾就好。
如下圖所示,在 Page Router 底下,如果希望呈現的頁面網址是/about/a,那我就是在 about 資料夾下再開一個 a 資料夾,並且建立 index 檔案就好。
在 App Router 底下,如果希望呈現的頁面網址是/about/a,一樣也是在 about 資料夾下再開一個 a 資料夾,並且建立 page 檔案就好。
如果想要設定路由,在 Page Router 模式和 App Router 模式下,都是使用以[]標示的資料夾來使用。
例如想要讓網址 about/1 或 about/2 都是進到相同頁面檔案的話,就是在 about 資料夾底下以 [動態path名] 這樣的格式去建立資料夾。
就如下圖這樣,在 Page Router 下建立名為 [id] 的資料夾後,透過 about/1 或是 about/2 就都會顯示資料夾底下之 index 檔案渲染出來的頁面。
在 App Router 下,也是用一樣的方式去建立動態路由,差異只在於檔案名稱需要命名為 page。
前面看 React Router 的內容有提到過不論是 History mode 還是 Hash mode 都會是以 CSR 這個渲染模式來呈現頁面,這是因為在 React 的機制下,build 檔案的時候,只會產出入口的 index.html 檔案,且這個 HTML 檔案並不會包含完整的頁面內容,裡面只會有執行後續頁面渲染相關的 JavaScriprit 檔案,並會透過這個檔案將初次進入的頁面以及切換的頁面渲染出來。
但是在 Next.js 的 Router 系統下,就有點不太一樣了。相較於 React 的純 CSR 機制,Next.js 採用了「預渲染」的策略,也就是在 build time 階段,Next.js 會產出對應內容的 HTML 和資料(例如 JSON)。當我們第一次進入頁面時,就會直接用 response 返回的完整 html 檔案然顯示在畫面中,當使用者透過 或 router 切換頁面時,Next.js 則會以 Client-side Rendering(CSR)的方式載入資料並更新畫面,實現流暢的 SPA 體驗。
總結來說,Next.js 並不會像 React 只侷限於單一渲染模式,而是結合了 SSG、SSR、CSR、甚至是 RSC 的混合式架構,讓每個頁面都能根據實際需求選擇最佳渲染方式,以實現優化網站效能與使用者體驗的效果。但是 Next.js 為了頁面的效能,會盡可能地使用預選染的方式,也就是 SSG 的方式來渲染頁面,除非有動態的內容,才會在進入頁面時,採用 SSR 的方式進行渲染。
最後快速來一個小總結!
在 React 中,Router 的設定必須依賴第三方套件(例如 react-router-dom),並透過 config 檔案來定義路由;在 Next.js 中,則是內建了 Router 系統,不需要額外安裝套件,也不需要撰寫 config 檔案,只需透過資料夾與檔案結構,Next.js 就能自動解析路由。
在渲染模式的部分,React Router 預設為 CSR,且僅支援 CSR;而 Next.js 則支援多種渲染方式:在首次載入頁面時,會根據頁面設定使用 SSG 或 SSR,進而產生完整的 HTML 回傳給瀏覽器;當使用者透過 或 router 進行頁面切換時,則會以 CSR 的方式在 client 端更新畫面,維持 SPA 的使用體驗。
今天快速回顧了 React 的 Router,還有看了 Next.js 的基本用法,明天還會繼續深入了解更多關於 Next.js Router 細節內容。