optimize next js bundle size and performance

# javascript

最近將這篇 zettelkasten card and blog static website generator 提到自己寫的部落格產生工具,做了一些 UI 優化還有 bugs 修正, 偶爾信箱還會收到一些不認識的人說要幫我提到 SEO 排名,想也知道是要收費的,不過我想想我這個只是個人網站似乎不太需要特別把SEO用到多好,但是我還是稍微做了一下功課優化,知道爲什麼嗎~

因爲萬惡的 google search console 對於他會不會把你的網站納入google search裡面的結果,裡面衆多可能的條件之一就是你的頁面 SEO 如果評分不夠好,是很有可能被拿掉,但是我覺得其實有其他佔比很重因素,只要它認爲你的內容沒價值(一個完全不知道標準爲何),它才不管你的網站優化多好,SEO做得多好,照樣拿掉 lol 不過想了想,不管如何,我多少還是做一下比較好,因爲有看過有人分享一些頁面被拿掉後,就再也加不回去,不管他做了多少修正,儘量減少被拿掉的因素!

當前網站評分

其實我之前有對於 image 的部分做了點優化,可以看看這篇 next image export optimizer with blur image in data url format, 圖片對於SEO的分數佔了不小的部分,因爲圖片通常是佔據網路下載用量的大部分~ 下面是目前用 pagespeed insights做的測試評分

其實就桌面環境來講,好像也沒有太多地方需要優化 lol 有不少地方其實是 next js 這個 framework 就有做不少地方的優化,可以看看官網,除了圖片之外接著就是一些bundle javascript的library可以看看怎麼優化大小了。

但是老話一句,我覺得這種優化大小,也都會隨著網路速度繼續進步的時代慢慢不重要,比如每秒10MB的速度,你是有多少 static assets 要下載? 正常有這種速度,頁面一進入老早開始進行 render 動作了,不會在卡在loading資源的階段了,不過現在高速網路還沒那麼普及,我們還是得來處理一下 bundle size。

優化 bundle size

https://nextjs.org/docs/app/building-your-application/optimizing/package-bundling#analyzing-javascript-bundles 官網有介紹怎麼使用,安裝並執行一下,應該會看到 terminal 類似下面這樣的針對各個route分析


Route (app)                                                   Size     First Load JS
┌ ○ /                                                         3.31 kB         119 kB
├ ○ /_not-found                                               871 B          88.1 kB
├ ○ /about                                                    181 B           100 kB
├ ○ /blog                                                     48.1 kB         163 kB
├ ● /blog/[slug]                                              1.26 kB         108 kB
├   ├ /blog/books_read_in_2024
├   ├ /blog/custom_sender_name_with_gmail_smtp_in_nodemailer
├   ├ /blog/coding_with_aider_caht
├   └ [+109 more paths]
├ ○ /blog/sitemap.xml                                         0 B                0 B
├ ○ /cv                                                       137 B          87.4 kB
├ ○ /icon.png                                                 0 B                0 B
└ ○ /sitemap.xml                                              0 B                0 B
+ First Load JS shared by all                                 87.2 kB
  ├ chunks/23-600be35fb9cf100a.js                             31.6 kB
  ├ chunks/fd9d1056-c4050f5b61ad0a3b.js                       53.6 kB
  └ other shared chunks (total)                               1.96 kB


○  (Static)  prerendered as static content
●  (SSG)     prerendered as static HTML (uses getStaticProps)

我們主要是要儘量降低 first load JS, 因爲這個一開始進入 route 就要先 load 好的,才會完整頁面渲染

另外其實瀏覽器也會自動打開分析圖,以我的例子我只需要看 client 端就好,如下圖

他可以搜尋 bundle 的 js 裡面用到的 library, 這裡我就發現我的 blog notfound page 多重複引用了 canva lite

其他我看到還有D3 library佔了不小,要怎麼優化呢?

思路

一個最極端的做法,如果今天你用到 D3 的地方不多,你可以考慮用 pure js 自己搞,這絕對是最大減少size的方法,或者找其他可以做到類似效用比較輕量的library,通常我會用這個網站 https://bundlephobia.com/ 來比較bundle後的大小。

今天我的部落格的確不太需要D3,我只是拿來做 blog 展示 zettelkasten 的節點渲染,像下圖

所以我有沒有在一開始就loading完D3,並不重要,我只要點擊打開再loading就夠了,但是我又懶得找其他的library,還有什麼做法呢?

你可能會想難道沒有 lazy loading 的方法嗎? 這時候 next js 的 dynamic import 就會說 「我這不是來了嗎」

