上周发版后,Lighthouse 跑分直接给移动端首屏赏了个 35 分的红灯,LCP (Largest Contentful Paint) 指标直接飙到了 6.8 秒。点开诊断报告一看,首屏那个占了大半个屏幕的主视觉 Banner 图,居然在页面加载完毕后 5 秒才姗姗来迟。更蠢的是,因为构建打包时用了某种通用的懒加载指令,这张决定首屏生死的大图居然被硬生生套上了 loading="lazy",再加上一大堆阻塞渲染的 CSS 和字体文件在前面排队,用户不跳出才怪。
针对这种情况,首要任务就是把这张首屏大图从延迟加载的泥潭里捞出来。常规的 <img> 标签即使没有 lazyload,也会因为浏览器解析 HTML 并发现它的优先级不够高,而被推迟到网络请求队列的后端。我们需要明确地告诉浏览器:“这张图是关键渲染路径上的核心,赶紧给我抢跑。”
首先,我们在 HTML 模板的 <head> 里直接塞入 <link rel="preload"> 指令。同时,利用 HTML5 最新的 fetchpriority 属性,把图片的获取优先级直接拉满到最高阶级:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>核心业务详情页</title>
<!-- 预加载 LCP 关键图片,并设置最高优先级 -->
<link
rel="preload"
as="image"
href="https://cdn.example.com/assets/images/hero-banner-large.webp"
fetchpriority="high"
type="image/webp"
>
<!-- 内联 Critical CSS,避免加载外部大样式表阻塞渲染 -->
<style id="critical-css">
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: #0f172a;
color: #f8fafc;
}
.hero-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 80vh;
background-position: center;
background-size: cover;
}
.hero-title {
font-size: 2.5rem;
font-weight: 800;
line-height: 1.2;
margin-bottom: 1rem;
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.hero-banner {
width: 100%;
max-width: 800px;
height: auto;
aspect-ratio: 16 / 9;
border-radius: 12px;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3);
}
</style>
<!-- 异步加载非关键 CSS -->
<link
rel="stylesheet"
href="/assets/css/main.chunk.min.css"
media="print"
onload="this.media='all'; this.onload=null;"
>
<noscript>
<link rel="stylesheet" href="/assets/css/main.chunk.min.css">
</noscript>
</head>
<body>
<div class="hero-container">
<h1 class="hero-title">构建下一代金融级数据看板</h1>
<img
class="hero-banner"
src="https://cdn.example.com/assets/images/hero-banner-large.webp"
fetchpriority="high"
alt="Hero Banner"
decoding="async"
>
</div>
</body>
</html>
光搞定图片还不够。很多前端习惯一把梭地在头部引用几百 KB 的全局 CSS 文件,导致浏览器在下载并解析完这些 CSS 之前,坚决不进行任何页面绘制(即所谓的渲染阻塞)。为了解决这个问题,我折腾了一套 Critical CSS(关键路径 CSS)提取方案。
在构建期,我们使用 PostCSS 配合 Puppeteer 模拟无头浏览器跑一遍页面,将处于首屏视口(Viewport)内的 HTML 元素所依赖 of CSS 样式强行提取出来,并直接注入到 HTML 模板的 <style> 标签中。而剩下那一大堆涉及弹窗、页脚、次要模块的非关键 CSS,则通过修改 media="print" 和 onload 回调变成异步加载,等首屏渲染完毕后再静默下载。
做完这两步改造后重新跑 Lighthouse,移动端 LCP 直接从 6.8 秒缩减到了 1.9 秒,评分终于回到了绿色的 96 分。这种细节优化不仅是对技术指标的追求,更直接关系到转化率,毕竟谁也不想在一个白屏转圈圈的网站上多等三秒钟。
你们在优化首屏时,有没有遇到过因为第三方统计脚本抢占带宽导致 preload 失效的坑?欢迎在下面聊聊解决方案。