本文内容使用 NuxtJS 2.14.1 (其中 vue 为 2.6.11, webpack 为 4.44.1) 和 Ant Design of Vue 1.6.4。因为版本和不同类库造成的差异请大家多进行搜索。
问题
用 NuxtJS 开发博客以来,一直有一些体验上的问题,这次正好有时间,便来优化一下,目前的问题主要有:
- 依赖的 js css 文件较大
- 服务器端渲染生成的 HTML 较大
导致访问速度较慢,特别是首次加载。
依赖的 js css 文件较大
首屏加载过慢有很多原因,最常见的就是网页依赖的资源(例如 vue 和各种类库的 js)太多或者太大,浏览器需要花一些时间才能将它们下载下来。
通过“开发人员工具”查看加载时间
打开浏览器的 开发人员工具 - 网络 ,然后访问网站,来查看网页需要加载哪些东西,以及它们的大小和耗时。
查看时需要勾选
禁用缓存来模拟首次到访网站的浏览者的真实状态。
如果界面和我的略有不同,可以点击右上角齿轮图标按钮,勾选使用大请求行。

通过 Size 列可以看到,60a2df....js 这个 js 文件大小为 594 k(相当于半兆),由于采用了 gzip 压缩,它的原始大小为 2.4MB,是一个相当大的文件了,而且花了5秒才下载完成,体验非常不好。
分析文件内容
由于这个文件在 /_nuxt 下,且名字为随机生成,这应该是 webpack 打包生成的文件。NuxtJS 内置了 webpack-bundle-analyzer 来分析打包后的文件。
打开 nuxt.config.js 并找到 build 属性,添加 analyze: true 以启用分析模式:
module.exports = {
build: {
analyze: true
}
}
然后在控制台中输入 npx nuxt build --analyze 等待分析完成。

