暮色中的山脊:本页首屏的 LCP 元素
同一张图 · 两种命运 · 数据实时取自本页构建产物

漂亮的 AI 网站,
凭什么也能秒开

「AI 做的页面越漂亮,加载就一定越慢」——不一定。慢的通常不是服务器,而是 首屏那张图太大 ,和 它被发现得太晚 。 这个站把两件事拆开给你看:本页是优化版,同款慢版 在另一个路由, 两边右下角都有实时 LCP 探针,用手机点开对比最直观。

21KB手机档首屏图(AVIF 960w),原图 3.70MB
+0ms图片发现时机:由 preload 在 HTML 解析时触发
0阻塞渲染的第三方脚本;动画全走 CSS
诊断

「慢」其实是三个不同的问题

只压缩图片,往往治不好另一个。先定位是哪一类,再决定改图、改代码还是改数据加载。

01 / BYTES

图本身太大

一张 3840×2160 的 JPEG 直出就有 3.70MB。手机视口只有 390px 宽, 传过去 99% 的像素都白下了。按展示宽度切档、换 AVIF/WebP,体积能掉一到两个数量级。

02 / DISCOVERY

图发现得太晚

浏览器拿到 HTML 后还得先解析 CSS、等 JS 挂载,才轮到首屏图发出请求。 这段时间跟图多大无关——所以要 preload,把请求提前到解析 HTML 的那一刻。

03 / MAIN THREAD

主线程被占住

图已经下载完,页面却迟迟不画:大体积客户端 JS、阻塞的第三方脚本、首帧才启动的动画, 都会把绘制一推再推。这类要靠 bundle 分析,压图压不出效果。

对照

同一套视觉,两种实现的账本

左边是本页;右边是 /slow/,故意复刻「图片又大、发现又晚、主线程还忙」的现场。 两边都装了 LCP 探针,打开后看右下角数字。

注:本站托管在 Cloudflare Pages,境内直连的首包(TTFB)本身有 0.5–1.5s 底噪。 那一段属于「线路选择」而不是「页面实现」,所以对比时要看两个页面的差值, 尤其看探针里「该图请求启动于 +Xms」这一行——它衡量的是发现时机,与线路无关。

/ 优化版

手机档首屏图(AVIF 960w)21KB
手机档兜底(WebP 960w)60KB
桌面档(AVIF 2400w)117KB
请求启动HTML 解析即触发(preload)
主线程阻塞无同步长任务
CLS0(图片带宽高比占位)

/slow/ 故意做慢的对照

首屏图3.70MB JPEG
发现时机等 JS 挂载后 + 延迟注入
加载方式loading="lazy"(经典错法)
主线程额外 250ms 同步计算
首屏先看到骨架屏
做法

本页实际做了这四件事

代码就在仓库里,可以逐条对照。每条都只改一个主要因素,方便用同一台设备、同一网络重复测。

01

按展示宽度切档,换现代编码

用 sharp 在构建期生成 480 / 960 / 1600 / 2400 四档 AVIF + WebP + JPEG, 页面用 <picture> + srcset, 手机只会下 960 档(约 21KB)。

<picture>
  <source type="image/avif" srcSet={...} sizes="100vw" />
  <source type="image/webp" srcSet={...} sizes="100vw" />
  <img src="/img/hero-1600.jpg" srcSet={...} sizes="100vw"
       width={2400} height={1350} fetchPriority="high" />
</picture>
02

把请求提前:preload + fetchPriority

首屏图是 LCP 元素,就别让它等着被发现。HTML 里直接声明 preload, 并带上 imageSrcSet,浏览器解析到就会发请求。

<link rel="preload" as="image" type="image/avif"
      imageSrcSet="/img/hero-480.avif 480w, ... 2400w"
      imageSizes="100vw" fetchPriority="high" />
03

别让动画和第三方脚本挡住首帧

进场动画全用 CSS(animation-timeline: view()), 不进 JS 主线程;没有字体网络请求;没有第三方埋点。

@supports (animation-timeline: view()) {
  .reveal { animation: rise linear both; animation-timeline: view(); }
}
04

用生产构建量,而不是 dev 模式

npm run dev 的时序完全不代表线上。 先 npm run build 再 npm run start, 然后 Chrome → Performance → Record and reload,网络限速到 Slow 4G 再录。

npm run build && npm run start
# 或者直接量线上(必须走生产域名)
npx lighthouse https://你的域名/ --form-factor=mobile --throttling-method=simulate