Next.js 9.3 版本後,官方釋出了兩個新的 API 分別為 getStaticProps
與 getServerSideProps
,這兩個 API 可以被使用在 SSG 與 SSR 兩種不同的策略上,如果不熟 SSG 與 SSR 可以參考之前寫過的文章。
官方文件中也寫道,如果使用的是 Next.js 9.3 版本之後,推薦使用上述的兩個 API,而不要使用 getInitialProps
。
今天,我們要來了解,為什麼官方不推薦使用 getInitialProps
,使用 getInitialProps
會早成什麼問題。
在 Next.js 9.0 版本後釋出了 Automatic Static Optimization 這個功能,這項功能對於工程師來說是一大福音,如同這項功能的名字,在不加上 getServerSideProps
或 getInitialProps
的 component 中,Next.js 會自動分析該 component 成為 SSG 的一員,也就是說 component 可以走 SSG 生成靜態的 HTML 檔案。
但是,如果加上了 getInitialProps
就會失去 SSG 的優勢。
getInitialProps
將會使整個專案失去 Automatic Static Optimization在一般的情況下 getInitialProps
可以幫我們得到 SSR 的各種好處,讓伺服器可以事先執行呼叫 API 獲得資料的流程,然後回傳已經渲染完資料的 HTML 給用戶端。
但是有一個情況要注意的是在 _app.js
中可以設定 getInitialProps
為全域的 component 獲得 SSR 的好處,但同時也會讓整個專案失去 Automatic Static Optimization 的功能,如果這不是期望的行為,則不要在 _app.js
中使用 getInitialProps
。
getInitialProps
可能會造成更大的 bundle sizegetInitialProps
與 getServerSideProps
乍看之下很相似,都是讓一個頁面擁有 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 的速度變慢。
以上是在「SSR and Server Only Modules」這篇文章中引用的一張圖,這篇 twitter 貼文提到 50 行的 JS 程式碼卻造成 1.2MB 的 bundle size,聽起來超級不合理。
我們要知道的事情是 getInitialProps
會在用戶端跟伺服器端執行,在 next build
時會把 getInitialProps
中的程式碼也會一起被打包進去。
所以儘管在 getInitialProps
中撰寫 if-else
判斷有些只在伺服器端執行的程式碼,但是最後仍然會打包近 bundle 中。
接著,我們來看一個範例簡單的範例,在一個新建立的專案中修改 pages/index.tsx
的程式碼。
以下的 getInitialProps
用 if (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" };
};
在 next build
時我們可以看到打包後的 bundle size,很奇怪的是 /
這個頁面對應的是 pages/index.tsx
也就上方的範例程式,雖然只有 30 行左右,但是檔案大小卻高達 541KB,讓人懷疑 next build
是不是沒有寫好。
在 pages/index.tsx
比較奇怪的點是 require('faker')
這段程式碼,但是口說無憑,我們用 webpack bundle analyzer 確認這個問題發生在哪裡。
參考官方 GitHub 範例 analyze-bundles
從以下的圖中可以知道儘管 faker
只在伺服器端執行,但是最後仍然會被 webpack 打包,這樣的情況不是我們想要的。
此外,再從 Chrome 的 Network 中看到,只有幾個字的頁面,卻需要 1.1MB 的網路傳輸量。
因此可以得知,在使用 getInitialProps
的同時,還必須注意使用 require
的方式引入 module 會導致 bundle size 變大,讓網頁的載入速度變慢,進而讓使用者體驗不好。
在這篇文章中,我們了解使用 getInitialProps
可能會遇到的三個問題,包含會失去 Automatic Static Optimization 的優勢,以及可能會讓 bundle size 變大的問題。
但是如果想要讓全域的 component 共用 SSR 的程式碼,那麼唯一的方法是使用較為古老的 getInitialProps
,因為目前在 _app.js
中不支援 getStaticProps
與 getServerSIdeProps
,但是如果用了 getInitialProps
會有一個很大的缺點會讓整個專案失去 Automatic Static Optimization,所以現階段就看怎麼取捨了。