Web 资源加载深度优化:图片、字体、视频、缓存与 CDN

系统性 Web 资源加载优化策略:图片格式选型(AVIF/WebP/JPEG XL)、响应式图片(srcset/sizes/picture)、懒加载(loading='lazy' + Intersection Observer)、字体加载策略(font-display/preload/subset)、视频与 iframe 懒加载、HTTP 缓存策略(Cache-Control/ETag/Last-Modified)、Service Worker 缓存、CDN 边缘加速与缓存规则、资源压缩(Brotli/Gzip)、HTTP/2 Server Push 替代方案(103 Early Hints/Preload)、关键渲染路径优化。

80% 的 Web 性能问题来自资源加载。 一张未优化的 4K 图片可能消耗比整个 JS Bundle 还多带宽;一次错误的缓存策略可能让用户每次访问都重新下载所有资源。本文覆盖从图片到缓存的全链路优化。


一、图片优化:最大收益点

1.1 现代图片格式对比

格式压缩率 vs JPEG浏览器支持适用场景
AVIF-50%~-70%Chrome 85+, Firefox 93+, Safari 16+最佳选择,优先使用
WebP-25%~-35%全现代浏览器AVIF 的 fallback
JPEG XL-50%~-60%Chrome 91+(实验性)未来潜力,暂不稳定
MozJPEG-10%~-20%全浏览器渐进式 JPEG
PNG全浏览器需要透明/无损
SVG全浏览器图标、Logo、简单图形

1.2 响应式图片:srcset + sizes

<!-- 根据视口宽度自动选择最佳尺寸 -->
<img
  srcset="
    /images/hero-400.avif   400w,
    /images/hero-800.avif   800w,
    /images/hero-1200.avif 1200w,
    /images/hero-1600.avif 1600w
  "
  sizes="
    (max-width: 640px) 100vw,
    (max-width: 1024px) 50vw,
    33vw
  "
  src="/images/hero-800.avif"
  alt="Hero image"
  width="1600"
  height="900"
  loading="lazy"
  decoding="async"
>

sizes 语法:告诉浏览器当前图片在不同媒体查询下的显示宽度,浏览器据此选择最佳 srcset 资源。

1.3 picture 标签:格式回退

<picture>
  <!-- 优先尝试 AVIF -->
  <source
    srcset="/images/photo.avif"
    type="image/avif"
  >
  <!-- 其次 WebP -->
  <source
    srcset="/images/photo.webp"
    type="image/webp"
  >
  <!-- 最后 JPEG -->
  <img
    src="/images/photo.jpg"
    alt="Photo"
    width="800"
    height="600"
    loading="lazy"
  >
</picture>

1.4 图片懒加载

<!-- 原生懒加载(现代浏览器支持 ~95%+) -->
<img src="photo.jpg" loading="lazy" alt="...">

<!-- 优先级控制 -->
<img src="hero.jpg" fetchpriority="high" alt="首屏图">
<img src="below-fold.jpg" fetchpriority="low" loading="lazy" alt="非首屏">
// Intersection Observer(更精细控制,如提前加载阈值)
const imgObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.classList.remove('lazy');
      observer.unobserve(img);
    }
  });
}, {
  rootMargin: '200px 0px', // 提前 200px 开始加载
  threshold: 0.01
});

document.querySelectorAll('img.lazy').forEach(img => imgObserver.observe(img));

1.5 图片优化工具链

# CLI 工具
npm install -D sharp imagemin-cli svgo

# sharp:批量转换 + 压缩
npx sharp input.jpg --avif --quality 75 --output output.avif
npx sharp input.jpg --webp --quality 80 --output output.webp

# 生成多尺寸
for w in 400 800 1200 1600; do
  npx sharp input.jpg --resize $w --avif --output "output-${w}.avif"
done

# SVG 优化
npx svgo icons/*.svg --pretty --multipass

二、字体加载优化

2.1 字体加载问题

FOIT(Flash of Invisible Text):字体下载前文字不可见
FOUT(Flash of Unstyled Text):先显示回退字体,加载后切换
FOIT 伤害感知性能,FOUT 伤害视觉稳定性(可能导致 CLS)

2.2 优化策略

<!-- 1. 预连接字体 CDN -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<!-- 2. 预加载关键字体(仅限首屏必须的 1-2 个) -->
<link rel="preload" href="/fonts/main-400.woff2" as="font" type="font/woff2" crossorigin>

<!-- 3. font-display 策略 -->
<style>
  @font-face {
    font-family: 'MyFont';
    src: url('/fonts/myfont.woff2') format('woff2');
    font-weight: 400;
    font-style: normal;
    /* 推荐:短暂阻塞(100ms)后显示回退字体,加载完成后切换 */
    font-display: swap;
  }
