页面加载速度优化:10个让首屏时间从3秒降到0.8秒的技巧
速度就是金钱
亚马逊做过实验,页面加载每慢100ms,销售额下降1%。沃尔玛的数据更夸张,速度提升1秒,转化率提升2%。
去年给一个内容站做优化,首屏时间从3.2秒降到0.8秒,三个月后广告收入涨了47%。同样的流量,速度一快,用户看更多页面,广告曝光自然多。
先测量再优化
瞎优化等于浪费时间。Lighthouse和PageSpeed Insights是基础工具,但真实用户数据更重要。
// 采集真实用户的性能数据
const perfData = {
lcp: 0,
fid: 0,
cls: 0,
ttfb: 0
};
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
perfData.lcp = entries[entries.length - 1].startTime;
navigator.sendBeacon('/analytics/perf', JSON.stringify(perfData));
}).observe({type: 'largest-contentful-paint', buffered: true});
把数据上报到服务端,看分布而不是看平均值。P75的LCP才是Google排名用的数据。
优化手段排序
按投入产出比排序:
| 优化项 | 难度 | 效果 | 投入时间 |
|---|---|---|---|
| CDN加速 | 低 | 显著 | 2小时 |
| 图片压缩 | 低 | 显著 | 1小时 |
| Gzip/Brotli压缩 | 低 | 中等 | 30分钟 |
| 代码分割 | 中 | 显著 | 半天 |
| 关键CSS内联 | 中 | 中等 | 半天 |
| 预加载关键资源 | 低 | 中等 | 1小时 |
| 字体优化 | 低 | 中等 | 1小时 |
| 移除第三方脚本 | 中 | 显著 | 视情况 |
| 服务端渲染 | 高 | 显著 | 1周起 |
| HTTP/2或HTTP/3 | 低 | 中等 | 1小时 |
图片是头号杀手
网页体积60-80%是图片。优化图片收益最大。
# Brotli压缩(比Gzip小15-25%)
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/javascript application/json image/svg+xml;
图片格式优先级:AVIF > WebP > JPG/PNG。同一张图,AVIF比JPG小50%,WebP比JPG小30%。
<picture>
<source type="image/avif" srcset="photo.avif">
<source type="image/webp" srcset="photo.webp">
<img src="photo.jpg" alt="描述" loading="lazy" width="800" height="600">
</picture>
首屏图片用 loading="eager" + fetchpriority="high",非首屏全用懒加载。
关键渲染路径优化
浏览器渲染页面的流程:解析HTML → 构建DOM → 解析CSS → 构建CSSOM → 合并成Render Tree → 布局 → 绘制。
CSS是阻塞渲染的,JS是阻塞DOM解析的。优化思路:CSS尽快加载,JS尽量延迟。
<head>
<!-- 关键CSS内联,省一次请求 -->
<style>
body { margin: 0; }
.hero { height: 60vh; background: #f0f0f0; }
</style>
<!-- 非关键CSS异步加载 -->
<link rel="preload" href="/css/full.css" as="style" onload="this.rel='stylesheet'">
<!-- JS用defer或async -->
<script src="/js/app.js" defer></script>
<script src="/js/analytics.js" async></script>
</head>
defer 等HTML解析完才执行JS,按顺序执行。async 下载完立即执行,不保证顺序。统计代码用async,业务代码用defer。
字体加载策略
字体文件默认是阻塞渲染的,浏览器等字体下载完才显示文字,白屏时间增加。
@font-face {
font-family: 'Custom';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap;
unicode-range: U+0000-00FF;
}
unicode-range 只加载页面用到的字符的字体子集,中文字体能从2MB降到100KB。
<!-- 预加载字体 -->
<link rel="preload" href="/fonts/custom.woff2" as="font" type="font/woff2" crossorigin>
第三方脚本是性能黑洞
某站点接入7个第三方脚本:Google Analytics、百度统计、客服SDK、Facebook Pixel、热力图、A/B测试、广告SDK。FID直接从80ms飙到450ms。
<!-- 延迟加载非关键第三方脚本 -->
<script>
window.addEventListener('load', function() {
setTimeout(() => {
const s = document.createElement('script');
s.src = 'https://third-party.com/sdk.js';
document.body.appendChild(s);
}, 3000);
});
</script>
统计代码用Server-side方案,不在客户端加载SDK。客服插件延迟3秒加载,用户停留3秒以上才加载。
HTTP/2和HTTP/3
HTTP/1.1时代浏览器并发连接数限制6个,资源多了排队等待。HTTP/2多路复用,一个连接并发处理多个请求。
listen 443 ssl http2;
HTTP/3基于QUIC协议,握手更快,丢包恢复更好。Nginx 1.25+支持:
listen 443 quic reuseport;
资源预加载
<!-- DNS预解析 -->
<link rel="dns-prefetch" href="//cdn.example.com">
<!-- 预连接(DNS+TCP+SSL) -->
<link rel="preconnect" href="https://cdn.example.com">
<!-- 预加载关键资源 -->
<link rel="preload" as="font" href="/fonts/main.woff2" crossorigin>
<link rel="preload" as="image" href="/hero.webp">
<link rel="preload" as="script" href="/js/critical.js">
预连接省掉DNS查询和SSL握手,能省100-300ms。预加载让关键资源提前进队。
缓存策略
# 静态资源文件名带hash,长缓存
location ~* \.([a-f0-9]{8,})\.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML不缓存
location ~ \.html$ {
add_header Cache-Control "no-cache";
}
Service Worker能做更精细的缓存控制,离线访问都不是问题。
数据库查询优化
后端慢查询也会拖慢首屏。一个列表页查询从800ms优化到50ms,TTFB直接降700ms。
-- 加索引前:全表扫描800ms
SELECT * FROM articles WHERE category_id = 5 ORDER BY created_at DESC;
-- 加索引后:索引查询50ms
CREATE INDEX idx_category_created ON articles(category_id, created_at DESC);
优化效果数据
某内容站点优化前后对比:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 首屏时间 | 3.2s | 0.8s |
| TTFB | 850ms | 120ms |
| 页面体积 | 4.2MB | 1.1MB |
| JS体积 | 850KB | 180KB |
| 月广告收入 | 基准 | +47% |
每一步优化都有效果,叠加起来就是从3秒到0.8秒的飞跃。
行动建议
- Lighthouse跑一次,看具体建议
- 真实用户数据采集P75的LCP/FID/CLS
- 先做收益高的优化:CDN、图片、压缩
- 第三方脚本审计一遍,能删就删能延迟就延迟
- 持续监控,新功能上线看性能回归
- 建立性能预算,JS超过150KB不让上线