读书笔记 · 约 17,728 字 · 预计推送 2 部分

前端性能优化方法论:Core Web Vitals、缓存、图片、代码分割与静态站点部署(主题精读) 未开始

目录

题记与说明


一、来源清单

#来源发布/更新时间角色抓取方式
1Web Vitals (web.dev)2020-05 发布,2024-10 更新CWV 指标框架web_reader 全文
2Optimize Largest Contentful Paint (web.dev)2020-04 发布,2025-03 更新LCP 优化方法论web_reader 全文
3HTTP caching (MDN)2026-08 更新HTTP 缓存权威指南web_reader 全文
4Image performance (web.dev Learn)2023-11 更新图片优化web_reader 全文
5Reduce JavaScript payloads with code splitting (web.dev)2018-11代码分割web_reader 全文
6Customize headers — Cloudflare Pages docs在线文档静态站点部署(缓存头配置)WebFetch 摘要
7(补充)Optimize CLS (web.dev)在线更新视觉稳定性WebFetch 摘要

检索过程:先用 WebSearch 定位了 web.dev / MDN / Cloudflare 等官方来源(检索词含 "Core Web Vitals LCP INP CLS thresholds"、"MDN HTTP caching Cache-Control ETag"、"web.dev image optimization AVIF WebP"、"reduce JavaScript payloads code splitting"、"Netlify OR Vercel OR Cloudflare Pages docs cache control"),再对命中页面逐篇精读。


二、各来源精读笔记

1. Web Vitals:CWV 指标框架(web.dev)

定位:Google 发起的"统一体验质量信号"计划,把过去泛滥的性能工具/指标收敛为三个核心指标。

"Site owners shouldn't have to be performance experts to understand the quality of experience they are delivering to their users."

当前三指标(阈值按 75 分位衡量,移动/桌面分开):

"The current set focuses on three aspects of the user experience—*loading*, *interactivity*, and *visual stability*."
"a good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices."

指标生命周期:experimental → pending → stable,稳定指标"一年最多改一次"("Stable Core Web Vitals metrics won't change more than once per year"),并有公开 CHANGELOG。

测量方式:

import {onCLS, onINP, onLCP} from 'web-vitals';

function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  (navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
    fetch('/analytics', {body, method: 'POST', keepalive: true});
}
onCLS(sendToAnalytics); onINP(sendToAnalytics); onLCP(sendToAnalytics);

辅助指标:TTFB 与 FCP 用于诊断 LCP(慢服务器 vs 渲染阻塞资源);TBT 是 lab 指标,用于诊断 INP,但"not field-measurable"故不入核心集。


2. Optimize LCP:把 LCP 拆成四段逐一优化(web.dev)

核心方法论:LCP 不能"一把抓",要拆成无重叠、无间隙、相加恒等于总时长的四个子段:

子段含义理想占比
TTFB用户发起导航到收到 HTML 首字节~40%
Resource load delayTTFB → 开始加载 LCP 资源<10%
Resource load duration加载 LCP 资源本身~40%
Element render delayLCP 资源加载完 → 元素完全渲染<10%
"Every single page can have its LCP value broken down into these four subparts. There is no overlap or gap between them."

理想状态:"The __vast majority__ of the LCP time should be spent loading the HTML document and LCP source. Any time before LCP where one of these two resources is *not* loading is __an opportunity to improve__."

关键警告(防"伪优化"):只压缩图片可能不降 LCP,省下的时间会转移给 render delay(如果 LCP 元素要等 JS 揭示)——"it wouldn't actually improve LCP because the time would just shift to the *element render delay* subpart"。所以四段都要优化。

