NextJS 如何有效地减少 First Load JS 的体积大小
先说结果:共减少了
580.8 kb
的 First Load JS 大小!
写前端的时候,功能只写了一点,原以为体积很小,部署后发现进入的时间越来越慢,我开始了深深的思考...
最简单的就是使用 Google LightHouse 进行检查,分析告诉我,我加载的 JS 文件太大了,我只好返回去看看编译的时候返回的大小情况
当我运行 next build
的时候,它却告诉我一个十分悲惨的事实:
Route (pages) Size First Load JS
┌ λ / 706 B 111 kB
├ /_app 0 B 108 kB
├ λ /[pages] 967 B 691 kB ⚠️
├ λ /404 188 B 108 kB
├ λ /category/[slug] 1.05 kB 133 kB
├ λ /links 2.12 kB 559 kB ⚠️
├ └ css/5daf79f472e2d72a.css 1.76 kB
├ λ /posts 1.08 kB 133 kB
├ λ /posts/[category]/[slug] 1.13 kB 691 kB ⚠️
└ λ /tag/[name] 1.01 kB 133 kB
+ First Load JS shared by all 119 kB
├ chunks/framework-8792ff04d9bb21fc.js 45.7 kB
├ chunks/main-465434c0b66bfe41.js 31.1 kB
├ chunks/pages/_app-8f9e59e58813e545.js 29.8 kB
├ chunks/webpack-06f027d2973c0918.js 1.01 kB
└ css/82c644df5d8e11c0.css 11.7 kB
/[pages]
居然达到了惊人的 700 kb。 这都接近 1M 了,再加上我的服务器在海外,加载速度能不慢吗?
使用 Analyze 分析问题所在
为了发现问题所在,我只能安装 @next/analyze
去分析到底是哪些文件这么丧心病狂
// in cmd
pnpm i -D @next/bundle-analyzer
// in next.config.js
if (process.env.ANALYZE === 'true') {
plugins.push([require('@next/bundle-analyzer')({ enabled: true })])
}
接着运行起 ANALYZE=true next build
,等一会儿就能在浏览器看到具体情况了
将鼠标放置上去即可看到这个文件的 Size,首先先优化一下 /_app:
移除/合并多余的组件
在 _app.tsx
中我的 Layout 是这么写的:
import Header from '../components/layouts/Header'
import Footer from '../components/layouts/Footer'
import MainLayout from '../components/layouts'
function App({ initialData, Component, pageProps }) {
return (
<>
<Header />
<main>
<div className="wrap min">
<Component {...pageProps} />
</div>
</main>
<Footer />
</>
)
}
很明显,写在 App 中的代码非常多余,Header
, Footer
这些根本就没有需要 Props 的就不应该出现在这个地方
我主要变化的组件只有 <Component />
。应该将那些组件放到一个文件里面实现。
除了这个还有一种情况: 使用了第三方的 layout 组件
换句话说,就是建议你自己实现它,而不要使用第三方组件,第三方组件为了兼容各方,会写入许多你获取用不上的内容(确信)
In AppLayout.tsx
export const AppLayout: FC<React.PropsWithChildren> = ({ children }) => {
// ...
return (
<>
<Header />
<main>
<div className="wrap min">
{children}
</div>
</main>
<Footer />
</>
)
}
// const Header: FC....
// ...
In _app.tsx
import AppLayout from '../components/layouts/AppLayout'
function App({ initialData, Component, pageProps }) {
//...
return (
<AppLayout>
<Component {...pageProps} />
</AppLayout>
)
}
动态导入组件
动态导入使用的是 nextjs 中自带的模块,并不需要安装,在文件里面引入即可。我那500多KB的减少,就是靠的这个模块. 使用 dynamic
导入组件不会包含在页面的 First Load JS 中
毅然选择动态导入你认为会有影响的组件
前天刚给前端添加了一个 Commander 的 Feature,使用的是 cmdk 项目,我自己心知肚明这个是导致 First Load JS 偏大的一个原因,由于考虑到用户一进来并不会立即使用到这个 Commander,于是我打算将它使用 dynamic
动态导入.
// layouts/AppLayout.tsx
// 因为他并不需要 Props,也没有必要展现在 _app 里面,所以也是写在 AppLayout 里的
const Commander = dynamic(() => import("../../widgets/Commander"), {
ssr: false
})
const AppLayout:.... ({children}) => {
return (
<>
<... />
<Commander />
</>
)
}
就只是换了一种写法,将 import Commander from '...'
变成了 dynamic(() => import("..."))
,调用它还是一样的。那这里就简单说一下dynamic的第二个参数:
suspense
: 与<Suspense fallback={``} />
同用,页面会先渲染 Suspensefallback
,import 的组件会被解析后才进行渲染ssr
: 如果 false 就会在客户端动态加载这个组件,如果组件依赖于浏览器 API (window
),那你应该把这个设置为false
官方的解释在这里: https://nextjs.org/docs/advanced-features/dynamic-import
最后在build阶段返回的 First Load JS 大小果然有所改变:108 kB --> 95.5 kB
由于文章与页面的代码相差并不多,那就直接把目光聚集到 [pages]/index.tsx 中,首先要找到到底是什么导致了 First Load JS 这么大的。你问我方法...真就排除法呗 😂
排除后动态导入有可能会影响的组件
import { GetServerSideProps, NextPage } from "next";
import Head from "next/head";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useSnapshot } from "valtio";
import Markdown from "../../components/Markdown";
import SEO from "../../components/others/SEO";
import Comments from "../../components/widgets/Comments";
import appState from "../../states/appState";
import { apiClient } from "../../utils/request.util";
import { isClientSide } from "../../utils/ssr.util";
不知道你是否觉得其中的 Markdown, SEO, Comments,都非常可疑,为了解决我的疑惑,我直接注释掉了其中的一个引入和使用了它的代码。结果非常明显,当我注释了 Comments 后, 678 kB --> 546 kB
,于是我立马选择了动态导入。那其实这三个都是带有较大影响的,我就不一一举例了。
最终把这些都进行动态导入后,我得到的结果是这样的:
Route (pages) Size First Load JS
┌ λ / 3.61 kB 111 kB --> 99.7 kB
├ /_app 0 B 108 kB --> 96.1 kB
├ λ /[pages] 1.08 kB 691 kB --> 97.2 kB
├ λ /404 188 B 108 kB --> 96.3 kB
├ λ /category/[slug] 774 B 133 kB --> 96.9 kB
├ λ /links 746 B 559 kB --> 96.8 kB
├ λ /posts 801 B 133 kB --> 96.9 kB
├ λ /posts/[category]/[slug] 1.24 kB 691 kB --> 97.3 kB
└ λ /tag/[name] 732 B 133 kB --> 96.8 kB
你可以看到,都有不同程度的减少.
什么组件可以动态导入但什么组件不行?
- 如果用户不是立马就需要使用/看到的,可以
- 如果有影响交互效果的,不可以
- 动态导入后,大小不降反升的,肯定不可以啦!
- 详情页中输出详情的,可以,但我不太建议