iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0
Modern Web

從 React 學 Next.js:不只要會用,還要真的懂系列 第 11

【Day 11】用 Next.js 實作 SSR/SSG/ISR

  • 分享至 

  • xImage
  •  

昨天雖然成功用純 React 實作出 SSR 和 SSG 了,但實際上還是有很多需要調整的地方,而且也只是一個很簡易的小實作而已,如果要拿來當作一個完整專案來使用,一定會遇到更多問題。這時候就該派出我們的第一男主角 - Next.js 登場了。

究竟在這幾種渲染模式的使用上,使用 Next.js 是否真的有比較方便呢?就讓我們繼續看下去!

從建立的 Next.js 專案開始

透過 Next.js 官網可以看到使用以下這個指令,並且依照自己的需求選擇框架提供的設定選項就能輕鬆建立起一個 Next.js 框架的專案。
npx create-next-app@latest

如果使用 page router 建立專案,可以看到專案內的預設的資料夾結構及檔案內容如下。
https://ithelp.ithome.com.tw/upload/images/20250901/201309145ni3jpIKSZ.png

如果使用 App Router 建立專案,則可以看到如下的資料夾結構及檔案內容。
https://ithelp.ithome.com.tw/upload/images/20250901/20130914efoVcw4GW0.png

從這個預設的資料夾結構和檔案內容可以發現到很多東西都已經被設定好了,自己只要依照實際上的需求進行調整就可以了,也不需要跑很多指令去把相關套件一個個添加上去。

不過我們今天只會先看在 Next.js 框架中,可以怎麼使用特定渲染模式,Page Router 和 App Router 細節的用法會在後面的篇幅才看。

用 Next.js 使用 SSR

在 Next.js 的 Page Router 和 App Router 模式中,當想要指定使用 SSR 時,寫法上會有一些差異,我們先來看當使用 Page Router 時,要使用 SSR 該怎麼做。

Page Router
如果在 Page Router 下,想要使用 SSR 這個渲染模式的話,在 Next.js 框架中,不需要額外自己建立的一個 node server,只需要使用 getServerSideProps 這個函式,在 Next.js 框架中就會被認定要使用 SSR。

這裡直接看一個實際的使用例子來看看,在一個首頁對應到的檔案中,使用加上 getServerSideProps 這樣的方式寫,就會是以 SSR 的方式呈現。

const Home = () => {
  return (
    <div>
      <main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
        <h1>Home Page</h1>
        <p>some description</p>
      </main>
    </div>
  );
};

export default Home;


// 主要是這個部分
export const getServerSideProps = () => {
  return {
    props: {},
  };
};

App Router
在 App Router 中,並不是以 getServerSideProps 這個 function 來判斷要使用 SSR,而是以有沒有動態的資料,或是有沒有顯式設定成 force-dynamic 來決定使用 SSR 這個動態渲染的方式產生頁面。

如果沒有顯式設定,也沒有動態資料的話,那麼 Next.js 預設會將這個頁面視為靜態頁面(SSG)並在 build time 預先產生 HTML。

這裡準備一個寫有 fetch,且 cache 設定為 no-store 的元件(這麼做是為了讓這個元件使用的資料變成未 cache 的資料,也就是我們前面提到的會變成是動態資料),把它放在 page.tsx 裡面,如下:

// DynamicRSC.tsx
async function getProjects() {
  const res = await fetch(`https://fakestoreapi.com/products`, {
    cache: "no-store",
  });
  const projects = await res.json();

  return projects;
}

const DynamicRSC = async () => {
  const projects = await getProjects();
  return <div>{projects[0]?.title}</div>;
};

export default DynamicRSC;
// src/app/page.tsx
import DynamicRSC from "./components/DynamicRSC";
const Page = () => {

  return (
    <>
      <h1>Home page</h1>
      <p>some description</p>
      <DynamicRSC />
    </>
  );
};

export default Page;

當 build 的時候,可以發現到 build 的結果,/ 首頁變成標示成 f 的 server-rendered 頁面了。
https://ithelp.ithome.com.tw/upload/images/20250901/20130914ZVLtXouU00.png

用 Page Router 和 App Router 實作 SSG & ISR

在 SSG 和 ISR 的部分,一樣會依照是 Page Router 還是 App Router 有不同的寫法。

Page Router
這裡一樣先來看一下在 Page Router 下使用 SSG 或 ISR 的方式渲染頁面的寫法是什麼。如果是使用 Page Router 時,在什麼都沒有設定的狀況下,預就設會是 SSG。但是如果想要額外讓一些值在 build 的時候就被帶入 HTML 裡面就會需要使用到 getStaticProps 這個 function。寫法就是把 getServerSideProps 那段改成使用 getStaticProps 這個 function 就可以了,如下:

export const getStaticProps = async () => {
  return {
    props: {},
  };
};