可以看到 vendors.app.js 很大,而它主要又由 ant-design-vue、@ant-design/icons/lib 和 moment 等内容组成。如果能把这些大个的文件减少,那生成的 vendors.app.js 文件体积也会减小。
将 antd vue 改为按需加载
从分析结果中可以看到 ant-design-vue 下面有很多的组件,虽然我并没有使用到,但也包含在了里面,应该是因为在引入 antd vue 时直接全量引入导致的。通过查找 antd vue 的 按需加载文档 了解到,可以使用 import Button from 'ant-design-vue/lib/button' 的方式来加载组件,那么只需要修改 NuxtJS 的 antd vue plugin 就好了。
我没有按文档使用 babel-plugin-import,因为我希望 antd.css 不要打包,我自己通过 CDN 来加载,这个后面会说。
找到 plugins/antd-ui.js 文件,原来的写法是直接引用整个 antd vue
import Vue from 'vue'
import Antd from 'ant-design-vue/lib'
Vue.use(Antd)
现在改为按需引入需要的组件
import Vue from 'vue'
import Antd from 'ant-design-vue/lib'
import { message, notification } from 'ant-design-vue'
import Button from 'ant-design-vue/lib/button'
import Layout from 'ant-design-vue/lib/layout'
// 后略
Vue.prototype.$message = message
Vue.prototype.$notification = notification
Vue.use(Button)
Vue.use(Layout)
// 后略
注意需要手动为 Vue 加上 $message 和 $notification 才能正常使用
减小 antd icons 占用的空间
![]()
antd vue 依赖 2.1.1 版本的 antd icons,而这 icons 也太大了……搜索到比较好的解决方案是 [ #12011 Svg icons make bunlde size too large](https://github.com/ant-design/ant-design/issues/12011 issuecomment-420038579) 说的,根据它的指引进入到 reduce-antd-icons-bundle-demo 可以看见解决方案。
是将我们需要使用的图标整合到一个 js 文件里,然后再通过 webpack 的 alias 用我们的 js 文件替代掉原始的占空间很大的 antd icons 文件。
首先,在 assets 文件夹下建立 antd-icons.js 文件(其实放在哪个文件夹、文件叫什么没有强制要求),把我们用到的图标都添加进去,包括控件上依赖的(比如 Popconfirm 气泡确认框前面那个感叹号图标):
export {
default as WechatOutline
} from '@ant-design/icons/lib/outline/WechatOutline'
export {
default as ExclamationCircleOutline
} from '@ant-design/icons/lib/fill/ExclamationCircleFill'
// 后略
需要注意的是要区分好图标属于什么类型的,需要分别从 @ant-design/icons/lib/outline、@ant-design/icons/lib/fill 或者 @ant-design/icons/lib/twotone 里引入。
![]()
控件依赖的图标可以通过“开发人员工具”的元素查看器查看到。
然后在 nuxt.config.js 中为 webpack 设置 alias:
var path = require('path');module.exports = {
build: {
extend(config, ctx) {
// antd icons 按需加载,能大幅降低 antd icons 占用的空间,提高加载速度
config.resolve.alias['@ant-design/icons/lib/dist$'] = path.resolve(__dirname, './assets/antd-icons.js')
}
}
}
这样 antd icons 就变成我们自己编写的精简版了。
减小 moment 占用的空间(使用 dayjs 代替 moment)

antd 团队有一个名为 antd-dayjs-webpack-plugin 的插件可以将 moment 替换为体积很小的 dayjs,非常方便。
首先安装 dayjs 和该插件:
npm i dayjs --save
npm i antd-dayjs-webpack-plugin --save-dev
然后为 NuxtJS 添加 dayjs 插件,用于配置默认地区(如果已经配置过则忽略这一步)。在 plugins 文件夹中新建 dayjs.js:
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
dayjs.locale('zh-cn')
接着在 nuxt.config.js 中添加上面创建的 dayjs plugin 以及向 webpack 添加 antd-dayjs-webpack-plugin
var AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin')
module.exports = {
plugins: [
'@/plugins/dayjs'
],
build: {
plugins: [
new AntdDayjsWebpackPlugin()
]
}
}
这样就把压缩后 60 多 k 的 moment 替换成了不到 4k 的 dayjs 了。
使用 CDN
对于原始文件
正如前文提到的 antd.css(压缩后有 55k 以上,很大),因为我没有做任何修改,所以直接从 CDN 引入这个文件会更好,在这里我强烈推荐 jsDelivr,能从 GitHub、npm 直接加速,而且国内也有节点,十分稳健。
直接向 nuxt.config.js 中添加 antd.css 的引用:
module.exports = {
link: [
{ rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/npm/ant-design-vue@1.6.4/dist/antd.css' }
]
}
对于动态生成的文件
直接从 CDN 引入的文件并非越多越好,webpack 将我们依赖的类库打包在一起能获得更好的加载性能(浏览器用于访问资源的线程是有限的)。所以对于剩下这些大小可以接受的类库,以及我们自己编写的代码,使用 webpack 打包后,上传到自己的 CDN 中,效果会更好。
nuxt.config.js 中可以配置 publicPath 指向 CDN 上的资源:
module.exports = {
build: {
publicPath: process.env.NODE_ENV === 'development' ? '/_nuxt/' : 'https://cdn.bun.plus/blog/client/'
}
}
然后将 npm run build 命令生成的包里的 /.nuxt/dist/client/ 文件夹上传到 CDN 对应目录即可(publicPath 配置的地址对应到 /.nuxt/dist/client/ 文件夹里面的内容)。
使用 CDN 时我们注意需要开启客户端缓存以及压缩
- 默认每次打包生成的文件名都不同,可以放心的把客户端缓存时间设置得较长,以保证回访者能在本地命中缓存,提高访问速度
- 可将
.js、.css这类文件在 CDN 中设置为自动压缩,最大限度减小文件体积 - 如果自己没有 CDN,可以考虑上传到 GitHub 上,然后通过 jsDelivr 来加速
小总结
鹅妹子嘤!经过上面一番操作,我们的成果非常显著:

app.js 的大小从原来的 2.25M(压缩后575k)减小到741k(压缩后213k),减小了一倍多!

并且在 CDN 的帮助下,100ms 以内就能返回,和原来 1M 带宽的小水管需要 5s 返回比起来提升更大!
那我们总结一下吧:
确定问题原因
- 依赖的 js 等文件过大
- 依赖的 js 等文件加载时间过长
由文件过大导致的加载时间过长
- 尽量按需引入依赖的类库
- 使用小巧的类库替代庞大的类库
- 将较大的,能通过公共 CDN 引用的文件单独引用(但数量不宜过多)
- 将大小可接受的内容打包到一起,通过自有的 CDN 加速
服务器端渲染生成的 HTML 较大
加载慢还有一个问题是因为访问到的 HTML 文件较大(可以像上面一样通过“开发人员工具”查看)。当点开服务器返回的 HTML 内容时惊呆了,博客正文内容上方全是项目依赖的,以及我自己编写的 CSS 代码:

这样有一个比较严重的问题——对 SEO 不友好。你以为所有人看到的都和浏览器里显示的一样,但搜索引擎看到的确是这样(没有上面图里那篇博文的截图了,下面是百度抓取另一篇博文《使用 Nuxt.js 搭建博客前端》的内容):

而且搜索引擎可能会直接忽略 200k 之后的内容,导致你的实际内容不被收录。
通过在 nuxt.config.js 中配置 extractCSS 就可以将 CSS 单独生成文件,而非嵌入到页面中:
module.exports = {
build: {
extractCSS: true
}
}
通过把 antd.css 改用 CDN 加载,并且自定义的 CSS 不嵌入到页面上,访问《Windows Terminal 美化及通过右键菜单启动》这篇博客,服务器生成的 HTML 大小由 529k(压缩后 72k)减小到 52.8k(压缩后 12.3k),加载时间从 400ms 降低到了 200ms 以内,效果很棒。
更重要的是,这增加了网页被搜索引擎发现与收录的机会。
