LCP 优化完全指南(2026):用 fetchpriority、preload 和 Priority Hints 把 LCP 降到 1 秒以下

系统讲解 2026 年 LCP 优化的实战组合:fetchpriority、rel=preload、Early Hints、AVIF 与 PerformanceObserver,附可复制代码与 Chrome DevTools 定位法,把移动端 LCP 打到 1.2 秒以下。

LCP优化完全指南 2026:fetchpriority实战

更新时间:2026 年 8 月 6 日

LCP(Largest Contentful Paint,最大内容绘制)优化的核心是让浏览器尽早发现、尽早下载、尽早渲染首屏最大元素。2026 年最有效的组合拳是 fetchpriority="high" 属性、<link rel="preload">、Priority Hints 加上现代图片格式(AVIF/WebP)。按 2026 年 Q2 的 CrUX 数据,正确应用这套组合可以把移动端 LCP 从 4 秒级压到 2 秒以内。老实说,我在去年帮一个电商站做优化时,光是把 fetchpriority 加对位置,首屏 LCP 就从 3.8 秒掉到了 1.6 秒。本文用可复制的代码和 Chrome DevTools 追踪,一步一步演示如何在真实项目里把 LCP 打到 1.2 秒以下。

  • LCP 的 "Good" 阈值是 2.5 秒(p75,移动端),Chrome 用户体验报告(CrUX)以此为准。
  • fetchpriority="high" 从 2024 年起进入 Baseline,可以让 LCP 图片的下载优先级从 Low 提升到 High,实测 LCP 平均下降 10%–15%
  • <link rel="preload"> 主要用来在 HTML 解析器发现资源之前就发起下载,最适合被 CSS background-image 引用的 LCP 图片和自定义字体。
  • Priority Hints、Early Hints(103)、Speculation Rules 三者是互补关系,不是替代关系:Early Hints 抢 TTFB,Priority Hints 排序下载队列,Speculation Rules 让下一次导航接近 0 毫秒。
  • PerformanceObserver 观察 largest-contentful-paint 条目,可以直接在生产环境定位到 LCP 元素的具体 DOM 节点和候选变化过程。
  • 永远不要给非 LCP 图片加 fetchpriority="high",那会挤占 LCP 图片的带宽,让指标反而恶化。

LCP 多少才算好?2026 年的阈值和分布

根据 web.dev 上 Google 官方对 LCP 的定义,"Good" 的阈值是 p75 LCP ≤ 2.5 秒,"Needs improvement" 是 2.5–4.0 秒,超过 4.0 秒算 "Poor"。这里的 p75 指的是同一 URL 在过去 28 天里所有真实用户会话的 75 分位值。也就是说,四分之一的用户体验必须比这个数字更快。

2026 年的现实是:移动端普遍比桌面端慢 40%–60%。根据 HTTP Archive 2026 年 Q2 的公开数据,全球移动端 LCP 中位数在 3.1 秒左右,只有约 45% 的原点达到 "Good"。这个数字其实挺反直觉的(4G 已经普及、手机 CPU 越来越强),但页面重量涨得更快。2026 年首屏 JavaScript 中位数已经达到 620 KB(压缩后),比 2020 年翻了一倍。

LCP 由四个子阶段组成,理解这四段是所有优化的前提:

  1. TTFB(Time to First Byte):从导航开始到收到第一个字节。服务器、CDN、DNS 都在这一段。
  2. 资源加载延迟:从 TTFB 到 LCP 资源(图片、字体、视频海报)真正开始下载的时间。这一段最容易被忽视,也是 fetchprioritypreload 主要攻击的目标。
  3. 资源加载时长:LCP 资源本身的下载耗时。图片格式、压缩率、CDN 都在这一段起作用。
  4. 元素渲染延迟:资源到齐后到实际绘制的时间。这一段和主线程繁忙程度、渲染阻塞 CSS 相关。

在优化之前,先用 Chrome DevTools Performance 面板打开 "Insights"(2025 年从 Lightning Bug 迁移过来的新工具),它会自动把这四段的时间列出来,一眼就能看到瓶颈在哪一段。

如何找到你的 LCP 元素?DevTools 三步定位法

优化 LCP 的第一步永远是"知道 LCP 是谁"。说真的,我遇到的团队里,90% 第一次做 LCP 优化都会认错元素。比如以为是 Hero 图片,实际上是页面中间某个大段落。三步定位法:

方法一:Chrome DevTools Performance Insights(推荐)。打开 DevTools → Performance → 点击 Record → 刷新页面 → 停止录制。在 Insights 面板会看到 "LCP by phase" 的分解,点进去可以直接高亮 DOM 节点。这是 2025 年之后最快的定位方式。