如果頁面是動態路由(例如: /item/[id]),除了 getStaticProps,還要搭配 getStaticPaths,才能在 build 階段生成對應的靜態頁面。

export async function getStaticPaths() {
  return {
    //  這裡會需要放入可預期的動態資料
    paths: [{ params: { id: "1" } }],
    fallback: false,
  };
}

還記得 SSG 和 ISR 之間的差異只在於 ISR 會定期的更新嗎?如果想要使用 ISR 的時候,只需要在 getStaticProps 中設定 revalidate 來控制什麼時候更新靜態頁面就可以了。

export const getStaticProps = async () => {
  return {
    props: {},
    // 每隔 60 秒重新生成靜態頁面
    revalidate: 60
  };
};

App Router
在 App Router 中,Next.js 在一開始建立專案時,預設也會是 SSG。即使頁面中有使用 fetch(),若沒有額外設定 cache: 'no-store',就會是預設的 cache: 'force-cache',頁面會保持靜態化。但要注意如果頁面中同時使用了 cookies()、headers(),或是顯示被設定為是動態頁面,就會被轉換為 SSR。

若希望資料定期更新,則可以在 fetch() 中加上 next: { revalidate: 指定的時間 },例如:next: { revalidate: 60 },讓頁面進入 ISR 的模式。這樣一來,Next.js 就會在首次 build 時生成 HTML,並在 60 秒過期後,於下一次使用者請求時在背景重新產生新的 HTML,實現靜態生成,但仍可以定期更新的效果。

例如當用以下的寫法時,會是 SSG。

export default async function Home() {
  const res = await fetch("https://....");
  return (
    <div>
     // 略
    </div>
  );
}

即使有呼叫 fetch,只要沒有加上 cache: no-store,就一樣還是維持 SSG。

export default async function Home() {
  const res = await fetch("https://....");
  return (
    <div>
     // 略
    </div>
  );
}

如果調整成以下的寫法,把 revalidate: 60加上,則會變成會在特定時間後更新靜態頁面的 ISR。

export default async function Home() {
  const res = await fetch("https://....", {
    next: { revalidate: 60 }
  });
  return (
    <div>
     // 略
    </div>
  );
}

改成這個寫法之後,會發現 header 上有標示設定的 maxage,以及有標示可以鑑別內容是否有變更的 Etag,當內容變更後,可以觀察到 Etag 會有變動。
https://ithelp.ithome.com.tw/upload/images/20250901/20130914PbdiGWIRbr.png

Next.js 要怎麼使用 CSR

看了在 Next.js 上,要怎麼使用 SSR、SSG、ISR 這些不同的渲染模式後,我想應該會有人又突然有個疑問,那就是「如果要使用 CSR 呢?」

如果還記得前幾天有提到 Client Component 的部分的話(相關連結),在 Page Router 中,只要在元件中使用 useEffect、useState 這些 hooks,也就呈現 CSR 的模式。在 App Router 中,由於所有元件預設是 Server Component,要使用 CSR 的方式,則需要在檔案最上方加上 'use client',讓元件變成 Client Component。這樣就能使用例如 useEffect、useState 等的 hooks,並於瀏覽器端進行資料取得與渲染。

雖然說在 Next.js 中可以使用 CSR,但是 Next.js 主要的強項是 SSR、RSC 等對於 SEO 的優勢,所以如果單純只是想要使用 CSR,等於沒發揮出 Next.js 的優勢,就會變得有點大材小用了。

React vs Next.js 在渲染模式使用上的差異

昨天嘗試只使用 React 來實作 SSR 和 SSG 這兩種渲染方式,今天則是使用 Next.js 來使用 SSR 和 SSG 及 ISR 這幾種渲染方式,應該可以很明顯的感受到我們的第一男主角 Next.js 和第二男主角 React 兩者間的差異。

React 雖然也能實作出 SSR 或 SSG,但因為他只是函式庫,所以還需要搭配其他工具才能夠有 SSR、SSG 的效果。即使搭配其他工具了,因為必須自己把這些工具組合在一起,所以維護的便利性比較低,使用上也比較不便利。Next.js 則是在背後幫我們解決了這些不方便的地方,並提供對應的 function 或是一些指定的用法,讓我們在使用它提供的方式時,Next.js 就能知道我們現在要使用什麼樣的渲染模式,免除需要自己額外結合其他工具的麻煩,在專案寫法上也更統一,讓開發者能更專注於自己專案的開發,而不需要另外分神去維護這些渲染模式的架構和邏輯。

關於 Next.js 如何實作 SSR、SSG 和 ISR 的部分就到這裡,明天我們會再接著更細部地觀察一些關於 App Router 和 Page Rouer 在渲染模式上的一些小細節。

參考資料

官方文件 - Client-side Rendering (CSR)


上一篇
【Day 10】SSR 一定要用 Next.js ?能用 React 實作 SSR、SSG 嗎?
系列文
從 React 學 Next.js:不只要會用,還要真的懂11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言