Learn Next.js 官方教學筆記 - Chapter 8: Static and Dynamic Rendering

此系列為 Next.js 官方教學 Learn Next.js 的筆記。

文章連結:Chapter 8: Static and Dynamic Rendering


在上一個章節中,幫 Dashboard Overview page fetch 了相關資料。然而,我們要討論現在設定下的兩個限制:

  1. 現在的 data request 會造成 waterfall。
  2. 目前 dashboard 是 Static,所以任何資料的更新都沒辦法顯示在畫面上。

在這一章節可以學到以下幾個項目:

  • 什麼是 static rendering 以及他如何改善效能?
  • 什麼是 dynamic rendering以及何時使用它?
  • 有什麼不同的方式讓 dashboard 可以是 dynamic 的。
  • 利用模仿緩慢的 data fetch 過程看會發生什麼?

What is Static Rendering?

有了 static rendering,data fetching 以及 rendering 會在 server 的 build time (當你 deploy 時)或是 revalidation 時發生。而這個結果會被分派以及 cache 在 Content Delivery Network (CDN)

Image.png

而不論使用者何時使用你的網站,結果都已經被 cache 保存起來。這樣的 static rendering 方式可以有幾個好處:

  • 更快的網站:Prerendered 內容可以先被 cache 以及分送到全球。這樣的方式可以確保全球的使用者可以更快速且穩定的閱讀你的網站內容。
  • 減少 Server 負擔:因為內容已經被 cache 了,所以 server 不用再依據使用者的 request 來動態地產生內容。
  • SEO:Prerendered 內容可以讓搜尋引擎比較容易爬到內容,因為內容再載入時就已經存在了。這會有助於搜尋到的排名。

Static rendering 對於沒有使用資料的 UI 以及在使用者間共享的資訊很有用,比如部落格或是產品頁面。但對於那種會頻繁更新使用者資訊的網站就不那麼適合。

而相對於 static rendering 的概念就是 dynamic rendering。

What is Dynamic Rendering?

使用 dynamic rendering 的話,網站內容會基於每個使用者的 request time (當使用者拜訪網站時),在 server 上 render。而這樣的方式有幾個好處:

  • 即時的資料:Dynamic rendering 可以讓你的網站呈現即時或是頻繁更新的資料。這樣的方式在資料更新快速的網站是比較理想的。
  • 使用者特定的內容:這種方式會比較好存取使用者的個人資訊,比如 dashboard 、 profile 或是基於使用者互動後的資料。
  • Request time information:Dynamic rendering 可以讓你取得只有在 request time 時才能看到的資料,比如 coolies 或是 URL 的搜尋參數。

Making the dashboard dynamic

在預設時,@vercel/postgres 不會設定自己的 cache 方式。而是讓框架來設定用 static 或是 dynamic 的方式。

所以你可以在你的 Server Component 或是 data fetch function 使用 Next.js API 來呼叫 unstable_noStore,來設定不要使用 static render。讓我們來試試看。

在你的 data.ts,從 next/cache 裡 import unstable_noStore,並在你的 data fetching function 裡呼叫它:

// ...
import { unstable_noStore as noStore } from 'next/cache'; // ADD

export async function fetchRevenue() {
  // Add noStore() here to prevent the response from being cached.
  // This is equivalent to in fetch(..., {cache: 'no-store'}).
  noStore(); // ADD

  // ...
}

export async function fetchLatestInvoices() {
  noStore(); // ADD
  // ...
}

export async function fetchCardData() {
  noStore(); // ADD
  // ...
}

export async function fetchFilteredInvoices(
  query: string,
  currentPage: number,
) {
  noStore(); // ADD
  // ...
}

export async function fetchInvoicesPages(query: string) {
  noStore(); // ADD
  // ...
}

export async function fetchFilteredCustomers(query: string) {
  noStore(); // ADD
  // ...
}

export async function fetchInvoiceById(query: string) {
  noStore(); // ADD
  // ...
}

備註:unstable_noStore 是一個實現性質的 API,他有可能未來會做改變。所以如果在你的專案中你偏好使用較穩定的 API,你也可以使用 Segment Config Option export const dynamic = "force-dynamic”

Simulating a Slow Data Fetch

讓 dashboard 的資料變成 dynamic 的方式雖然是個不錯的開始。然而,還是會有一個前一章節提到的問題。那就是如果 request 比其他所有 request 都慢會發生什麼事?

那就讓我們來模擬一個緩慢的 data fetching 過程。在 data.ts 檔案中,取消註解 console.log 以及在 fetchRevenue() 裡的 setTimeout

// /app/lib/data.ts
export async function fetchRevenue() {
  try {
    // We artificially delay a response for demo purposes.
    // Don't do this in production :)
    console.log('Fetching revenue data...'); // uncomment
    await new Promise((resolve) => setTimeout(resolve, 3000)); // uncomment

    const data = await sql<Revenue>`SELECT * FROM revenue`;

    console.log('Data fetch completed after 3 seconds.'); // uncomment

    return data.rows;
  } catch (error) {
    console.error('Database Error:', error);
    throw new Error('Failed to fetch revenue data.');
  }
}

然後開啟 http://localhost:3000/dashboard/ 後,去注意頁面載入的時間花了多少。而在終端機中也可以看到以下訊息:

Fetching revenue data...
Data fetch completed after 3 seconds.

這邊就是模擬一個需要三秒的緩慢 data fetching 過程。這樣的所呈現出來的結果就是,現在應該要顯示在頁面的資料,都因為這個 fetching 過程而被 block 住了。

這是一個開發人員都會遇到的共同難題:

在 dynamic rendering 的方式下,頁面的速度只會跟最慢的那一個 data fetching 一樣而已。