页面加载速度优化: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不让上线