爲了要減少 D3 在 first load JS 的 size,這邊特別使用了 next js 的 dynamic import,不過一開始使用都發現怎樣減少不了 first load js 的 size,加上看到有些人文章說也是一樣狀況,讓我不禁懷疑,這~真的有用嗎? 突然有個謎之聲 import 一次不行,就 import 第二次啊~ 我趕快甩甩頭,繼續找原因,後來發現不少人反應這個問題! 看看這個 issue ,算是容易誤踩的地雷? https://github.com/vercel/next.js/issues/49454

簡單說其實正確的用法是(這邊不得不說官方文檔寫得。。。 沒容易讓人搞懂用法),裡面討論串最後有人講到解法是

The quick way to make code splitting work is to use next/dynamic in client components.

也就是說你的 dynamic import 不應該在 server component 去 call 這個,而是另外寫一個 client component 去 dynamic import 你想要的 target component。 雖然有點繞就是了,我一直以爲 server component會自己看到 dynamic import 後,就會自己 split code 咧~

我這樣額外包裝後

某個 client side component

const DynamicGraphVisualization = dynamic(() => import('@/components/graph'), {
    loading: () => <p>Loading...</p>,
    ssr: false,
})


export const LazyTechExpansion = ({
    blinks,
}: GraphVisualizationProps) => {
    return (
        <TechExpansion
            ContentComponent={
                <DynamicGraphVisualization
                    blinks={blinks}
                />
            }
        />
    )
}

然後 server side component 再引用 LazyTechExpansion,確實size有變少,可以看看 /blog 這個 route


Route (app)                                                   Size     First Load JS
┌ ○ /                                                         3.75 kB         119 kB
├ ○ /_not-found                                               871 B          88.2 kB
├ ○ /about                                                    182 B           101 kB
├ ○ /blog                                                     22.9 kB         138 kB
├ ● /blog/[slug]                                              1.26 kB         109 kB
├   ├ /blog/books_read_in_2024
├   ├ /blog/custom_sender_name_with_gmail_smtp_in_nodemailer
├   ├ /blog/coding_with_aider_caht
├   └ [+109 more paths]
├ ○ /blog/sitemap.xml                                         0 B                0 B
├ ○ /cv                                                       137 B          87.4 kB
├ ○ /icon.png                                                 0 B                0 B
└ ○ /sitemap.xml                                              0 B                0 B
+ First Load JS shared by all                                 87.3 kB
  ├ chunks/23-600be35fb9cf100a.js                             31.6 kB
  ├ chunks/fd9d1056-c4050f5b61ad0a3b.js                       53.6 kB
  └ other shared chunks (total)                               2.02 kB


○  (Static)  prerendered as static content
●  (SSG)     prerendered as static HTML (uses getStaticProps)

其實以我 bundle size 並不算大,因爲絕大部分都是因爲 NextJS + React 太肥~ 所以這邊我就不追求極端小的size了。

下面講講一些 next js 特別的東西

https://nextjs.org/docs/pages/api-reference/components/link#prefetch 預設next js的 link 是會進行 prefetch 的,也就是說一旦link進入畫面(viewport),就會先去背後loading好資料,這個我覺得需要注意一下,如果你有很多link跟資料也不小,這有可能拖慢你的頁面顯示,所以看你的狀況可以設定成 false

https://nextjs.org/docs/app/api-reference/components/link#prefetch 不過 nextjs 有些恐怖地方是,根據你專案用 app route 還是 page route 行爲會有不一樣。。 就以這個 link prefech 設定成 false 一個是在 hover 後還是會 load 資料,另外一個是不會~ 其實還有很多地方~ 只能說官方文檔用力看 lol

next js 還有很多東西,只是目前用不太到,以後有用到再額外來寫一篇。

下一步

經過一些優化後,清理一些技術債後,我想差不多可以繼續下一步幫我的部落格產生器加一下新玩意了~ 當前應該還是會先加強一些頁面UI,還有CV頁面一直沒想到怎呈現比較好

另外之後預計是撰寫預覽體驗,雖然也可能不是那麼必要啦,目前很大部分都是直接在emacs直接寫完文章然後才build看成效的,對我來講算是很夠用了,不太需要即時預覽,反而是我有時候 突然想到如果要在手機端進行寫作的話呢? 似乎就不是那麼方便了,但是這個工程可能也會很大,姑且只是先想一下而已 :)

If you like my content,

feel free to buy me a coffee

Enjoy crafting new things

Never stop learning.

Life is the sum of your daily habits.

Find things that you enjoy and please

Doit.

Feel free to connect with me.

Created by potrace 1.16, written by Peter Selinger 2001-2019

© Jing 2024. All rights reserved.