iT邦幫忙

2021 iThome 鐵人賽

DAY 9
0
Modern Web

從零開始學習 Next.js系列 第 9

Day09 - 在 Next.js 中使用 pre-rendering (getServerSideProps)

前言

Next.js 的 pre-rendering 分成兩種形式,一種是 SSG (Static Side Generation),另一種是 SSR (Server Side Rendering)。SSG 與 SSR 不一樣地方是,SSG 是 next build 打包 HTML 後,每次使用者都會是拿到一樣的 HTML。SSR 則是每次使用者瀏覽頁面時,伺服器都會重新建立 HTML,會回傳新的 HTML 給使用者。

先前我們已經知道怎麼在 Next.js 做 SSG,接下來我們要來了解怎麼在 Next.js 中做 SSR。


getServerSideProps

getServerSideProps 的寫法跟 getStaticProps 很相似,一樣是在跟 component 同一個檔案 export 一個 async 的 function,然後回傳的 props 就會注入到 component 中,讓 Next.js 幫我們做 SSR:

import { GetServerSideProps } from "next";

export const getServerSideProps: GetServerSideProps = async (context) => {
  return {
    props: {},
  };
};

範例

我們改寫前幾天有寫過的 pages/products/[id].tsx ,把之前的 isFallback 刪除,把這個頁面當作是 Pure Component,只渲染內容而已:

import { Product as ProductType } from "../../fake-data";
import ProductCard from "../../components/ProductCard";
import { PageTitle, ProductContainer } from "./[id].style";

interface ProductProps {
  product: ProductType;
}

const Product = ({ product }: ProductProps) => {
  return (
    <>
      <PageTitle>商品詳細頁面</PageTitle>
      <ProductContainer>
        <ProductCard product={product} all />
      </ProductContainer>
    </>
  );
};

export default Product;

接著,我們看到怎麼在元件中寫 getServerSideProps ,首先我們呼叫 fake API,因為這個 function 也是 async 所以可以使用 await 的關鍵字等待 fetch 回傳結果,然後再轉換成 JSON 格式傳到 component 中。

import { GetServerSideProps } from "next";
import { ParsedUrlQuery } from "querystring";

// react component

interface Params extends ParsedUrlQuery {
  id: string;
}

export const getStaticProps: GetServerSideProps<ProductProps, Params> = async ({
  params,
}) => {
  // params! 是用來斷言 params 一定不是 null 或 undefined
  const api = `https://fakestoreapi.com/products/${params!.id}`;
  const res = await fetch(api);
  const json: ProductType = await res.json();

  return {
    props: { product: json },
  };
};

讀者應該會發現 GetServerSideProps 多了兩個 generic type,分別為 ProductPropsParamsProductProps 被用於定義回傳 props 的型別,而 Params 則是用來定義 url 中 query string 的型別。

先前我們使用 file-based routing 中提過,由檔案的名稱定義路由可以符合多種形式,所以 params 可能會有多種的型別,接下來我們要來查找它在 Next.js 中的型別是什麼?

我們直接從 Next.js 的 GetServerSideProps 往裡面追 params 的型別定義,會看到它的型別繼承了 ParsedUrlQuery

export type GetServerSidePropsContext<
  Q extends ParsedUrlQuery = ParsedUrlQuery
> = {
  params?: Q;
};

然後,繼續往下追 ParsedUrlQuery 的型別定義是 stringstring[]

interface ParsedUrlQuery extends NodeJS.Dict<string | string[]> {}

現在我們可以知道 params 的型別有可能會是 stringstring[] 或是 undefined 其中一種,所以在使用 params 這個物件時可以為它撰寫型別,讓程式碼更嚴謹,像是上述的範例中定義 id: string

什麼時候要使用 getServerSideProps

根據 Next.js 官方說明,如果需要每次使用者瀏覽網頁時,伺服器都能呼叫 API,將最新的資料都注入到 HTML,則可以選擇使用 getServerSideProps 。否則,如果不在意使用者是否拿到最新的資料,可以考慮使用 getStaticProps

如果以成本面來看,由於 SSG 不需要每次使用者瀏覽頁面時都重新執行 getStaticProps ,可以直接回傳靜態的 HTML 給使用者,甚至可以仰賴 CDN 的 cache 大量減少伺服器的成本。反之, getServerSideProps 每次使用者瀏覽一個頁面時時都要讓伺服器執行 getServeSideProps 中的程式碼,如果大多數頁面都是 SSR 將會對伺服器造成負擔。

再從使用者體驗的觀點來看,SSG 除非是設定 fallback: 'blocking' ,否則使用者在 SSG 的頁面看到內容的平均速度會比 SSR 更快,會導致 web vitals 的其中一項指標 Time to First Byte (TTFB) 在 SSG 頁面的表現比較好。


Automatic Static Optimization

最後,我們來談談 Automatic Static Optimization 這項功能。

在 Next.js 中除了自己撰寫 getStaticPropsgetServerSideProps 決定一個頁面是 SSG 或 SSR 之外,它可以自動化分析一個頁面是不是有依賴一些 API 的資料回傳,如果沒有的話,這個頁面在不用加上 getStaticPropsgetServerSideProps 的情況下,就可以獲得 pre-rendering 的好處。

以下是一個一簡頁面,雖然沒有寫 getStaticPropsgetServerSideProps 這兩個 function,但是從網頁的原始中可以看到 <div> 裡面是有字的,與原生的 React 一開始都只會給予空的節點是不一樣的。

function Page() {
  return (
    <div>
      <div>Next.js</div>
      <div>Next.js</div>
      <div>Next.js</div>
    </div>
  );
}

export default Page;

在 Automatic Static Optimization 後,Next.js 會在 next build 的階段為符合的頁面產生 .html 檔案,假設上方範例的頁面是 pages/home.tsx ,所以就會產生對應的 HTML 檔案:

.next/server/pages/home.html

但如果加上了 getSererSideProps 或是 getInitialProps ,則頁面就會變成 .js 檔案:

.next/server/pages/home.js

Automatic Static Optimization 的條件

要觸發這個自動優化的功能,頁面不能加上 getSererSideProps 或是 getInitialProps ,亦即該頁面不是走 SSR 的流程。

再者,如果在專案中的 Custom App _app.ts 中使用 getInitialProps ,將會導致整個專案的自動優化功能消失。

例如以下便是讓自動優化消失的範例:

import "../styles/globals.css";
import App, { AppProps, AppContext } from "next/app";

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}
MyApp.getInitialProps = async (appContext: AppContext) => {
  const appProps = await App.getInitialProps(appContext);

  return { ...appProps };
};

export default MyApp;

小結

在這篇文章中我們知道在 Next.js 中使用 SSR 必須使用 getServerSideProps ,如此一來就可以讓使用者在瀏覽網頁時都能夠拿到最新的資訊。但是 Next.js 官方說到如果沒有「使用者必須拿到最新資料」的需求,則是推薦使用 getStaticProps ,因為不論是以使用者體驗或是成本面,SSG 的效果都比 SSR 更好。

而且 Next.js 甚至有自動化分析頁面是否可以進行 SSG,不用撰寫 getStaticProps 就可以讓頁面擁有 SSG 的優點,所以讀者在使用 Next.js 時也能夠以 SSG 為優先考量,如果有上述需求時再使用 SSR 撰寫頁面。

Reference


上一篇
Day08 - 在 Next.js 中使用 pre-rendering (getStaticProps) — Part 2
下一篇
Day10 - 為什麼官方不推薦使用 getInitialProps
系列文
從零開始學習 Next.js30

尚未有邦友留言

立即登入留言