Pages

注意: 我們正在改進 Next.js 的路由。 Layouts RFC 提供更多詳細的資訊及回饋管道。

在 Next.js 中,一個頁面( Pages ) 就是從 pages 目錄底下匯出的一個 React 元件,它可以是 .js.jsx.ts.tsx 任意一種檔案格式。每個檔案的檔名會進而關聯到一個頁面的路由。

範例:如果你建立了 pages/about.js 檔案,並匯出了如下的 React 元件,你將會在網站的 /about 頁面下看到它。

function About() { return <div>About</div> } export default About

在頁面中使用動態路由

Next.js 支援具有動態路由的頁面。例如,如果你建立了一個名為 pages/posts/[id].js 的檔案,那麼你將可以在網站的 posts/1posts/2 等路徑下看到它。

想要學習更多有關動態路由的資訊,請參閱 動態路由文件

提前渲染

在預設的情況下,Next.js 會提前渲染( pre-render ) 每個頁面。這意味著 Next.js 會提前為每個頁面產生 HTML,而不是讓所有事情都由客戶端 JavaScript 完成,這可以為我們帶來更好的效能與搜尋引擎最佳化( SEO )。

每個產生的 HTML 都會與該頁面所需要的最少 JavaScript 程式碼相關聯。當瀏覽器載入一個頁面時,它的 JavaScript 程式碼會執行並使頁面可以讓使用者進行互動(這個過程被稱為 hydration )。

兩種形式的提前渲染

Next.js 有兩種形式的提前渲染:靜態生成( Static Generation )伺服器端渲染( Server-side Rendering )。兩者的差別在於它們產生頁面 HTML 的時機

更重要的是,Next.js 讓你可以選擇在每個頁面中使用哪種形式的提前渲染。你可以建立一個「混合」的 Next.js 應用程式,並使用靜態生成來處理大部分的頁面,並使用伺服器端渲染來處理其他頁面。

我們建議在效能方面使用靜態生成而不是伺服器端渲染。靜態生成的頁面可以透過 CDN 進行快取,而不需要額外的設定來提升效能。然而,在某些情況下,伺服器端渲染可能是唯一的選擇。

你也可以在靜態生成或伺服器端渲染的頁面中使用客戶端資料取得。這意味著頁面的某些部分可以完全由客戶端 JavaScript 進行渲染。要學習更多相關內容,請參閱資料獲取文件。

靜態生成(建議)

範例

如果一個頁面使用靜態生成,頁面 HTML 將在建置時產生。這意味著在生產環境中,當你執行 next build 時,頁面 HTML 將會被產生。然後,這個 HTML 將會在每個請求中被重複使用,也因此它可以被 CDN 快取。

在 Next.js 中,你可以使用或不使用資料來靜態生成頁面。讓我們來看看每個情況。

不使用資料的靜態生成

在預設的情況下,Next.js 使用靜態生成預先渲染頁面,而不會抓取資料。這是一個範例:

function About() { return <div>About</div> } export default About

注意到這個頁面不需要抓取任何外部資料來預先渲染。在這種情況下,Next.js 在建置時為每個頁面產生一個 HTML 檔案。

使用資料的靜態生成

有些頁面需要抓取外部資料來預先渲染。這個時候會分成兩種情況,也可能同時屬於兩個情況。在每種情況下,你可以使用 Next.js 提供的這些函數:

  1. 你的頁面內容依賴外部資料:使用 getStaticProps
  2. 你的頁面路徑依賴外部資料:使用 getStaticPaths (通常會和 getStaticProps 一起使用)。

情況 1:你的頁面內容依賴外部資料

範例:你的部落格頁面可能需要從 CMS (內容管理系統)抓取部落格文章列表。

// TODO: Need to fetch `posts` (by calling some API endpoint) // before this page can be pre-rendered. function Blog({ posts }) { return ( <ul> {posts.map((post) => ( <li>{post.title}</li> ))} </ul> ) } export default Blog

為了在預先渲染時抓取這些資料,Next.js 允許你從同一個檔案 export 一個 async 函數,這個函數叫做 getStaticProps。這個函數會在建置時被呼叫,並讓你在預先渲染時傳遞抓取到的資料給頁面的 props

