iT邦幫忙

2021 iThome 鐵人賽

DAY 10
1
Modern Web

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

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


上一篇
Day09 - 在 Next.js 中使用 pre-rendering (getServerSideProps)
下一篇
Day11 - 在 Next.js 中使用 CSR - feat. useSWR
系列文
從零開始學習 Next.js30

尚未有邦友留言

立即登入留言