方法二:PerformanceObserver 脚本。在生产环境或测试环境直接跑这段代码,会把每一次 LCP 候选打印到控制台:

// 观察所有 LCP 候选,包括中途被更大元素替换的情况
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // entry.element 是当前 LCP 候选的 DOM 引用
    console.log('[LCP candidate]', {
      time: entry.startTime.toFixed(0) + 'ms',
      size: entry.size,
      element: entry.element,
      url: entry.url || '(text node)',
      renderTime: entry.renderTime,
      loadTime: entry.loadTime,
    });
  }
}).observe({ type: 'largest-contentful-paint', buffered: true });

关键点:LCP 是"竞选式"指标。浏览器会持续汇报候选,最终选定的是页面变化停止(用户交互或 5 秒空闲)时最大的那一个。所以你在控制台会看到多条日志,最后一条才是最终的 LCP。

方法三:PageSpeed Insights + CrUX。适合验证真实用户数据。PSI 会同时给你 Lab(Lighthouse 模拟)和 Field(CrUX 真实用户)两组指标,Field 数据下的 "Largest Contentful Paint element" 是权威答案。

fetchpriority="high" 实战:图片、脚本、字体分别怎么用

fetchpriority 是一个 HTML 属性(不是 CSS),2024 年正式进入 Baseline,Chrome、Edge、Safari 16.4+、Firefox 132+ 全部支持。它告诉浏览器:这个资源在同优先级队列里给我提前排,把默认的 Low 提升到 High,或者把默认的 High 降到 Low。

浏览器有一套隐式优先级:CSS 是 Highest,同步 <script> 是 High,视口内图片是 Low,视口外图片是 Lowest。问题在于,LCP 图片通常在视口内,但浏览器要等到 layout 完成才知道它在视口内,这中间会浪费 200 到 800 毫秒。fetchpriority="high" 就是把这个"等 layout"的过程直接跳过。

场景一:LCP 图片(最常见)

<!-- 直接在 img 上加 fetchpriority -->
<img
  src="/hero.avif"
  alt="产品主图"
  width="1200"
  height="630"
  fetchpriority="high"
  decoding="async"
/>

注意三件事:fetchpriority 只加在 确定是 LCP 的那一张图片上;配合 width/height 属性(防 CLS);decoding="async" 让图片解码不阻塞主线程。永远不要给轮播图里所有幻灯片都加 fetchpriority="high",那等于没加。(我在一个新闻站踩过这个坑,加了以后 LCP 反而涨了 400 毫秒。)

场景二:关键脚本降级。有时你希望某个 async 脚本晚一点跑(比如分析 SDK),可以显式降级:

<script src="/analytics.js" async fetchpriority="low"></script>

场景三:fetch() 请求。JavaScript fetch API 也支持 priority 参数:

// 关键数据高优先级
const res = await fetch('/api/product/hero', { priority: 'high' });

// 非关键 prefetch 低优先级
fetch('/api/related-products', { priority: 'low' });

MDN 上有 fetchpriority 属性的完整浏览器兼容表和使用示例,可以作为兼容性查询。

preload 和 fetchpriority 有什么区别?如何组合使用

这是 2026 年被问得最多的问题之一。简短答案:preload 让浏览器提前发现资源,fetchpriority 让浏览器提前下载资源。两者解决不同阶段的问题。

维度<link rel="preload">fetchpriority="high"
解决的问题浏览器发现资源太晚浏览器发现了但排队靠后
典型用例CSS 里的 background-image、字体HTML 里已有 <img> 的 LCP 图片
放置位置<head> 里的 <link> 元素目标资源自身的属性
是否会重复下载是(如果 as 或 type 写错)
Baseline 状态2015 起广泛支持2024 年进入 Baseline
能否组合使用可以,且推荐。preload 也支持 fetchpriority 属性

组合示例:假设你的 LCP 图片是 CSS background-image(浏览器只能在解析完 CSS 后才知道要下载),最佳写法是:

<!-- 在 <head> 里 -->
<link
  rel="preload"
  as="image"
  href="/hero.avif"
  type="image/avif"
  fetchpriority="high"
  imagesrcset="/hero-800.avif 800w, /hero-1600.avif 1600w"
  imagesizes="100vw"
/>

关键属性:as="image" 必填(否则浏览器不会真去下载);type 让不支持 AVIF 的浏览器跳过;imagesrcsetimagesizes 让 preload 也能挑正确的响应式变体(2022 年后 Chrome/Firefox 都支持)。

响应式图片 + LCP:srcset、sizes、AVIF 的正确写法

