一次性能优化实战:从 5s 到 1.5s 的页面加载
目录
有一次线上迭代后,陆续收到反馈:
「系统能用,就是打开慢了一截。」
「首页白屏时间变长了。」
监控一看,某个核心页面的首屏时间从 约 2s 抬升到了接近 5s。
这篇文章就是那次性能优化的实战复盘:我们如何量化问题、定位瓶颈、再一步步把首屏拉回到 1.5s 左右。
1. 先量化,而不是拍脑袋
第一步是搞清楚「慢」在哪:
- 首包下载?
- JS 执行?
- 接口耗时?
- 还是一堆「非必要逻辑」抢在首屏执行?
我们主要用了三种手段:
- Chrome DevTools Performance 看关键时序;
- Lighthouse 跑实验室指标;
- 前端埋点 看真实用户数据(FMP/TTI 的简化近似)。
初步结论:
- JS 资源总体体积较大,首屏要加载的 JS 超过 1MB(压缩后);
- 首屏渲染完成前存在一段较长的 JS 执行(主线程被占用);
- 首屏接口本身不是特别慢,但被一些不必要的请求夹在中间。
目标明确:瘦 JS、少阻塞、分优先级。
2. Bundle 拆分:让首屏只拿它真需要的
2.1 分析构建结果
接入 webpack-bundle-analyzer / Vite 插件之后,很快看到几个「大头」:
- 整个 UI 组件库全量引入;
- 某些图表库(在其他页面才用)被打进首屏 bundle;
- 若干工具库重复/未 Tree-shaking。
2.2 做的调整
-
组件库改为按需引入
- 把
import ElementUI from 'element-ui'改成 babel 插件/手动按需; - 打包结果里组件库体积明显下降。
- 把
-
路由级别代码拆分
const Dashboard = () => import('@/views/dashboard/index.vue'); const Report = () => import('@/views/report/index.vue');首屏只加载首页路由,其它页面按需加载。
-
大模块进一步 Lazy load
- 某个复杂筛选面板、报表区域改成「用户点击再加载」;
- 图表渲染延迟到首屏稳定后再执行。
结果:首屏 JS 体积从 ~1MB 降到 ~600KB 左右。
3. 首屏只做「必须的事」
3.1 拆解首屏职责
原来的首页逻辑:
- 同时请求 4–5 个接口(摘要数据、消息、推荐信息、运营位…);
- 所有数据都返回后再一起渲染;
- 同时初始化多个图表。
我们把它改成:
- 第一批接口:只请求首屏「核心信息」(例如资产总览、关键指标);
- 首屏渲染完成后,再发第二批请求(消息列表、推荐、报表等);
- 图表采用懒加载:用户滑到可视区域时再渲染。
3.2 骨架屏(Skeleton)
长白屏带来的体感其实比「慢」更糟糕。
我们给首页增加了:
- 顶部摘要卡片 Skeleton;
- 列表的占位条形块。
Skeleton 本身非常轻量,只需要一些灰块和简单动画,用户会感知到:
页面「正在」加载,而不是「卡死」了。
4. 缓存:重复访问的用户要更快
对于高频访问的首页,我们增加了两层缓存:
-
前端内存缓存
- 在单次会话中多次切回首页时,不重复请求;
- 结合时间戳做简单过期控制(例如 30s)。
-
本地持久缓存(localStorage / IndexedDB)
- 冷启动时优先渲染上一次打开时的摘要数据;
- 同时在后台请求最新数据并刷新。
伪代码:
async function loadDashboard() {
const cache = loadDashboardCache();
if (cache) {
render(cache);
}
const fresh = await api.getDashboard();
render(fresh);
saveDashboardCache(fresh);
}
这样即使网络条件一般,用户至少不会等着一片空白。
5. 小细节:图片、字体和第三方脚本
-
图片
- 首页尽量使用 SVG 或压缩后的 WebP;
- 尽量避免首屏出现大尺寸非必要图。
-
字体
- 不乱引第三方花哨字体,避免额外字体文件;
- 如果有图标字体,确认只打包实际用到的字形。
-
第三方脚本
- 延迟加载非关键第三方 SDK(埋点、聊天浮标等);
- 能异步就不要阻塞,能
defer就不要sync。
6. 优化前后指标对比
以某核心页面为例(有一定统计样本):
| 指标 | 优化前(均值) | 优化后(均值) |
|---|---|---|
| 首屏可见时间(近似 FMP) | ~5.0s | ~1.5–1.8s |
| 可交互时间(近似 TTI) | ~6.5s | ~2.5–3.0s |
| 首屏 JS 体积 | ~1MB | ~600KB |
| JS 执行总时长 | ~2.5s | ~1.2s |
更重要的是:
- 用户主观反馈「打开速度明显好一些了」;
- 业务侧不再频繁提「首页卡」的问题。
7. 遗留的问题和后续计划
虽然这次把首屏拉回来了,但还有一些遗留点:
- 有些老组件性能一般,后续还计划逐步替换;
- 双端适配(PC + 移动)下,部分样式计算/重排还有优化空间;
- 监控体系需要进一步精细化(区分不同网络类型、终端型号)。
性能优化本身没有终点,重要的是:建立可复用的排查套路和指标体系。
8. 小结:一套可复用的实践路径
这次实战下来,我觉得可以抽象出一条通用路径:
- 先用工具 & 埋点量化问题,搞清楚慢在哪;
- 查看打包结果,做 bundle 拆分 & 按需引入;
- 让首屏只做真正必须的事,把次要内容延后;
- 加 Skeleton 减少体感白屏时间;
- 对高频页面增加适度缓存;
- 持续看监控,避免「优化反弹」。
只要按这套流程走一遍,大多数 SPA 的首屏体验都会有肉眼可见的改善。