function Blog({ posts }) { // Render posts... } // This function gets called at build time export async function getStaticProps() { // Call an external API endpoint to get posts const res = await fetch('https://.../posts') const posts = await res.json() // By returning { props: { posts } }, the Blog component // will receive `posts` as a prop at build time return { props: { posts, }, } } export default Blog

要學習更多關於 getStaticProps 如何運作的資訊,請參考 資料抓取文件

情況 2:你的頁面路徑依賴外部資料

Next.js 允許你建立具有動態路徑的頁面。例如,你可以建立一個名為 pages/posts/[id].js 的檔案,來顯示一篇部落格文章,這篇文章的 id 是動態的。這樣就可以讓你在存取 posts/1 時,顯示 id: 1 的部落格文章。

要學習更多關於動態路由的資訊,請參考 動態路由文件

然而,你在建置時想要預先渲染哪個 id 可能會取決於外部資料。

範例:假設你只在資料庫中新增了一篇部落格文章(並且他的 id: 1)。在這種情況下,你只想在建置時預先渲染 posts/1

之後,你可能會新增第二篇部落格文章(並且他的 id: 2)。然後你也想要預先渲染 posts/2

所以你的頁面路徑會依賴外部資料。為了處理這個情況,Next.js 允許你從一個動態頁面(在這個範例中,指的是 pages/posts/[id].jsexport 一個 async 函數,叫做 getStaticPaths。這個函數會在建置時被呼叫,並讓你指定哪些路徑你想要預先渲染。

// This function gets called at build time export async function getStaticPaths() { // Call an external API endpoint to get posts const res = await fetch('https://.../posts') const posts = await res.json() // Get the paths we want to pre-render based on posts const paths = posts.map((post) => ({ params: { id: post.id }, })) // We'll pre-render only these paths at build time. // { fallback: false } means other routes should 404. return { paths, fallback: false } }

同時,在 pages/posts/[id].js 中,你也需要 export 一個 getStaticProps 函數,這樣你就可以抓取這個 id 的部落格文章資料,並用它來預先渲染頁面:

function Post({ post }) { // Render post... } export async function getStaticPaths() { // ... } // This also gets called at build time export async function getStaticProps({ params }) { // params contains the post `id`. // If the route is like /posts/1, then params.id is 1 const res = await fetch(`https://.../posts/${params.id}`) const post = await res.json() // Pass post data to the page via props return { props: { post } } } export default Post

想要學習更多關於 getStaticPaths 如何運作的資訊,請參考 資料抓取文件

我應該什麼時候使用靜態生成?

我們建議你盡可能使用靜態生成(無論頁面是否需要外部資料),因為你的頁面可以被建置一次,並且由 CDN 提供服務,這使得它比在每個請求中由伺服器渲染頁面來的快。

你可以使用靜態生成來建立許多種類的頁面,包括:

  • 行銷頁面
  • 部落格文章和作品集
  • 電商網站的產品列表
  • 幫助和文件

你要問自己一個問題:「我可以在使用者請求之前預先渲染這個頁面嗎?」如果答案是肯定的,那麼你應該選擇靜態生成。

在另一個方面,如果你不能在使用者請求之前預先渲染頁面,那麼靜態生成就不是一個好主意。也許你的頁面會顯示頻繁更新的資料,並且每個請求都會改變頁面內容。

在這種情況下,你可以做以下其中一件事:

  • 使用靜態生成但從客戶端抓取資料:你可以跳過部分頁面的預先渲染,然後使用客戶端 JavaScript 來補充它們。要了解更多關於這種方法的資訊,請參考 資料抓取文件
  • 使用伺服器端渲染:Next.js 會在每個請求中預先渲染頁面。這會比較慢,因為頁面不能被 CDN 快取,但是預先渲染的頁面會一直是最新的。我們會在下面討論這種方法。

伺服器端渲染

也被稱為「SSR」或「動態渲染」。

如果一個頁面使用伺服器端渲染,那麼頁面的 HTML 會在每個請求中被生成。

為了使用伺服器端渲染,你需要 export 一個 async 函數,並且命名為 getServerSideProps。這個函數會在每個請求中被伺服器呼叫。

舉例來說,假設你的頁面需要預先渲染頻繁更新的資料(從外部 API 抓取的資料)。你可以寫一個 getServerSideProps 函數,它會抓取這些資料,然後像下面這樣把它們傳遞給 Page

function Page({ data }) { // Render data... } // This gets called on every request export async function getServerSideProps() { // Fetch data from external API const res = await fetch(`https://.../data`) const data = await res.json() // Pass data to the page via props return { props: { data } } } export default Page

如同上面你所看到的,getServerSidePropsgetStaticProps 很相似,但是它們的差別在於 getServerSideProps 會在每個請求中被呼叫,而不是在建置時。

要了解更多關於 getServerSideProps 如何運作的資訊,請參考我們的資料抓取文件

總結

我們在這篇文章討論了 Next.js 的兩種預先渲染方式。

  • 靜態生成(建議): HTML 會在建置時被生成,並且會在每個請求中被重複使用。要讓頁面使用靜態生成,你可以 export 頁面元件,或者 export getStaticProps(如果需要的話也可以 export getStaticPaths)。這對於可以在使用者請求之前預先渲染的頁面來說是很棒的。你也可以和客戶端渲染一起使用,以便帶入額外的資料。
  • 伺服器端渲染: HTML 會在每個請求中被生成。要讓頁面使用伺服器端渲染,你可以 export getServerSideProps。 因為伺服器端渲染比靜態生成的效能較差,所以只有在絕對必要的情況下才使用。

了解更多

我們建議你接著閱讀下面的章節:

本篇文章由yuaanlin

yuaanlin

貢獻翻譯。瞭解如何參與貢獻