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

「AI 做的页面越漂亮,加载就一定越慢」——不一定。慢的通常不是服务器,而是 首屏那张图太大 ,和 它被发现得太晚 。 这个站把两件事拆开给你看:本页是优化版,同款慢版 在另一个路由, 两边右下角都有实时 LCP 探针,用手机点开对比最直观。
只压缩图片,往往治不好另一个。先定位是哪一类,再决定改图、改代码还是改数据加载。
一张 3840×2160 的 JPEG 直出就有 3.70MB。手机视口只有 390px 宽, 传过去 99% 的像素都白下了。按展示宽度切档、换 AVIF/WebP,体积能掉一到两个数量级。
浏览器拿到 HTML 后还得先解析 CSS、等 JS 挂载,才轮到首屏图发出请求。 这段时间跟图多大无关——所以要 preload,把请求提前到解析 HTML 的那一刻。
图已经下载完,页面却迟迟不画:大体积客户端 JS、阻塞的第三方脚本、首帧才启动的动画, 都会把绘制一推再推。这类要靠 bundle 分析,压图压不出效果。
左边是本页;右边是 /slow/,故意复刻「图片又大、发现又晚、主线程还忙」的现场。 两边都装了 LCP 探针,打开后看右下角数字。
注:本站托管在 Cloudflare Pages,境内直连的首包(TTFB)本身有 0.5–1.5s 底噪。 那一段属于「线路选择」而不是「页面实现」,所以对比时要看两个页面的差值, 尤其看探针里「该图请求启动于 +Xms」这一行——它衡量的是发现时机,与线路无关。
代码就在仓库里,可以逐条对照。每条都只改一个主要因素,方便用同一台设备、同一网络重复测。
用 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>首屏图是 LCP 元素,就别让它等着被发现。HTML 里直接声明 preload, 并带上 imageSrcSet,浏览器解析到就会发请求。
<link rel="preload" as="image" type="image/avif"
imageSrcSet="/img/hero-480.avif 480w, ... 2400w"
imageSizes="100vw" fetchPriority="high" />进场动画全用 CSS(animation-timeline: view()), 不进 JS 主线程;没有字体网络请求;没有第三方埋点。
@supports (animation-timeline: view()) {
.reveal { animation: rise linear both; animation-timeline: view(); }
}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