逐段处方(按影响排序):

  1. 消灭 resource load delay:
    • 判断标准:"your LCP resource should start loading at the same time as the first resource loaded by that page"。
    • LCP 资源必须在初始 HTML 中可被 preload scanner 发现;只在 CSS/JS 里引用的,用 <link rel="preload" fetchpriority="high" as="image"> 补救。
    • 绝不能懒加载 LCP 图:"Never lazy-load your LCP image, as that will always lead to unnecessary resource load delay, and will have a negative impact on LCP."
    • 给 LCP 图加 fetchpriority="high"(但"setting a high priority on more than one or two images makes priority setting unhelpful")。
    • 关键资源尽量与 HTML 同源(跨源要新建连接)。
  2. 消灭 element render delay:减小/内联渲染阻塞 CSS("inlining your style sheet is only recommended if your style sheet is small");<head> 里几乎永远不该放同步脚本;用 SSR——SSG/预渲染更好:"If prerendering is possible with your architecture, it's generally a better choice for performance.";打断长任务(图片渲染也在主线程)。
  3. 缩短 resource load duration:减尺寸(最优尺寸/现代格式/压缩)、减距离(CDN,图片 CDN 尤佳)、减带宽竞争、用高效缓存策略把网络时间归零("bringing the *resource load duration* to essentially zero")。
    • 官方提醒:"the load duration portion of LCP tends not to be a significant bottleneck for most sites"——先看真实数据再下力气。
  4. 降低 TTFB:减少重定向链、避免带唯一查询参数导致 CDN 边缘缓存失效("all requests must be directed all the way back to the origin server")。

诊断入口:PageSpeed Insights 优先看 CrUX 分 URL→分 origin→分设备的四类 field 数据;TTFB 与 FCP 差值大 = 渲染阻塞多/CSR 过重;FCP 与 LCP 差值大 = LCP 资源发现晚或主线程被占。


3. HTTP Caching(MDN):缓存模型与两种通用模式

缓存分型:

核心机制:

两种通用模式(MDN 给出的"绝大多数网站"配方):

  1. Cache busting 子资源:内容变则 URL 变(bundle.v123.js / bundle.js?v=123 / 文件名哈希),配最长缓存:
   # bundle.v123.js
   Cache-Control: public, max-age=31536000, immutable
   Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT
   ETag: "YsAIAAAA-QG4G6kCMAMBAAAAAAAoK"

public 仅在带 Authorization 头时才需要显式给出。QPACK 常用值表(HTTP/3 下 1 字节压缩):max-age=604800(1 周)/2592000(1 月)/public, max-age=31536000(1 年)。

  1. 主资源(HTML):URL 无法 cache busting,用验证式缓存:
   Cache-Control: no-cache
   Last-Modified: ...
   ETag: "..."

个性化 HTML 加 private。favicon、manifest、.well-known、不可换 URL 的 API 同此法。

部署侧联动:标准 HTTP 缓存无法主动删除中间缓存内容;用允许 purge 的 CDN 或 Service Worker/Cache API 即可对主资源也做激进缓存,"storing the main resource and explicitly purging the relevant cache only when an update occurs"。


4. Image Performance(web.dev Learn):图片工程

前提:"Images are often the heaviest and most prevalent resource on the web."

四个优化杠杆:

  1. 正确的尺寸(dimensions):
    • 目标: intrinsic size ≈ CSS 容器尺寸 × DPR。
    • DPR 概念源自 iPhone 4(640×960 屏幕、DPR=2);"In most cases, the human eye is unable to benefit from a DPR of 3"——DPR≥3 可适当降档。
    • srcset(像素密度描述符 1x/2x/3x 或宽度描述符 500w/1000w)+ sizes(媒体条件 + CSS 像素显示宽度)由浏览器自动选图。
    • 注意:宽度描述符"must have both sizes attribute and width descriptors, or they don't work"。
  2. 现代格式:
    • "WebP is a widely supported format that works on all modern browsers"(有损/无损、带 alpha 的有损压缩)。
    • "AVIF ... tests have shown greater than 50% savings when compared to JPEG in some cases",并支持 WCG/HDR;兼容性弱于 WebP。
    • 用 <picture> 做格式降级:AVIF → WebP → JPEG,<img> 兜底且 alt/width/height 写在 <img> 上。
  3. 压缩:有损(量化 + 色度抽样,适合照片;锐边/文字类图慎用)与无损(GIF/PNG/WebP/AVIF);工具 Squoosh/ImageOptim/图片 CDN;"there isn't a universal setting suitable for all cases",需逐图试。
  4. 加载时序:loading="lazy" 视口内才下载(带宽让给关键资源);decoding 属性控制解码时机(对超大高分辨率图才有可感知差异)。

复杂度管理(容易被忽视的反面教训):

"every additional image variant comes at a cost and makes less efficient use of the browser cache."

变体越多 → 缓存条目越多、回源概率越高、HTML 体积越大(每图可能多出数 KB)。平衡点是"合理的候选数量 + 实测用户体验";全宽 hero 图可以比商品缩略图多变体。
另一种方案:服务端按 Accept 请求头返回最优格式,并必须配 Vary 响应头让 CDN 正确分流,否则共享缓存会串格式。


