一、前言
性能篇收官汇总:前面散落的优化点(050/064/072/073/075/076/077)串成一张"首屏速度"作战地图。拿到"首屏 5 秒"的烂项目,按清单从上往下打,这一篇就是执行手册。
二、先诊断:首屏到底慢在哪
打开 Chrome DevTools → Network,刷新页面看瀑布图,按四步定位:
text
1. 看总量:底栏 Total 体积多大?(> 5MB 就有硬伤)
2. 看大头:按 Size 排序,谁占最大?(大 JS 包 / 大图 / 字体)
3. 看时序:JS 是不是串行等待?(main.js 下载完才开始拉 chunk)
4. 看渲染:Performance 录制,白屏段在等什么?(接口阻塞渲染?)
常见病根排序:包太大 > 图片太大 > 串行请求 > 服务端慢。对症下药才有意义,别上来就盲改。
三、优化清单(按性价比排序)
3.1 包体积层(收益最大)
| 优化 | 出处 | 要点 |
|---|---|---|
| 路由懒加载 | 050 篇 | 所有页面 () => import(),首页只发首页的包 |
| 组件懒加载 | 072 篇 | 富文本/图表等重组件 defineAsyncComponent |
| UI 库按需导入 | 064 篇 | unplugin 自动按需 |
| 分包 manualChunks | 077 篇 | 库走长缓存,发版只更新业务包 |
| gzip 预压缩 | 077 篇 | 体积立减 60%+,几乎零成本 |
| 库替换 | 077 篇 | moment→dayjs(-280KB)、lodash→lodash-es(支持 tree-shaking) |
3.2 渲染体验层(感知提速)
| 优化 | 出处 | 要点 |
|---|---|---|
| 骨架屏 | 075 篇 | 白屏变成"内容形状预告" |
| 图片懒加载 | 075 篇 | 首屏只加载可视区图片 |
| 长列表虚拟化 | 074 篇 | 首屏 DOM 数量封顶 |
| loading 骨架先行 | 075 篇 | 接口慢≠界面慢,占位先行 |
3.3 交互层(防卡顿)
| 优化 | 出处 | 要点 |
|---|---|---|
| 防抖节流 | 076 篇 | 搜索/滚动不再疯狂触发 |
| keep-alive 缓存 | 073 篇 | 返回不重渲染 |
| 事件清理 | 037 篇 | 防内存泄漏(越用越卡) |
四、预加载:让"下一步"提前出发
分包/懒加载的代价是"用到才请求",预加载就是把代价补回来:
4.1 Vite 自动做了什么
html
<!-- Vite 构建后 index.html 里自动出现:-->
<link rel="modulepreload" href="/assets/vendor-xxx.js" />
<!-- 首页路由 chunk 也会被分析并预加载:现代浏览器解析 HTML 时就提前下载 JS -->
默认已够用,不用手写。
4.2 手动控制的三种 link
html
<!-- index.html -->
<!-- 提前建立与接口服务器的连接(DNS + TCP + TLS) -->
<link rel="preconnect" href="https://api.example.com" />
<!-- 预解析第三方 CDN 的 DNS -->
<link rel="dns-prefetch" href="https://cdn.example.com" />
<!-- 预加载关键资源(如首屏大图、字体) -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin />
原则:只预加载"几乎必然要用"的资源,预加载没人看的东西是负优化。
4.3 关键接口并行
ts
// ❌ 串行:user 1s + menu 1s + notice 1s = 3s
const user = await getUser()
const menu = await getMenu(user.uid)
const notice = await getNotice()
// ✅ 能并行的并行:总耗时 = 最慢的 1s
const [userRes, menuRes, noticeRes] = await Promise.all([
getUser(), getMenu(), getNotice()
])
菜单如果依赖 uid:user 先行,menu + notice 并行------分析依赖关系再决定谁能并行(059 篇 Promise.all 的实战落点)。
五、缓存策略:让"第二次访问"飞起来
浏览器缓存的分层设计(Nginx 配置视角):
nginx
# 带 hash 的静态资源:内容变 → 文件名变 → 可以放心"永久缓存"
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# index.html:永不长缓存(★ 发版入口,缓存错了用户永远在旧版本)
location = /index.html {
add_header Cache-Control "no-cache"; # 每次都向服务器确认是否更新
}
原理:Vite 打包的文件名自带内容 hash(app-3f8b2c.js),内容变了名字就变------assets 缓存一年也不会出旧版本,只要 index.html 不被缓存,用户永远拿到最新入口。
六、SSR 概念引入:什么时候该换架构
前端优化的天花板:CSR(现有方案)的 HTML 是空壳,必须等 JS 下载执行完才有内容。
| 方案 | 首屏 HTML | 适用 | 成本 |
|---|---|---|---|
| CSR(SPA) | 空壳 <div id="app"> |
中后台、交互密集 | 低(本系列方案) |
| 预渲染 prerender | 构建时生成完整 HTML | 官网、落地页(内容固定) | 低 |
| SSR | 服务器实时渲染完整 HTML | C 端内容页、SEO 刚需 | 高(Nuxt / 服务端运维) |
判断口诀:
text
内部系统、要登录 → CSR 足够,把本篇清单打满
官网/落地页内容固定 → 预渲染(vite-plugin-ssr / prerender)
C 端内容页、SEO 重要(商城/资讯) → SSR(Nuxt 3,Vue 官方生态,另开系列)
SSR 的本质:把"渲染"从浏览器搬到服务器,用户拿到 HTML 就能看,代价是服务器成本和开发复杂度------性能优化清单没打满之前,别急着上 SSR。
七、踩坑记录
- 只优化不测量:改完不录 Performance/不对比 Network,不知道有没有用------每次优化记录前后数据
- index.html 被强缓存:用户永远加载旧版本入口,发版不生效------no-cache 只对 index.html,assets 才长缓存
- 预加载滥用:preload 十几个资源抢带宽,关键路径反而慢------预加载"必然用"的少数资源
- Promise.all 一个失败全崩:首屏数据互相独立时用 allSettled 或分别 catch(059 篇提过)
- 优化顺序反了 :先折腾 SSR/微前端等大手术,基础清单没打满------性价比从上往下,包体积永远第一
- 混淆首屏慢和接口慢:Network 里 DOMContentLoaded 快但内容晚出 = 接口慢,优化方向完全不同(接口在 060-063 篇)
八、今日小结
- 先诊断后优化:Network 四步定位(总量/大头/时序/渲染)
- 清单三层:包体积(最大收益)→ 渲染体验(感知提速)→ 交互防卡
- 预加载补懒加载的代价:modulepreload 自动、preconnect/preload 手动、接口并行
- 缓存关键:assets 永久缓存(hash 文件名)+ index.html no-cache
- 架构选择:CSR 够用打满清单,内容站才考虑预渲染/SSR(Nuxt)
下篇预告
性能篇完结,进入生态扩展篇:VueUse 必会工具集 top20------防抖、剪贴板、本地存储、鼠标跟踪全都有现成的,开发效率直接起飞。下一篇 079。