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


<<:  【设计+切版30天实作】|Day10 - 因应Bootstrap的元件去弹性设计Reviews区块

>>:  咏唱防御魔法,抵御外敌攻击AWS上的服务

铁人赛 Day11-- PHP SQL基本语法(六) -- INSERT 基本语法

INSERT 基本语法 INSERT INTO '资料表名称'('栏位名称1','栏位名称2',.....

如何衡量万事万物 (2) 衡量的客体 & 方法

作者认为,认为一件事物无法量测,理由有三个面向: 衡量的观念:按照昨天的摘要,最大的误会是人们常常觉...

[Day 23] 究竟AI能不能预测股价?

一、究竟AI能不能预测股价? 不能 好了,被我骗进来的可以按上一页了(X 结论已经讲了,如果你对原因...

Day29-台湾菜鸟工程师除错之卷四

不过让我印象最深刻的面试就是yahoo所委托的专案 那时候委托者是需要做出一个yahoo news...

Day 27. B2E-密码加密

还记得第2天在做专案规划时,有提到一个目标「加密敏感资料实现资安管理」吗? 目前我们的密码还是一样...