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,分別為 ProductProps
與 Params
,ProductProps
被用於定義回傳 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
的型別定義是 string
或 string[]
:
interface ParsedUrlQuery extends NodeJS.Dict<string | string[]> {}
現在我們可以知道 params
的型別有可能會是 string
、 string[]
或是 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 這項功能。
在 Next.js 中除了自己撰寫 getStaticProps
或 getServerSideProps
決定一個頁面是 SSG 或 SSR 之外,它可以自動化分析一個頁面是不是有依賴一些 API 的資料回傳,如果沒有的話,這個頁面在不用加上 getStaticProps
或 getServerSideProps
的情況下,就可以獲得 pre-rendering 的好處。
以下是一個一簡頁面,雖然沒有寫 getStaticProps
或 getServerSideProps
這兩個 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
要觸發這個自動優化的功能,頁面不能加上 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 撰寫頁面。
感謝作者的分享。
有小地方 typo getSererSideProps
應為 getServerSideProps
。