重构大版本更新
一口气全面统一博客样式,移除部分冗余导航页面,更专注内容展示。
整理前端与站点性能优化十项主流方法:性能分析、HTTP 缓存、CDN、Nginx 配置、加载策略、图片、字体与 JS 长任务治理,每项说明原因、做法与收益。

性能优化的手段多且杂,本文把它们归拢到「字节、往返、距离、主线程」四个变量上,按先测量后动手的顺序展开十项主流实践。每项统一说明问题原因、处理方法、提升幅度与注意事项,文末附端到端 Nginx 配置、踩坑清单与方法总览表,关键数据标注了来源。
一个常见场景:一个日均 PV 几千的内容型站点,4G 网络下首屏耗时 4 秒开外,Lighthouse 跑分不到 50。排查结果很典型:文章配图是没压缩的 2 MB 原图;静态资源全都没有缓存头,回访用户每次重新下载;服务器部署在境外,TTFB 接近 800 ms。三个问题对应三类浪费:字节太多、往返太多、距离太远。
业务侧的代价有数据口径:Google 的移动端速度研究显示,页面加载时间从 1 s 涨到 3 s,跳出概率上升 32%(来源:Google/SOASTA 研究,业界广泛引用)。
性能优化的每一项手段,最终都落在少数几个变量上。本文按「先测量,再动手」的顺序整理十项主流实践:性能分析、传输协议、传输压缩、HTTP 缓存、CDN、Nginx 配置、加载策略、图片、字体、JS 执行,每项按问题原因、处理方法、提升幅度、注意事项展开,数据来源统一列在参考资料。
Core Web Vitals 是 Google 定义的三项真实用户体验指标,同时是搜索排名信号(web.dev):
| 指标 | 含义 | 良好阈值 |
|---|---|---|
| LCP(最大内容绘制) | 主内容渲染完成的时间 | ≤ 2.5 s |
| INP(交互到下一次绘制) | 交互响应延迟,2024 年 3 月取代 FID | ≤ 200 ms |
| CLS(累积布局偏移) | 视觉稳定性 | ≤ 0.1 |
页面加载耗时可以近似拆成三段:资源体积除以带宽、请求数乘以往返时间(RTT)、服务端处理时间。后文的方法都在改这几个变量:压缩和图片格式减少字节,缓存和协议升级减少往返,CDN 缩短 RTT 本身,JS 治理则发生在另一头的主线程上。
测量工具分两类:实验室数据(Lighthouse、WebPageTest,可复现,适合定位)与真实用户数据(CrUX、RUM,反映真实设备与网络)。PageSpeed Insights 两者都提供。优化决策以真实用户数据为准。
问题原因:性能瓶颈很少符合直觉。没测过基线就动手,优化的大概率不是真正的瓶颈,改完也无法证明有没有效果。
处理方法:
web-vitals 库在页面内上报指标。提升幅度:不直接产生提升,作用是把优化清单按预期收益排序。先看 TTFB(良好阈值 800 ms,web.dev)再看前端资源,避免服务端已经拖后腿时去抠前端字节。
注意事项:Lighthouse 单次跑分有波动,同一页面两次能差几分,结论基于多次运行取中位数;CrUX 有流量门槛,小站点可能查不到字段数据,只能靠实验室工具加本地节流。
问题原因:HTTP/1.1 同一 TCP 连接一次只能处理一个请求,浏览器靠每个域名开 6 个连接缓解队头阻塞,每个连接都要独立的 TCP 加 TLS 握手;TLS 1.2 握手需要 2 个 RTT。
处理方法:
http2 on; 指令开启。Alt-Svc 响应头让浏览器自动协商。提升幅度:TLS 1.3 每次握手省 1 个 RTT。HTTP/3 的收益集中在弱网:CloudPanel 实测 4G 加 15% 丢包环境下,页面加载比 HTTP/2 快约 55%;DebugBear 的结论是收益具有场景性,稳定高速网络下提升有限。
注意事项:QUIC 走 UDP,部分企业防火墙会拦截,必须保留 HTTP/2 兜底(浏览器按 Alt-Svc 自动降级);HTTP/2 多路复用后,HTTP/1.1 时代的域名分片、雪碧图合并变成负优化,见踩坑一节。
问题原因:HTML/CSS/JS 是高度冗余的文本。不压缩时,一个 300 KB 的 JS 文件在 10 Mbps 带宽下纯传输就要 240 ms,还没算握手与排队。
处理方法:
ngx_brotli 模块),压缩率更高,主流浏览器全支持。.br/.gz 文件(brotli_static/gzip_static),可以用最高压缩级别;动态内容实时压缩只敢用中低级别,此时 zstd 的速度优势明显。提升幅度:gzip 通常压掉文本资产体积的 60-75%(Blazing CDN 数据);Brotli 最高级别再比 gzip 小 15-25%(Paul Calvano 测试)。压缩速度决定实时压缩的选型:zstd 约 510 MB/s、Brotli 约 290 MB/s、gzip 约 105 MB/s(OpenResty 数据)。
注意事项:gzip_comp_level 超过 6 后体积收益递减、CPU 线性上涨;图片、视频、woff2 这类已压缩格式不要二次压缩,纯烧 CPU;CDN 场景必须回带 Vary: Accept-Encoding,否则可能把压缩版发给不支持解压的客户端。
问题原因:默认行为下浏览器每次都问服务器「变了没」。回访用户重复下载没改过的 JS/CSS,每个未命中都是一次完整往返。
处理方法:资源分两类,各给一种策略:
app.3f8a9c.js):Cache-Control: public, max-age=31536000, immutable,一年内浏览器不发任何验证请求。Vite、webpack、Astro 等构建工具默认输出带 hash 的文件名,指纹机制是现成的。Cache-Control: no-cache 加 ETag 协商,内容没变返回 304(几百字节的响应头)。提升幅度:命中强缓存的请求耗时趋近于 0。指纹化模式下,回访页面只剩 HTML 一个必发请求,其余静态资源全部来自本地;HTML 协商未变时,全量响应退化为 304。
注意事项:长 max-age 只能配指纹化文件名,否则用户拿不到更新;Nginx 的 ETag由 mtime 和文件大小算出,多机部署时 mtime 不一致会让同一文件算出不同 ETag,命中率反常下降;HTTP Archive 统计里,使用 immutable 的站点绝大多数搭配 365 天以上的 max-age,属于成熟模式。
问题原因:光速是硬约束。用户到源站的物理距离直接加进 TTFB:跨国 RTT 轻松超过 200 ms,页面几十个请求每个都要走完全程;源站带宽有限,静态下载还会挤占动态接口。
处理方法:图片、JS、CSS、字体这些静态资源接入 CDN,DNS 指向边缘节点;配置回源策略,边缘未命中才回源,开启中间层缓存(如 Origin Shield)降低回源量;缓存头沿用上一节的方案,CDN 会尊重 Cache-Control。
提升幅度:真实案例中改善区间常见 20-50%:Cloudflare 的 Pacsun 案例接入后整体快 27%。源站越远、用户分布越广,收益越大。
注意事项:CDN 的价值取决于缓存命中率,缓存键(query string、header)配置不当会把命中率打到很低,性能反而不如直连源站;HTML 一般不进 CDN 长缓存,用短 TTL 或纯回源;静态资源用独立域名可以甩掉 Cookie,但 HTTP/2 下不要为分片拆太多域名。
问题原因:默认配置按通用场景偏保守。静态文件默认经过用户态缓冲,多两次内存拷贝;短连接每个请求重新握手;TLS 参数保守时握手慢、加密套件低效。
处理方法:
sendfile on; # 内核态直接发文件,省掉用户态拷贝tcp_nopush on; # 配合 sendfile,攒满一个包再发,适合静态文件tcp_nodelay on; # 禁用 Nagle,keepalive 连接上的小包立即发keepalive_timeout 65; # 连接复用,减少重复握手ssl_session_cache 复用会话;OCSP Stapling 由服务器预取证书状态随握手下发,客户端不用再单独问 CA。提升幅度:sendfile 减少内存拷贝与上下文切换,静态大文件吞吐提升明显;keepalive 让同连接的后续请求免掉整个 TCP 加 TLS 握手;OCSP Stapling 为客户端省一次对 CA 的查询(百毫秒级)。这几项属于消除明显浪费,具体提升幅度取决于基线有多差。
注意事项:worker_processes auto 按核数配齐;改一项测一项,Nginx 配置错误直接 502;BBR 需要内核 4.9+,改内核参数前先确认宿主机支持。
问题原因:浏览器的默认发现机制有结构性延迟:CSS 阻塞渲染,<script> 默认阻塞解析;CSS 里引用的字体要等 CSS 下载解析完才发起请求;跨域关键资源要现做 DNS、TCP、TLS。
处理方法:
defer(不阻塞解析,DOM 建好后按序执行),独立的第三方脚本用 async。preconnect:提前完成 DNS 加 TCP 加 TLS,只给确定马上要用的关键源(字体、CDN),字体必须带 crossorigin。preload:当前页高优先级资源(LCP 图、关键字体),必须写 as 属性。prefetch:下一跳页面的资源,空闲期低优先级拉取。fetchpriority:修正浏览器自己排错的优先级,典型用法是给 LCP 图标 high。提升幅度:preconnect 省掉 1-3 个 RTT(按网络质量约 50-300 ms);preload 字体消除「CSS 下载、发现字体、再下载字体」的串行瀑布;defer 消除脚本对解析的阻塞。
注意事项:资源提示过用会反噬,preload 所有资源等于没有优先级,preconnect 控制在 4 个以内;prefetch 的资源可能在使用前被逐出,只适合大概率发生的导航。
问题原因:图片通常是页面字节的大头。没约束的原图、桌面端大图塞进手机、缺少尺寸属性引起的布局偏移,是三类最常见的浪费。
处理方法:
<picture> 里 AVIF 打头、WebP 兜底、JPEG 收尾。srcset 加 sizes,按视口宽度给不同分辨率。width/height 属性写死,浏览器提前算出宽高比,同时收获 CLS 收益。fetchpriority="high",绝不懒加载;折叠以下图片一律 loading="lazy"。提升幅度:WebP 比 JPEG 小 25-35%,AVIF 约 50%(Google 数据,web.dev)。格式升级加响应式,是单点收益最大的一类优化。
注意事项:懒加载 LCP 图会直接推迟 LCP,首屏图务必排除;AVIF 编码慢,构建时间会变长;压缩质量过低时,伪影先出现在纯色渐变和文字边缘。
问题原因:字体请求排在 CSS 之后,天然串行;字体未就绪时浏览器要么空白(FOIT)要么替换闪烁(FOUT);中文字体全量动辄数 MB。
处理方法:
preload,带 crossorigin。font-display: swap,先用回退字体渲染,字体就绪后替换。size-adjust/ascent-override/line-gap-override 把回退字体度量对齐到 webfont,可压到接近零 CLS。unicode-range 分片按需下载;中文场景做子集化,只保留实际用到的字符。提升幅度:WOFF2 相比 TTF 减约 60%、相比 WOFF 减约 30%;preload 消除字体请求的串行等待;中文子集化通常是数量级级别的缩减。
注意事项:swap 必然有一次替换闪烁,正文可接受,强视觉的品牌标题慎用;子集化要覆盖动态内容的字符集,后到的字符会回退渲染;字体自托管比第三方托管省一次跨域连接。
问题原因:下载只是 JS 成本的开始,解析、编译、执行都占主线程,而主线程同一时刻只能跑一个任务。超过 50 ms 的任务定义为长任务(web.dev),长任务期间页面无法响应交互,这是 INP 不达标的首要原因。
处理方法:
import() 非关键模块。defer,或延迟到首次交互后加载。scheduler.yield()(或退化为 setTimeout)把长任务切成小块让出主线程;纯计算挪进 Web Worker。提升幅度:收益定性但方向明确:发送的字节越少,解析编译时间越短;长任务拆开后交互不再排队,目标是把 INP 压进 200 ms。
注意事项:分割过细会产生大量碎片请求,按路由或交互边界拆;Web Worker 通信有序列化成本,不适合高频小任务;INP 要在真实设备上测,桌面开发机的性能会掩盖问题。
把前面几节落到一份 Nginx 配置(服务端部分):
http { sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65;
gzip on; gzip_comp_level 6; gzip_types text/css application/javascript application/json image/svg+xml text/plain application/xml; gzip_static on; # 优先发送构建期预压缩的 .gz # brotli on; # 安装 ngx_brotli 后启用 # brotli_comp_level 6; # brotli_static on;}
server { listen 443 ssl; http2 on; # Nginx 1.25.1+ 新语法 listen 443 quic reuseport; # HTTP/3,Nginx 1.25+ add_header Alt-Svc 'h3=":443"; ma=86400' always;
ssl_protocols TLSv1.3 TLSv1.2; ssl_session_cache shared:SSL:10m; ssl_stapling on; ssl_stapling_verify on;
# 指纹化静态资源:一年强缓存 location ~* \.(js|css|woff2|png|jpe?g|webp|avif|svg|ico)$ { add_header Cache-Control "public, max-age=31536000, immutable"; }
# HTML:协商缓存 location / { add_header Cache-Control "no-cache"; try_files $uri $uri/index.html =404; }}注意 add_header 不继承:location 里只要出现一个 add_header,server 级的全部失效(见踩坑五),需要把 Alt-Svc 在各 location 内重复声明。
HTML 侧的资源提示与图片标记:
<head> <!-- 关键第三方源提前建连 --> <link rel="preconnect" href="https://cdn.example.com" crossorigin> <!-- 关键字体提前加载,打断 CSS 到字体的串行瀑布 --> <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin> <!-- LCP 主图提前加载并提高优先级 --> <link rel="preload" as="image" href="/images/hero.avif" fetchpriority="high"> <script defer src="/app.js"></script></head>
<body> <!-- LCP 图:高优先级、绝不懒加载、带尺寸防布局偏移 --> <img src="/images/hero.avif" width="1200" height="600" fetchpriority="high" alt=""> <!-- 折叠以下:懒加载 --> <img src="/images/inline.webp" width="800" height="450" loading="lazy" alt=""></body>域名分片、雪碧图、内联小文件是 6 连接时代的产物。HTTP/2 单连接多路复用后,分片破坏优先级调度,反而变慢。升级协议后要清理这批遗留手段。
实验室分不等于真实体验:跑分环境是快速桌面网络,真实用户在移动弱网。搜索排名用的是 CrUX 字段数据。正确的分工是实验室定位问题、字段数据验收效果。
五项一起上,指标变好说不清是谁的功劳,变差也拆不出谁拖累。性能优化按单变量迭代,每项改动前后对比同一指标、同一环境。
immutable 加指纹文件名的模式只对静态资源成立。HTML 引用的是文件名,文件名不变时用户会长期拿到旧页面。HTML 必须保持 no-cache 协商。
location 中只要出现一个 add_header,server 级的所有 add_header 对该 location 失效。上面配置里 Cache-Control 与 Alt-Svc 同用就会踩到,需要在 location 内重复声明。
| 方法 | 典型提升幅度 | 成本与风险 |
|---|---|---|
| 性能分析 | 不直接提升,负责排序优化清单 | 只有时间成本 |
| HTTP/2 + TLS 1.3 | 每次握手省 1 RTT | 需全站 HTTPS |
| HTTP/3 | 弱网页面加载约 -55%(CloudPanel 实测),快网有限 | UDP 可能被拦截,需 HTTP/2 兜底 |
| 传输压缩 | gzip 压掉 60-75%;Brotli 再省 15-25% | 实时压缩耗 CPU,静态资源应预压缩 |
| HTTP 缓存 | 回访只剩 HTML 一个必发请求 | 必须配指纹化文件名 |
| CDN | 典型 -20% 到 -50%(案例 -27%) | 命中率配置不当反而变慢 |
| Nginx 与内核调优 | 消除拷贝与重复握手,幅度取决于基线 | 配置错误直接不可用 |
| 资源提示 | preconnect 省 1-3 RTT | 过用反噬优先级 |
| 图片优化 | WebP -25% 到 -35%,AVIF 约 -50% | AVIF 编码慢 |
| 字体优化 | WOFF2 比 TTF 约 -60%,子集化数量级缩减 | swap 有一次替换闪烁 |
| JS 长任务治理 | 目标 INP ≤ 200 ms | 分割过细则请求碎片化 |
顺序比方法重要:先测量建立基线,再按「字节、往返、距离、主线程」四个变量逐项处理。对多数站点,收益最大的三件事是缓存策略、图片格式升级和传输压缩;协议升级与 CDN 是一次性投入、长期受益的基础设施项。手段会过时,FID 已被 INP 取代,HTTP/3 正在普及,但「先测量、单变量迭代、用真实用户数据验收」的流程不会变。最后一条经验:性能优化最贵的不是漏掉某个技巧,而是把时间花在不是瓶颈的环节上。
如果这篇文章对你有帮助,欢迎点赞收藏。有问题欢迎评论区交流。