5. Code Splitting(web.dev):JavaScript 体积工程

"Over 50% of users abandon a website if it takes longer than 3 seconds to load."

定义与收益链路:

"Code splitting is a technique that seeks to minimize startup time. When we ship less JavaScript at startup, we can get applications to be interactive faster by minimizing main thread work during this critical period."

手段:


6. Optimize CLS(web.dev,补充来源)


7. Customize headers(Cloudflare Pages docs):静态站点部署侧的缓存落地

机制:构建产物根目录放无扩展名纯文本 _headers 文件(框架项目放 public//static/),Pages 解析后对静态资产响应附加头;文件本身不会被当作静态资产发出;文件里定义的头覆盖 Cloudflare 默认头。

典型配方(文档原例——哈希资产长缓存):

/static/*
  Cache-Control: public, max-age=31556952, immutable

移除头:

/*.jpg
  ! Content-Security-Policy

规则与坑(均出自该文档页):

结合 MDN 的模式,即可得出静态站部署的标准策略:HTML → no-cache(+ ETag/Last-Modified),哈希子资源 → max-age=31536000, immutable,均由 _headers(Netlify/Vercel 同类机制)或平台配置声明。


三、七维提取(综合)

1. 核心问题

如何把"网页快不快"从主观感受变成可量化、可归因、可系统性优化、跨工具统一报告的工程问题。它需要同时回答三件事:用什么标准衡量(CWV)、瓶颈藏在链路哪一段(子段拆解)、用哪些杠杆去撬动(缓存/图片/JS/部署架构)。五份材料合起来是一条完整流水线:度量 → 归因 → 资源工程 → 缓存策略 → 部署架构。

2. 主要主张

  1. 统一度量:LCP/INP/CLS 三指标 + 75 分位 + field 数据优先,是当前判定"好体验"的公共语言(Google 全工具对齐)。
  2. 分段归因:任何单一 quick fix 都难显著改善 LCP;必须四段(TTFB/load delay/load duration/render delay)逐一优化,因为"时间会转移"而非消失。
  3. 缓存是最大杠杆:cache busting + 长缓存 immutable(子资源)与 no-cache + ETag 验证(HTML/主资源)两个模式即可覆盖绝大多数网站;别滥用 no-store。
  4. 图片是最重资源,要做"尺寸工程":intrinsic size 对齐 容器×DPR,现代格式(AVIF/WebP)+ 压缩 + 懒加载,但绝不懒加载 LCP 图;变体数量要克制以免稀释浏览器缓存。
  5. JS 优化服务于主线程:代码分割/懒加载/ vendor 分离 → 更快可交互 → 改善 INP;CSR 架构下同样改善 LCP。
  6. 静态站点 + CDN 是性能最优默认架构:SSG/预渲染让 LCP 资源可在初始 HTML 中被发现、无需额外 JS 即可渲染;配合平台 _headers/purge API 把"HTML 永远新鲜 + 资产永远命中"变成声明式配置。

3. 方法

4. 数据与证据

5. 结论

前端性能优化已经方法论化:它不是技巧集,而是一条可复用的流水线——用 CWV 定靶、用子段拆解归因、用资源工程(图片/JS)与缓存策略两个轮子驱动、最终收敛到"静态化 + CDN + 声明式缓存头"的部署形态。对内容型/文档型站点,"SSG + no-cache 的 HTML + immutable 的哈希资产 + preload/fetchpriority 的 LCP 图"就是接近理论最优的默认配方;单页重交互应用再叠加路由级代码分割。所有优化动作必须回到 field 数据验证,防止"时间转移"式伪优化。

6. 局限(含作者自认与我的推断)

  1. 指标仍在演化:INP 替代 FID 仅一年余;CWV"一年最多一改"意味着任何建立在具体阈值上的自动化都会随年度更新失效。
  2. 75 分位是折衷:它隐掉了最差 25% 用户的体验;对转化关键页可能需要更严的内部目标(文档未展开)。
  3. 时效不齐:code splitting 文更新于 2018 年(围绕 webpack SplitChunksPlugin,未覆盖 Vite/Rollup 原生动态 import 生态或 Turbopack/Rspack);image performance 模块 2023 年更新,未纳入较新的格式降级策略实践。读者应把"机制"当真、"工具链"当历史。
  4. LCP 四段占比是"指南,非规则"(文档原话),且官方自认 load duration 对多数站点并非瓶颈——压图之外的 render delay/TTFB 常被低估。
  5. 效益数据的出处强度弱:3 秒放弃率、AVIF 节省均为引用型统计,未给出样本与年代,且"some cases"限定词明显。
  6. 缓存模型的天花板:标准 HTTP 无法主动删除中间缓存中已存储的长缓存响应(MDN 明示);解法依赖各 CDN 的 purge API,跨平台行为不一(搜索结果亦提示"双 CDN 叠加导致 stale 部署"这类坑,官方文档未覆盖)。
  7. 图片变体工程无普适答案:多少变体"合理"全靠"it depends"+ 实测,Accept 头方案要求服务端/边缘可编程,纯静态托管只能靠 <picture>(HTML 体积膨胀)。
  8. 推断性局限(非原文自认):Cloudflare 文档仅限自身平台语义(如 CDN-Cache-Control),Netlify/Vercel 默认头策略与之有差异,跨平台迁移时"同一配方"可能产生不同响应头;本笔记未实测各平台,不能断言一致性。

7. 对我的启发(可执行清单)

  1. 给自己的静态站点定一个"默认部署配方":HTML no-cache + ETag/Last-Modified;/assets/*(哈希文件名)public, max-age=31536000, immutable;用 _headers 文件声明并进版本库,部署即配置。
  2. 10 行代码上 RUM:web-vitals + sendBeacon 上报三指标,攒自己的 75 分位数据,替代只看 Lighthouse 分数的习惯。
  3. LCP 图四件套:fetchpriority="high"、必要时 <link rel=preload>、绝不 loading="lazy"、与 HTML 同源托管;反过来给轮播图等非首屏图降优先级。
  4. 图片管线收敛:每图 2–3 个宽度变体(对齐 DPR 2/3 与移动/桌面断点)+ AVIF→WebP→JPEG 的 <picture>,拒绝变体爆炸;hero 图允许比缩略图更多变体。
  5. JS 默认做路由级分割 + vendor chunk 分离,把"首屏只发当前路由所需"当作构建产物的验收标准。
  6. 排查顺序纪律:先 CrUX/RUM 确认真问题 → 拆 LCP 四段找最大段 → 逐段优化 → 回 field 验证;严禁"凭感觉压图"。
  7. 写作/沉淀角度:MDN 的"两种通用缓存模式" + Cloudflare 的 _headers 示例可以合成一篇《静态站部署缓存一页纸》,比散读十篇博客更能复用。

四、原始引述索引(重要原文备查)

  1. "Site owners shouldn't have to be performance experts to understand the quality of experience they are delivering to their users." — Web Vitals
  2. "a good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices." — Web Vitals
  3. "Only field measurement can accurately capture the complete picture." — Web Vitals
  4. "Never lazy-load your LCP image, as that will always lead to unnecessary resource load delay, and will have a negative impact on LCP." — Optimize LCP
  5. "If prerendering is possible with your architecture, it's generally a better choice for performance." — Optimize LCP
  6. "Our research shows that the load duration portion of LCP tends not to be a significant bottleneck for most sites." — Optimize LCP
  7. "basically all responses should explicitly specify a Cache-Control header." — MDN HTTP caching
  8. "The no-cache directive does not prevent the storing of responses but instead prevents the reuse of responses without revalidation." — MDN HTTP caching
  9. "There is no way to delete responses on an intermediate server that have been stored with a long max-age." — MDN HTTP caching
  10. "every additional image variant comes at a cost and makes less efficient use of the browser cache." — Image performance
  11. "AVIF ... tests have shown greater than 50% savings when compared to JPEG in some cases." — Image performance
  12. "Over 50% of users abandon a website if it takes longer than 3 seconds to load." — Code splitting
  13. "Ads are one of the largest contributors to layout shifts on the web." — Optimize CLS
  14. "/static/* → Cache-Control: public, max-age=31556952, immutable" — Cloudflare Pages: Customize headers
抓取说明:来源 1–5 经 web_reader 抓取全文(含表格与代码);来源 6–7 经 WebFetch 摘要抓取(引述与数字均来自其返回内容)。INP 取代 FID、CWV 阈值等亦与 Google Search 文档搜索结果交叉印证。本笔记未做任何本地实验,全部证据为文档与检索所得。
← 资料库今日播报 →