昨天看過圖片的優化方式,今天接著再來看看載入 JavaScript 的優化方式。在正式來看為什麼需要額外做一些動作來優化 JavaScript 的載入之前,我們先來看一個實際例子。
我們在這一個畫面中,可以透過一個按鈕顯示出一個圖表內容。這樣的畫面看起來沒有什麼問題,但是當畫面內的內容變多,影響到需要載入的 JavaScript 的檔案大小,就有可能會出現畫面載入太慢的狀況。
我們先把原本的畫面改成一個比較單純,只有文字,但沒圖表的頁面,觀察一下 build 完後,會在 client 端被下載來使用的 JavaScript 大小。
這裡可以看到首頁的路由在瀏覽器上會下載的 JavaScript 檔案 Size 只有 650 B,首次進入頁面需要載下的 JavaScript 大小是 102 KB。
我們再來看看把 Chart 放回去之後,build 後的檔案大小會有什麼樣的變化。
當我們把 Chart 放回畫面後,可以發現不管是專屬於首頁的 JavaScript 檔案,還是首次進入頁面需要下載的 JavaScript 大小就變大了,甚至變成了原本的兩倍。
這時候有人可能會有一個疑問,那就是「畫面上要顯示的內容變多了,JavaScript 大小當然會變多啊!不是很正常嗎?」。
沒錯!這不是一個什麼很神奇或是很異常的現象,但是大家可以仔細思考一下,剛剛我們先拿掉的 Chart 內容,是一進入畫面就需要顯示的內容嗎?如果不是一進入頁面就需要使用,或是需要看到的內容,是不是可以不要在一開始進入畫面就載入相關的 JavaScript 呢?當專案內容越來越龐大,如果可以把需要載入的一大坨 JavaScript,像我們把畫面拆小一樣,拆成一小包一小包的檔案,按需載入的話,是不是就不需要在一開始進入畫面時,一次載入一大包 JavaScript 了?
next/dynamic 就是 Next.js 提供來處理這個情境的方式。
前面的情境已經看到痛點,也就是「不是所有功能都需要在首屏就下載與執行,但是卻會影響到需載入的 JavaScript 檔案大小」。而 next/dynamic 存在的目的也就是把 「首屏非必要」的元件拆成獨立的 chunk,並且延後載入,已達成更強化的 code splitting。
雖然 Next.js 在 build 時會自動做以路由為單位的拆分(每個頁面有自己的 bundle,另含共用的 chunks),但實務上只以路由為單位,在很多時候顆粒度還是不夠小。這時就可以使用 next/dynamic 把顆粒度降到「元件級」,讓大型或只在互動後才需要的元件,在要用到時才下載,從而降低首屏 JavaScript 的大小、縮短可互動時間(TTI)。
接著來看一下 next/dynamic 的使用方式。
我們延續最前面提到的那個例子,來看要怎麼透過 next/dynamic 來達到 code splitting 的效果。
原本的 ChartCard 元件的內容是以下這樣。
"use client";
import { useState } from "react";
import Chart from "./Chart";
const ChartCard = ({ title }: { title: string }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="flex flex-col">
<p className="text-3xl bold">{title}</p>
<div className="flex flex-col mt-4">
<p>some information.....</p>
<button
className="mt-3 rounded-lg px-4 py-2 bg-black text-white hover:bg-black/80 transition"
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen}
>
{isOpen ? "Hide Chart" : "Show Chart"}
</button>
</div>
<div
className={`mt-6 w-full flex justify-center overflow-hidden transition-all duration-500 ease-out ${
isOpen
? "opacity-100 scale-y-100 max-h-[1200px]"
: "opacity-0 scale-y-95 max-h-0"
}`}
style={{ transformOrigin: "top" }}
>
{isOpen && (
<div className="rounded-xl p-2 shadow-lg">
<Chart />
</div>
)}
</div>
</div>
);
};
export default ChartCard;
想要使用 next/dynamic 的話,可以把引入 Chart 的部分,改成 dynamic 的寫法。
"use client";
import { useState } from "react";
// import dynamic
import dynamic from "next/dynamic";
// 不直接引入 Chart 元件,改用 dynamic 引入 Chart 元件
const Chart = dynamic(
() => import("./Chart")
);
const ChartCard = ({ title }: { title: string }) => {
const [isOpen, setIsOpen] = useState(false);
// 略 ...
};
export default ChartCard;
實際看改寫後的結果!
調整過後可以看到,首次進入頁面需要加載的 JavaScript 真的有大幅減少,從 262KB 減少至 103KB。
不過除了這個基本的用法外,還可以在使用 next/dynamic 的時候,加上 { ssr: false }
這個設定。
如果有一個元件不僅是不需要再一進入頁面的時候就載入,而且這個元件裡面還有使用到一些只有在 client 端才可以使用的內容,例如 window、document 等瀏覽器專屬的 API,或者是一個使用檔案較大的第三方套件的元件,就可以加上 { ssr: false }
,用來跳過在伺服器端就先產生這個元件的內容。但是設定 { ssr: false }
的時候,因為在伺服器端上進行畫面渲染時,只會將對應區塊標示這裡會插入一個內容,例如以下這個截圖,實際上的內容會在瀏覽器上才渲染出來,
所以如果是需要立即顯示,不是進行操作才會出現在畫面上的內容,會建議加上 loading 來佔位,以避免畫面突然變高,造成版面有位移的狀況(也就 CLS 的狀況)。
我們先把畫面都改成預設 Chart 都打開顯示,並加上 { ssr: false }
,先不加上 loading。
const Chart = dynamic(() => import("./Chart"), {
ssr: false,
});
可以看到,在畫面中,有一段時間 Chart 沒有顯示出來,因為在伺服器上沒有先處理 Chart 這部分的話,等到在瀏覽器上,Chart 載入完成後,才顯示在畫面上,造成畫面有位移的狀況。
但是當我們把 loading 加上後,就可以佔一個位置。
const Chart = dynamic(() => import("./Chart"), {
ssr: false,
loading: () => <div className="h-[400px] w-[600px] rounded bg-gray-100" />,
});
最後呈現的狀況就是在 Chart 還沒顯示出來之前,先顯示一個灰底的區塊,把 Chart 的位置佔住,等到 Chart 顯示出來,就不會有位移的狀況出現了。
看到這裡應該有不少人腦海中會浮現另一個很類似的用法,那就是 React.lazy。其實 React.lazy 的使用目的和 next/dynamic 幾乎是一樣的,它們都是為了進行 code splitting,以及將不需要馬上就載入的內容延遲載入的方式。在使用上,由於 next/dynamic 不需要額外使用 Suspense,所以 next/dynamic 又被視為是 React.lazy 和 Suspense 的複合用法。其中比較不一樣的地方是 next/dynamic 增加了可以略過 SSR 模式渲染的設定,React.lazy 則沒有支援這個用法。
當專案逐漸龐大時,JavaScript 的載入大小會直接影響頁面首屏的速度與使用者體驗。雖然 Next.js 會以路由為單位自動做 code splitting,但這樣的顆粒度還是不夠小,這時候就可以使用 next/dynamic 來以元件為單位進行 code splitting。
使用 next/dynamic 時,能夠將非首屏必要的功能延後載入,降低首屏 JavaScript 的大小,加快進入頁面的速度。透過 { ssr: false }
,還能針對只在瀏覽器端可執行的元件(例如使用 window、document 或是大型第三方套件)完全跳過 SSR,並搭配 loading 屬性避免畫面位移(CLS)。與 React.lazy 相比,next/dynamic 不僅省略了額外的 Suspense 包裝,還提供略過 SSR 的能力,更適合在 Next.js 專案中使用。
next/dynamic 是 Next.js 優化 JavaScript 載入的重要工具,能讓開發者有效降低首屏負擔,並改善整體的載入體驗。
今天優化 JavaScript 的方法 next/dynamic 就看到這裡告一個段落。透過 next/dynamic 進行 code Splitting 與效能優化後,不僅能改善使用者體驗,也能因為效能指標的提升而間接對 SEO 帶來幫助。
不過想要把 SEO 做好,並不是把所有直接間接有幫助的方法都使用上去就有最佳效果,關於這部分的細節,就讓我們明天再接著好好了解一下吧,我們明天見~