Skip to content

Performance

Aafff623 edited this page Aug 10, 2026 · 3 revisions

Performance

本站做过一轮系统的前端性能专项(分支 optimize-blog-performance,28+ 个 perf commit)。
本页记录:目标、方法、实测数字、踩过的坑、复测工具。改性能前先读,别凭直觉下手。

核心结论:瓶颈往往不在「下载慢」,而在「画不出来」。先上探针拿证据,再改代码。

目标与铁律

用户最痛的是「点击跳转要极快」(Swup SPA 跳转 / LCP / TTI)。

铁律(不可违背):界面样式是站主精心美化的,绝不阉割/删除美化组件换性能
只优化「加载策略 + 生命周期」——防重复注册、按需加载、离页销毁。任何触碰视觉/交互的改动必须先经站主同意。

实测成果(本地生产 build,5 次中位数)

指标 优化前 优化后 变化
移动 4G LCP 27236 ms 8332 ms -69%
桌面 LCP 960 ms 712 ms -26%
堆净增(20 跳) 18.9 MB 9.7 MB -48%
监听器净增(20 跳) 14096 9989 -29%
节点净增(20 跳) 123642 97240 -21%
首屏字体请求 50 6 收敛
功能回归 8 页全 200、0 报错 通过

移动 LCP 案例:一张图为什么 21s 才画出来

这是本轮最典型的「假根因 → 真凶」教学案例。

现象:移动 4G 下首屏横幅图(m1)LCP 高达 21s+。

证据链(CDP 探针实测):

m1 网络下载完成:        4.6 s
m1 解码完成(img.complete): 8.6 s
#wallpaper-wrapper 显示:  23.6 s   ← 卡在这
LCP 实际绘制:            ~21 s

真凶#wallpaper-wrapper 在 SSR 时带了 display: none,要等 initWallpaperMode()display: block。而这个函数挂在 DOMContentLoaded 上——移动弱网 + 4x CPU 下 DCL 要 23s 才触发。图早就下好了,就是一直被藏着。

修复(只动加载策略,不碰视觉):

  1. banner 模式首页 SSR 不再写 display: none(移动端非首页仍由 mobile-hide-banner class 隐藏),首帧 fadeIn={false} 去掉 opacity-0 的 JS reveal 依赖。
  2. 侧栏「园径便签」背景视频(518KB .mp4)改 data-src 惰性注入,PerformanceObserver 监听 LCP 落定后再播放,移出 LCP 窗口。

教训:排查时先后被三个假根因带偏——氛围层 11 张图(实为旧部署缓存数据)、opacity-0 reveal(改完只动一点)、CPU 节流(1x CPU 仍 18s)。是探针的逐帧时间线戳破了直觉

四大类优化手段

类别 手段 代表 commit
泄漏治理 page-lifecycle runtime(Swup 换页统一 abort/cleanup)、WalineController 离页销毁、TagCloud keep:false、14+ 组件幂等 guard 44d6a536 a95206bc 46f9dd3b
图片加载 桌宠只预取当前皮、氛围层只物化当前帧、idle 预取改 hover、日历 IntersectionObserver 按需、轮播物化上限 + m1 eager/avif 4f3d733d 63babfe6 a1e86474
覆盖率 三岛 client:idle、Pagefind 懒加载、内联脚本外置(assetsInlineLimit: 0 5ec897ec 7ded7f5f
渲染门控 banner 首帧去 display:none / fadeIn、装饰视频 LCP 后加载 e60a9606 9d9b149c

page-lifecycle runtime(防 Swup 泄漏的关键)

Swup 多跳会累积匿名监听器 / observer / timer。src/lib/page-lifecycle.ts 提供 per-page epoch,换页时统一清理:

API 用途
bootPageLifecycle() 幂等启动(Symbol.for 全局 guard),挂 astro:before-swap / swup:page:view
onPage(target, type, handler) AbortSignal 的监听,换页自动移除
observePage(observer) Mutation/ResizeObserver 换页自动 disconnect()
setPageTimeout / requestPageFrame 换页自动 clearTimeout / cancelAnimationFrame
registerPageCleanup(fn) 注册任意清理函数

用法:组件里 bootPageLifecycle() 后用上述 API 替代裸 addEventListener / setInterval。参考 src/components/comment/agent-avatar.ts

复测工具(探针)

完整探针脚本在 Codex 会话证据目录(见主仓 handoff 文档索引)。核心方法:

  • 本地验证rm -rf dist .astro + pnpm build + python -m http.server <port> serve dist/client,探针 FIREFLY_PERF_BASE=http://127.0.0.1:<port>
  • LCP 诊断:CDP Network.requestWillBeSent 抓请求发起方 + PerformanceObserver('largest-contentful-paint')loadTime/renderTime + longtask/Profiler 定位主线程瓶颈。
  • 泄漏:20 跳 Swup 导航后对比 JSEventListeners / Nodes / JSHeapUsedSize 净增。

踩过的坑(别再踩)

现象 正解
本地 server 锁 dist 改了代码复测全假象 验证前 rm -rf dist .astro + 停掉 serve dist/client 的进程
.astro 缓存被清 dev server 文章列表空 / 文章页 404 重启 dev server(内容集合缓存重建)
旧部署探针数据 拿着过期数据当现状 先确认探针打的是不是最新部署/最新 build
只信「报告说快」 优化无数字背书 每项改动必须探针前后对比

相关

Clone this wiki locally