Day10 - 为什麽官方不推荐使用 getInitialProps

getInitialProps 是较为老旧的 API

Next.js 9.3 版本後,官方释出了两个新的 API 分别为 getStaticPropsgetServerSideProps ,这两个 API 可以被使用在 SSG 与 SSR 两种不同的策略上,如果不熟 SSG 与 SSR 可以参考之前写过的文章

官方文件中也写道,如果使用的是 Next.js 9.3 版本之後,推荐使用上述的两个 API,而不要使用 getInitialProps

今天,我们要来了解,为什麽官方不推荐使用 getInitialProps ,使用 getInitialProps 会早成什麽问题。

第一个问题:失去 Automatic Static Optimization

在 Next.js 9.0 版本後释出了 Automatic Static Optimization 这个功能,这项功能对於工程师来说是一大福音,如同这项功能的名字,在不加上 getServerSidePropsgetInitialProps 的 component 中,Next.js 会自动分析该 component 成为 SSG 的一员,也就是说 component 可以走 SSG 生成静态的 HTML 档案。

但是,如果加上了 getInitialProps 就会失去 SSG 的优势。

第二个问题:在 _app.js 使用 getInitialProps 将会使整个专案失去 Automatic Static Optimization

在一般的情况下 getInitialProps 可以帮我们得到 SSR 的各种好处,让服务器可以事先执行呼叫 API 获得资料的流程,然後回传已经渲染完资料的 HTML 给用户端。

但是有一个情况要注意的是在 _app.js 中可以设定 getInitialProps 为全域的 component 获得 SSR 的好处,但同时也会让整个专案失去 Automatic Static Optimization 的功能,如果这不是期望的行为,则不要在 _app.js 中使用 getInitialProps

第三个问题: getInitialProps 可能会造成更大的 bundle size

getInitialPropsgetServerSideProps 乍看之下很相似,都是让一个页面拥有 SSR 的功能,而它们不一样的地方在於 getInitialProps 会把程序码带到用户端,在服务器端与用户端都会执行 getInitialProps 的程序码;而 getServerSideProps 仅会在服务器端执行,因此在打包时也会让 bundle size 更小一些。

在官方文件中也特别提到一段使用 getInitialProps 要注意的事项:「If you are using server-side only modules inside getInitialProps, make sure to import them properly, otherwise it'll slow down your app」,看起来有点妙,究竟是什麽样的问题会让 Next.js 的速度变慢。

getInitialProps 可能会造成更大的 bundle size

以上是在「SSR and Server Only Modules」这篇文章中引用的一张图,这篇 twitter 贴文提到 50 行的 JS 程序码却造成 1.2MB 的 bundle size,听起来超级不合理。

问题出在哪里?

我们要知道的事情是 getInitialProps 会在用户端跟服务器端执行,在 next build 时会把 getInitialProps 中的程序码也会一起被打包进去。

所以尽管在 getInitialProps 中撰写 if-else 判断有些只在服务器端执行的程序码,但是最後仍然会打包近 bundle 中。

接着,我们来看一个范例简单的范例,在一个新建立的专案中修改 pages/index.tsx 的程序码。

以下的 getInitialPropsif (req) 判断要在服务器端还是在用户端执行,我们预期在服务器端才会用到 faker 这个 module,所以在 if 里面才用 require 的方式引入这个 module,尽管 faker 只会在服务器端用到,但实际上 faker 仍然会被打包到 bundle 中。

import { NextPageContext } from "next";
import Link from "next/link";

interface HomeProps {
  name: string;
}

export default function Home({ name }: HomeProps) {
  return (
    <div>
      <h1>Home Page</h1>
      <p>Welcome, {name}</p>
      <div>
        <Link href="/about">
          <a>About Page</a>
        </Link>
      </div>
    </div>
  );
}

Home.getInitialProps = async ({ req }: NextPageContext) => {
  if (req) {
    // 只在服务器端执行
    const faker = require("faker");
    const name = faker.name.findName();
    return { name };
  }

  // 只在用户端执行
  return { name: "Lawrence" };
};

使用 webpack-bundle-analyzer 确认问题真的存在

next build 时我们可以看到打包後的 bundle size,很奇怪的是 / 这个页面对应的是 pages/index.tsx 也就上方的范例程序,虽然只有 30 行左右,但是档案大小却高达 541KB,让人怀疑 next build 是不是没有写好。

next bundle

pages/index.tsx 比较奇怪的点是 require('faker') 这段程序码,但是口说无凭,我们用 webpack bundle analyzer 确认这个问题发生在哪里。

参考官方 GitHub 范例 analyze-bundles

从以下的图中可以知道尽管 faker 只在服务器端执行,但是最後仍然会被 webpack 打包,这样的情况不是我们想要的。

webpack-bundle-analyzer

此外,再从 Chrome 的 Network 中看到,只有几个字的页面,却需要 1.1MB 的网路传输量。

Chrome Network

因此可以得知,在使用 getInitialProps 的同时,还必须注意使用 require 的方式引入 module 会导致 bundle size 变大,让网页的载入速度变慢,进而让使用者体验不好。

总结

在这篇文章中,我们了解使用 getInitialProps 可能会遇到的三个问题,包含会失去 Automatic Static Optimization 的优势,以及可能会让 bundle size 变大的问题。

但是如果想要让全域的 component 共用 SSR 的程序码,那麽唯一的方法是使用较为古老的 getInitialProps ,因为目前在 _app.js 中不支援 getStaticPropsgetServerSIdeProps ,但是如果用了 getInitialProps 会有一个很大的缺点会让整个专案失去 Automatic Static Optimization,所以现阶段就看怎麽取舍了。

Reference


<<:  【心得】我就是要跟别人不一样!!List 清单样式变变变~

>>:  [Day11]什麽是智慧合约?

【D8】制作图表:三大法人-区分期货与选择权二类

前言 有了资料後,就要进行分析,因此需要做出图表比较适合观察,所以我们现在来做图罗! 本日程序码使用...

[Day12] Trait 与 STD 库中的 fs

我开始怀疑观看人数了,我觉得我猜得好不准ㄛ。我觉得会比较少人看得竟然会蛮多人看得@@ 太扯了太扯 我...

Day.14 Hash map II

今天要把上一篇讲的hash map写成code! struct type Node struct {...

我们的基因体时代-AI, Data和生物资讯 Day03- 基因医学的数据问题

上一篇我们的基因体时代-AI, Data和生物资讯 Day02- 机器学习在生物资讯中之应用介绍了机...