目录

一次性能优化实战:从 5s 到 1.5s 的页面加载

有一次线上迭代后,陆续收到反馈:

「系统能用,就是打开慢了一截。」
「首页白屏时间变长了。」

监控一看,某个核心页面的首屏时间从 约 2s 抬升到了接近 5s
这篇文章就是那次性能优化的实战复盘:我们如何量化问题、定位瓶颈、再一步步把首屏拉回到 1.5s 左右。


1. 先量化,而不是拍脑袋

第一步是搞清楚「慢」在哪:

  • 首包下载?
  • JS 执行?
  • 接口耗时?
  • 还是一堆「非必要逻辑」抢在首屏执行?

我们主要用了三种手段:

  1. Chrome DevTools Performance 看关键时序;
  2. Lighthouse 跑实验室指标;
  3. 前端埋点 看真实用户数据(FMP/TTI 的简化近似)。

初步结论:

  • JS 资源总体体积较大,首屏要加载的 JS 超过 1MB(压缩后);
  • 首屏渲染完成前存在一段较长的 JS 执行(主线程被占用);
  • 首屏接口本身不是特别慢,但被一些不必要的请求夹在中间。

目标明确:瘦 JS、少阻塞、分优先级。


2. Bundle 拆分:让首屏只拿它真需要的

2.1 分析构建结果

接入 webpack-bundle-analyzer / Vite 插件之后,很快看到几个「大头」:

  • 整个 UI 组件库全量引入;
  • 某些图表库(在其他页面才用)被打进首屏 bundle;
  • 若干工具库重复/未 Tree-shaking。

2.2 做的调整

  1. 组件库改为按需引入

    • import ElementUI from 'element-ui' 改成 babel 插件/手动按需;
    • 打包结果里组件库体积明显下降。
  2. 路由级别代码拆分

    const Dashboard = () => import('@/views/dashboard/index.vue');
    const Report = () => import('@/views/report/index.vue');
    

    首屏只加载首页路由,其它页面按需加载。

  3. 大模块进一步 Lazy load

    • 某个复杂筛选面板、报表区域改成「用户点击再加载」;
    • 图表渲染延迟到首屏稳定后再执行。

结果:首屏 JS 体积从 ~1MB 降到 ~600KB 左右。


3. 首屏只做「必须的事」

3.1 拆解首屏职责

原来的首页逻辑:

  1. 同时请求 4–5 个接口(摘要数据、消息、推荐信息、运营位…);
  2. 所有数据都返回后再一起渲染;
  3. 同时初始化多个图表。

我们把它改成:

  1. 第一批接口:只请求首屏「核心信息」(例如资产总览、关键指标);
  2. 首屏渲染完成后,再发第二批请求(消息列表、推荐、报表等);
  3. 图表采用懒加载:用户滑到可视区域时再渲染。

3.2 骨架屏(Skeleton)

长白屏带来的体感其实比「慢」更糟糕。

我们给首页增加了:

  • 顶部摘要卡片 Skeleton;
  • 列表的占位条形块。

Skeleton 本身非常轻量,只需要一些灰块和简单动画,用户会感知到:

页面「正在」加载,而不是「卡死」了。


4. 缓存:重复访问的用户要更快

对于高频访问的首页,我们增加了两层缓存:

  1. 前端内存缓存

    • 在单次会话中多次切回首页时,不重复请求;
    • 结合时间戳做简单过期控制(例如 30s)。
  2. 本地持久缓存(localStorage / IndexedDB)

    • 冷启动时优先渲染上一次打开时的摘要数据;
    • 同时在后台请求最新数据并刷新。

伪代码:

async function loadDashboard() {
  const cache = loadDashboardCache();
  if (cache) {
    render(cache);
  }
  const fresh = await api.getDashboard();
  render(fresh);
  saveDashboardCache(fresh);
}

这样即使网络条件一般,用户至少不会等着一片空白。


5. 小细节:图片、字体和第三方脚本

  1. 图片

    • 首页尽量使用 SVG 或压缩后的 WebP;
    • 尽量避免首屏出现大尺寸非必要图。
  2. 字体

    • 不乱引第三方花哨字体,避免额外字体文件;
    • 如果有图标字体,确认只打包实际用到的字形。
  3. 第三方脚本

    • 延迟加载非关键第三方 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. 遗留的问题和后续计划

虽然这次把首屏拉回来了,但还有一些遗留点:

  1. 有些老组件性能一般,后续还计划逐步替换;
  2. 双端适配(PC + 移动)下,部分样式计算/重排还有优化空间;
  3. 监控体系需要进一步精细化(区分不同网络类型、终端型号)。

性能优化本身没有终点,重要的是:建立可复用的排查套路和指标体系。


8. 小结:一套可复用的实践路径

这次实战下来,我觉得可以抽象出一条通用路径:

  1. 先用工具 & 埋点量化问题,搞清楚慢在哪;
  2. 查看打包结果,做 bundle 拆分 & 按需引入
  3. 让首屏只做真正必须的事,把次要内容延后;
  4. Skeleton 减少体感白屏时间;
  5. 对高频页面增加适度缓存;
  6. 持续看监控,避免「优化反弹」。

只要按这套流程走一遍,大多数 SPA 的首屏体验都会有肉眼可见的改善。