NextJS 首屏加载优化
迭代 Kami 已经很长时间。中间也经过几次大的重构,但一直没有对首屏优化过。首屏加载速度很大程度被打包产物的体积所影响。在 Google Lighthouse 的 Performance 中也有一项建议是 Remove unused JavaScript。
在前段日子我发现 NextJS 虽然会进行 Tree Shake,但并不会细化到每个 Page 粒度的拆分多个文件,比如说你的项目总使用了 lodash-es 会进行 Tree Shake,但是他只是移除了你整个项目中未用到的方法,比如在 Page A 使用了 omit 方法,在 Page B 使用了 throttle 方法,由于都是使用 import {} from 'lodash-es'
导入,导致在 Page A 加载时也会去加载 Page B 的 throttle 方法。
对于首屏来说,这是一个极大的影响,如果能精确细化成一个个文件,首次载入的体积将大大减少。在优化之前,可以看到 /_app
非常的大。300K 出头的基础包_app.js
。入口 /
311K。
info - Collecting page data
Page Size First Load JS
┌ λ / 5.17 kB 311 kB
├ └ css/653a178efa4724c9.css 950 B
├ /_app 0 B 304 kB
├ λ /[page] 1.06 kB 329 kB
├ └ css/0675faafb467c2b9.css 1.66 kB
├ λ /404 692 B 305 kB
├ λ /api/bilibili/bangumi 0 B 304 kB
├ λ /api/bilibili/cover 0 B 304 kB
├ λ /api/bilibili/video 0 B 304 kB
├ λ /api/feed 0 B 304 kB
├ λ /api/netease/music 0 B 304 kB
├ λ /api/netease/song 0 B 304 kB
├ λ /api/sitemap 0 B 304 kB
├ λ /category/[slug] 7.89 kB 316 kB
├ λ /dev/zoom 365 B 304 kB
├ λ /favorite/bangumi 1.87 kB 310 kB
├ λ /favorite/music 2.14 kB 310 kB
├ └ css/faba63f7732fd70f.css 573 B
├ λ /friends 9.94 kB 326 kB
├ └ css/28a312a38641b4e6.css 489 B
├ λ /login 2 kB 306 kB
├ └ css/8cba0370ecaeebe2.css 521 B
├ λ /notes/[id] 5.47 kB 333 kB
├ └ css/d8e0a9b1015a3d50.css 1.93 kB
├ λ /posts 6.94 kB 315 kB
├ └ css/e7a1dce72d581d85.css 755 B
├ λ /posts/[category]/[slug] 3.76 kB 332 kB
├ └ css/460cb4c7f44c400f.css 1.74 kB
├ λ /projects 1.99 kB 310 kB
├ λ /projects/[id] 2.29 kB 310 kB
├ λ /recently 3.46 kB 307 kB
├ └ css/36c2d720f9b8ac99.css 636 B
├ λ /says 18.4 kB 326 kB
├ └ css/3d0401d6d411fe80.css 369 B
└ λ /timeline 8.57 kB 316 kB
└ css/711ec0198f2688fe.css 395 B
+ First Load JS shared by all 304 kB
├ chunks/framework-774fc966c0f2820d.js 42.2 kB
├ chunks/main-4dbd8084f1abd7f8.js 32.6 kB
├ chunks/pages/_app-e73bc29159e27c50.js 227 kB
├ chunks/webpack-a3fd8ab904e44f8c.js 1.77 kB
└ css/b85e767cb9b1e73f.css 18.4 kB
由于 NextJS 12 目前并不支持 Layout(Layout 正在 RFC 中有望近期与我们见面),所以 _app.tsx
一般都是布局相关的东西。布局涉及到大量导入,组件和外部库。首先还是需要把 import 细化。比如修改 import { merge } from 'lodash-es'
到 import merge from 'lodash-es/merge'
,细化 utils 导入,import { someFunction } from '~/utils'
到 `import { someFunction } from '~/utils/someFunction'
。这是一件非常枯燥的事情。
第二,使用组件懒加载,在 React 18 和 NextJS 12 中,可以使用 React.lazy 和 Suspense 包装。在原先 BasicLayout 中,所有导入都是同步的,在加载这个布局是就需要同时去渲染大量额外的组件,可能这些不是 Content 的重要内容,如 Header Footer Sider 都是可以异步延迟去渲染的。
然后 Lazy 的组件用 React 18 新特征 Suspense 进行包装。只留重要的 Content 部分。细化之后,React 会注重渲染 Content 部分,再是周边组件。遇上弱网,可以很明显的对比效果。
再者,移除 NextJS 提供的对老旧浏览器的垫片。在 next.config.js
添加。
experimental: {
legacyBrowsers: false,
browsersListForSwc: true,
},
通过以上的优化,_app.js
已经很大程度减少了体积。#1245
继续对主页面,进入主站的第一站也就是 pages/index.tsx
重复上面细化 import 的步骤进行优化。#1292
经过优化,在原先的全局体积 300K 优化到了基础包 171K。比原先将近少了一半。
通过 Google Lighthouse Perfermance 跑分。首屏高达 93 分。