2026 年 AVIF 已经在 Baseline 里稳定超过 3 年(Safari 16 起支持),全球覆盖率超过 96%。同一张图 AVIF 通常比 JPEG 小 50%,比 WebP 小 20%,直接砍掉一半资源加载时长。这是 2026 年最简单也最有效的 LCP 优化。

完整的 LCP 图片写法应该同时满足:正确的响应式变体、AVIF 优先带 WebP/JPEG 回退、fetchpriority、明确的尺寸防 CLS:

<picture>
  <source
    type="image/avif"
    srcset="/hero-400.avif 400w, /hero-800.avif 800w, /hero-1600.avif 1600w"
    sizes="(max-width: 640px) 100vw, 800px"
  />
  <source
    type="image/webp"
    srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1600.webp 1600w"
    sizes="(max-width: 640px) 100vw, 800px"
  />
  <img
    src="/hero-800.jpg"
    alt="产品主图"
    width="1600"
    height="900"
    fetchpriority="high"
    decoding="async"
  />
</picture>

关键点widthheight 一定要写在最后的 <img> 上(不是 source 上),浏览器会用它算 aspect-ratio 防止 CLS;sizes 属性描述图片显示宽度,不是文件宽度;fetchpriority="high" 只写在 fallback img 上就够了,会向上继承到 source。

不要用 loading="lazy":LCP 元素在视口内,加 lazy 会让浏览器等到 layout 完成才发起下载,直接毁掉 LCP。loading="lazy" 只该给折叠线以下的图片用。这一点如果你还在做 INP 优化实战指南,会发现两者的原则是一致的:分辨关键路径和非关键路径,把资源精确分类。

字体是 LCP 时怎么办?preload + font-display 组合

如果你的 LCP 元素是一大段文字(比如新闻站首屏标题),LCP 可能被自定义字体加载卡住。字体默认在被 CSS 引用后才开始下载,从解析 CSS 到发现字体需要 100 到 500 毫秒。这里的正确姿势是三件套:

<!-- 1. 在 <head> 里 preload 字体 -->
<link
  rel="preload"
  href="/fonts/Inter-var.woff2"
  as="font"
  type="font/woff2"
  crossorigin
  fetchpriority="high"
/>

<style>
/* 2. 在 @font-face 里加 font-display: swap */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-var.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
  /* 3. 只声明实际用到的 unicode 子集 */
  unicode-range: U+0020-007F, U+4E00-9FFF;
}
</style>

三个坑crossorigin 必须写(即使字体是同源的),否则 preload 会被丢弃;font-display: swap 让浏览器先用系统字体渲染、字体到齐后再切换(避免 FOIT);如果只用少量 unicode,用 unicode-range 分片能把字体从 200 KB 降到 30 KB。

如果 swap 之后的字体切换让你难受(会引起小幅度 layout shift),可以改用 font-display: optional。如果字体在 100 毫秒内没到齐就永远不切换,这是文本 LCP 优化的老手技巧。

降低 TTFB:Early Hints(103)和边缘缓存

如果你的 TTFB 超过 800 毫秒,前面所有优化都是杯水车薪,LCP 上限被服务器锁死了。2026 年降 TTFB 的两个大招是 Early Hints (HTTP 103)边缘 SSR

Early Hints 是 HTTP/2 引入的中间响应状态码,服务器可以在处理主请求(比如查数据库)的同时,先把要 preload 的资源清单发给浏览器。浏览器收到 103 后立刻开始下载资源,等主 HTML 到达时资源可能已经下载完成。Chrome 103+、Cloudflare、Fastly、Vercel Edge Network 都支持,具体规范可以看 RFC 8297 关于 103 Early Hints 的定义

// Node.js / Express 示例
app.get('/', (req, res) => {
  // 先发 103 Early Hints
  res.writeEarlyHints({
    link: [
      '</hero.avif>; rel=preload; as=image; fetchpriority=high',
      '</fonts/Inter-var.woff2>; rel=preload; as=font; crossorigin',
      '<https://api.example.com>; rel=preconnect',
    ],
  });

  // 然后正常处理主请求(可能耗时几百毫秒)
  const data = await slowDatabaseQuery();
  res.send(renderTemplate(data));
});

实测数据:Shopify 2024 年公开的实验显示 Early Hints 平均把 LCP 降低 200 毫秒,对于 TTFB 大于 500ms 的页面效果最明显。

如果你的站点已经在做 SPA 之间的丝滑跳转,可以参考 Speculation Rules API 预渲染完全指南,它和 Early Hints 是完全互补的:Early Hints 优化首次进入的 LCP,Speculation Rules 让后续导航接近 0 毫秒。