</style>

<!-- font-display 取值:
     auto     — 浏览器默认
     block    — 3s 隐形文字(FOIT)→ 回退
     swap     — 立即回退(FOUT)→ 加载后切换 ✅ 推荐
     fallback — 100ms 隐形 → 回退 → 3s 内未加载则保持回退
     optional — 100ms 隐形 → 回退 → 不切换(网络慢时)
-->

2.3 字体子集化(Subsetting)

# 用 glyphhanger 生成只包含使用到的字符的字体
npx glyphhanger https://example.com \
  --formats=woff2 \
  --subset=/fonts/original.ttf \
  --output=/fonts/subset/

# 或用 pyftsubset(fontTools)
pyftsubset font.ttf \
  --text="常用汉字列表..." \
  --output-file=font-subset.woff2 \
  --flavor=woff2

2.4 系统字体栈

/* 最快加载速度:不用网络字体 */
body {
  font-family:
    -apple-system, BlinkMacSystemFont,  /* macOS / iOS */
    'Segoe UI', Roboto,                  /* Windows / Android */
    'Helvetica Neue', Arial,
    'Noto Sans', 'PingFang SC',          /* 中文 */
    'Microsoft YaHei',                   /* Windows 中文 */
    sans-serif;
}

三、视频与 iframe 懒加载

3.1 视频优化

<!-- 1. 封面图占位 + 点击加载 -->
<div class="video-placeholder" data-video-id="abc123">
  <img src="video-poster.jpg" alt="Video thumbnail" loading="lazy">
  <button class="play-button"></button>
</div>

<!-- 2. 仅首屏视频 autoplay,其他懒加载 -->
<video
  poster="thumbnail.jpg"
  preload="none"           <!-- 不预加载 -->
  controls
  loading="lazy"
>
  <source src="video.mp4" type="video/mp4">
</video>

<!-- 3. 用 Lighthouse 推荐的 Video 压缩 -->
<!-- ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset fast -c:a aac -b:a 128k output.mp4 -->

3.2 iframe 懒加载

<!-- 原生支持 -->
<iframe src="map.html" loading="lazy" width="600" height="400"></iframe>

<!-- YouTube 嵌入优化:用 facades 模式 -->
<!-- 先显示封面图 + 播放按钮,点击后才加载 iframe -->
<div class="youtube-facade" data-video="dQw4w9WgXcQ">
  <img src="https://img.youtube.com/vi/dQw4w9WgXcQ/hqdefault.jpg" alt="...">
  <button class="play-btn"></button>
</div>

<script>
document.querySelectorAll('.youtube-facade').forEach(el => {
  el.addEventListener('click', () => {
    const id = el.dataset.video;
    el.innerHTML = `<iframe src="https://www.youtube.com/embed/${id}?autoplay=1" ...></iframe>`;
  });
});
</script>

四、HTTP 缓存策略

4.1 缓存机制全图

浏览器缓存层级:
┌─────────────────────────────────────────┐
│  1. Service Worker Cache(最优先)      │
│     ← 完全可控,可实现离线访问           │
├─────────────────────────────────────────┤
│  2. HTTP Cache(Disk / Memory)         │
│     ← Cache-Control / ETag / Last-Modified│
├─────────────────────────────────────────┤
│  3. 协商缓存(304 Not Modified)        │
│     ← ETag 对比                          │
├─────────────────────────────────────────┤
│  4. 直连服务器(无缓存)                │
└─────────────────────────────────────────┘

4.2 Cache-Control 指令

指令含义场景
max-age=31536000缓存 1 年版本化静态资源(JS/CSS/图片)
immutable内容永不改变fingerprint 文件名
no-cache必须重新验证动态 API 响应
no-store完全不缓存敏感数据
private仅浏览器缓存用户专属内容
publicCDN 可缓存通用静态资源
stale-while-revalidate=86400缓存过期后 1 天内先返回旧值再后台更新非实时数据

4.3 静态资源缓存配置

# Nginx 配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|avif|webp)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    add_header Vary "Accept-Encoding";
}

# 带 hash 的资源(如 main.a3f2b1c.js)→ 永久缓存
# 不带 hash 的 HTML → 不缓存或短时间缓存
location ~* \.html$ {
    expires 5m;
    add_header Cache-Control "public, must-revalidate";
}

4.4 ETag 与协商缓存

首次请求:
  客户端 → GET /api/data
  服务端 → 200 OK + ETag: "abc123" + body

后续请求:
  客户端 → GET /api/data + If-None-Match: "abc123"
  服务端 → 304 Not Modified(body 为空,省带宽)
         → 或 200 OK + 新 ETag(内容变了)

五、CDN 与边缘加速

5.1 CDN 缓存层级

用户请求 → 浏览器缓存 → CDN Edge(PoP)→ CDN Origin Shield → 源站
                    边缘节点遍布全球,就近响应
                    静态资源命中 Edge → 延迟 < 50ms

5.2 Cloudflare 缓存规则示例

规则 1:静态资源长期缓存
  匹配:URL Path contains ".js" OR ".css" OR ".avif" OR ".webp"
  操作:Cache Level = Cache Everything
       Edge TTL = 1 month
       Browser TTL = 1 year

规则 2:HTML 短期缓存
  匹配:URL Path contains ".html" OR Path = "/"
  操作:Cache Level = Cache Everything
       Edge TTL = 5 minutes
       Browser TTL = 5 minutes

规则 3:API 不缓存
  匹配:URL Path starts with "/api/"
  操作:Cache Level = Bypass

5.3 边缘渲染(Edge SSR)

// Vercel Edge Function:在 CDN 边缘运行,减少 TTFB
export const config = { runtime: 'edge' };

export default async function handler(request: Request) {
  const url = new URL(request.url);

  // 检查 CDN 缓存
  const cache = caches.default;
  const cached = await cache.match(request);
  if (cached) return cached;

  // 边缘渲染
  const html = await renderPage(url.pathname);
  const response = new Response(html, {
    headers: {
      'Content-Type': 'text/html',
      'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300'
    }
  });

  await cache.put(request, response.clone());
  return response;
}

六、资源压缩

6.1 Brotli vs Gzip

算法压缩率CPU 开销浏览器支持
Brotli(quality=11)最高(~20% better than gzip)高(压缩慢)~98%
Gzip中等~100%
# Nginx 配置(Brotli + Gzip fallback)
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;

brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/javascript application/json;

gzip on;
gzip_vary on;
gzip_types text/plain text/css application/javascript application/json;

6.2 构建时预压缩

# 用 Brotli 预压缩静态资源(减少服务器实时压缩开销)
for f in dist/**/*.{js,css,html,json,svg}; do
  brotli -q 11 -o "$f.br" "$f" &
  gzip -9 -k "$f" &
done
wait

七、关键渲染路径优化

7.1 资源优先级控制

<!-- 最高优先级:关键 CSS -->
<link rel="preload" href="/critical.css" as="style" onload="this.rel='stylesheet'">

<!-- 高优先级:首屏字体 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

<!-- 中优先级:首屏图片 -->
<link rel="preload" href="/hero.avif" as="image" type="image/avif">

<!-- 低优先级(或不需要预加载):非关键资源 -->

<!-- 103 Early Hints(HTTP/2+):在完整响应前发送预加载提示 -->
<!-- 服务端发送:
  HTTP/1.1 103 Early Hints
  Link: </critical.css>; rel=preload; as=style
  Link: </main.js>; rel=preload; as=script
-->

7.2 关键 CSS 内联

<head>
  <!-- 只内联首屏必须的 CSS(通常 < 14KB gzip) -->
  <style>
    /* critical.css 内容 */
    .hero { min-height: 100vh; display: flex; ... }
    .nav { position: fixed; top: 0; ... }
    /* 其他 CSS 异步加载 */
  </style>

  <!-- 非关键 CSS 异步加载 -->
  <link rel="preload" href="/non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="/non-critical.css"></noscript>
</head>

7.3 脚本加载策略

<!-- async:下载不阻塞,下载完立即执行 -->
<script src="analytics.js" async></script>

<!-- defer:下载不阻塞,DOM 解析完按序执行 ✅ 推荐 -->
<script src="app.js" defer></script>

<!-- module:自动 defer -->
<script type="module" src="app.js"></script>

<!-- type="speculationrules":预渲染下一页(Chrome 103+) -->
<script type="speculationrules">
{
  "prerender": [{
    "source": "list",
    "urls": ["/about", "/contact"]
  }]
}
</script>

八、资源优化 Checklist

检查项做法预期收益
图片格式AVIF > WebP > JPEG + responsive50-70% 体积减少
图片尺寸srcset + sizes 或 移动端省 60%+
懒加载loading=“lazy” + Intersection Observer减少初始请求
字体font-display: swap + subset消除 FOIT
缓存策略Cache-Control immutable(静态资源)重复访问 0 传输
压缩Brotli(构建时预压缩)25% 额外减少
CDN全球 Edge 节点 + 缓存规则TTFB < 100ms
关键渲染关键 CSS 内联 + preloadFCP 提升
脚本加载defer / async / type=“module”减少阻塞

参考与延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「frontend」更多文章

  1. API 设计与 BFF 层:REST、GraphQL、tRPC 选型与前后端协作
  2. WebAssembly 前端工程化实践:编译链、性能对比与混合架构
  3. 现代浏览器 API 与 Web 平台能力地图