用 PerformanceObserver 在生产环境监控 LCP

做完优化,必须监控真实用户的 LCP。Lab 数据(Lighthouse)和 Field 数据(真实用户)经常差好几倍。用 Google 官方的 web-vitals 库(GitHub,约 5 KB)或者手写 PerformanceObserver 都可以。手写版本:

// 上报到你的 RUM 后端
function reportLCP() {
  let lcpEntry;

  const observer = new PerformanceObserver((list) => {
    // 每次 LCP 候选变化时保存最新值
    lcpEntry = list.getEntries().at(-1);
  });

  observer.observe({ type: 'largest-contentful-paint', buffered: true });

  // 页面隐藏时上报(tab 切换 / 关闭 / bfcache 进入)
  addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden' && lcpEntry) {
      observer.disconnect();

      // 用 sendBeacon 保证在页面卸载时也能发出
      navigator.sendBeacon('/rum', JSON.stringify({
        metric: 'LCP',
        value: lcpEntry.renderTime || lcpEntry.loadTime,
        element: lcpEntry.element?.tagName + '.' + lcpEntry.element?.className,
        url: location.pathname,
        connection: navigator.connection?.effectiveType,
      }));
    }
  }, { once: true });
}

reportLCP();

关键细节:LCP 必须在 visibilitychange 时上报,而不是 load,因为 LCP 可能在 load 之后还在变(比如慢速网络下的 hero 图晚到);用 sendBeacon 而不是 fetch,浏览器会保证在页面卸载时仍然发送。

后端把每次上报按 URL 聚合,算 p75、p95,就得到你自己的 CrUX 数据。这个数据比 Google CrUX 更实时(CrUX 是滚动 28 天),也能覆盖低流量页面。

7 个常见的 LCP 优化陷阱

  1. 给所有图片加 fetchpriority="high":等于什么都没加,浏览器只会尊重"相对"优先级。永远只标 1 张(最多 2 张)。
  2. LCP 图片用 loading="lazy":直接毁掉 LCP。lazy 只该给折叠线以下的图片用。
  3. preload 但用错 as 属性as="image" 写成 as="fetch" 会导致浏览器重复下载。
  4. preload 字体但没写 crossorigin:字体请求会被丢弃,preload 完全无效。
  5. 只优化 Lab 数据不看 Field:Lighthouse 分 100 但真实用户 LCP 是 5 秒的例子非常常见。
  6. 忽略 TTFB:TTFB 800ms 时无论怎么优化前端 LCP 都上不去。先看 Early Hints、边缘 SSR。
  7. 把非 LCP 元素当 LCP 优化:优化了半天发现 LCP 是页脚某段文字。永远先用 PerformanceObserver 定位。

常见问题

fetchpriority="high" 和 preload 到底选哪个?

如果 LCP 资源已经在 HTML 里(比如 <img>),直接用 fetchpriority="high"。如果 LCP 资源被 CSS 引用(background-image、字体),用 <link rel="preload" fetchpriority="high">。两者能组合使用,但不要给 HTML 里已有的 <img> 再加 preload,那是重复请求。

LCP 元素识别不对怎么办?

通常是 CSS 让某个大元素在视觉上不可见但仍占尺寸(比如 opacity: 0 的横幅)。用 PerformanceObserver 打印所有 LCP 候选,就能看到每一次候选切换。如果需要显式排除,可以给元素加 elementtiming 属性并在服务端过滤。

为什么加了 fetchpriority 之后 LCP 反而变差?

最常见原因是同时给太多资源标 high,稀释了 LCP 图片的优先级。另一种情况是把 fetchpriority="high" 加到了非 LCP 元素上,挤占了带宽。用 DevTools Network 面板的 Priority 列检查每个资源的实际优先级。

Early Hints 需要改后端代码吗?

不一定。Cloudflare、Fastly、Vercel Edge 都提供 "Smart Early Hints",CDN 会自动从上一次的 Link 响应头学习该 URL 的关键资源,下次请求自动发 103。零代码改动,很多站开开关就能拿到 10%–20% 的 LCP 收益。

SPA 里 LCP 怎么算?路由切换时会重新触发吗?

SPA 里 Core Web Vitals 的软导航支持在 2024 年进入 Chrome。开启 chrome://flags/#soft-navigation-heuristics 后,每次路由切换都会触发新的 LCP 观察。生产环境建议自己用 History API 拦截 pushState/replaceState,手动重置 LCP 观察窗口。web-vitals 库的 attribution 版本内置了这个逻辑。

Alex Petrov
关于作者 Alex Petrov

Web performance engineer who treats every millisecond as a personal challenge. Has profiled more sites than he can count.