KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

07 · 性能与核心网页指标 — keel 龙骨

本章目标:LCP / INP / CLS 达标,且静态资源体积与缓存策略合理。 验收标准:GSC 的「核心网页指标」报告显示"良好",或本地 Lighthouse 三项达标。

本章目标:LCP / INP / CLS 达标,且静态资源体积与缓存策略合理。
验收标准:GSC 的「核心网页指标」报告显示"良好",或本地 Lighthouse 三项达标。

定位:性能是放大器不是发动机。内容不收录时优化性能毫无意义;但收录之后,加载体验会直接影响排名与跳出率。这一章只讲与 SEO 强相关的部分。


一、三个指标分别是什么

指标 全称 衡量 良好阈值 差
LCP Largest Contentful Paint 最大内容元素的渲染时间(加载速度) ≤ 2.5s > 4s
INP Interaction to Next Paint 交互到下一帧(响应速度,2024 起取代 FID) ≤ 200ms > 500ms
CLS Cumulative Layout Shift 累计布局偏移(视觉稳定性) ≤ 0.1 > 0.25

看哪个数据? GSC 里的是真实用户数据(field data,CrUX),比本地 Lighthouse(lab data)更有说服力。本地测的是"我这台机器这次访问",GSC 反映的是真实访客的 P75。

若 GSC 数据不足(新站常见),先用本地 Lighthouse 定位问题:

npx lighthouse https://www.xxxxxx.cn/learning/ --preset=desktop --output=json --quiet

二、LCP:让主内容赶紧出来

LCP 元素通常是首屏最大的图片或文本块。三条最有效的手段:

① 预渲染 / SSR(已在第 01 章做过)

这是本站最大的一笔收益:正文内联在 HTML 里,首屏不需要等 JS 下载、解析、执行、发请求、再渲染。对内容型站点,这一步通常能把 LCP 从 3~4s 降到 1s 出头。

② 消除关键路径上的阻塞

<!-- 首屏关键 CSS 内联,其余延后 -->
<style>/* critical css */</style>
<link rel="stylesheet" href="/assets/main.css" media="print" onload="this.media='all'">

<!-- 非关键 JS 延后 -->
<script src="/assets/app.js" defer></script>

③ 图片与字体

<!-- 显式尺寸防 CLS,loading=lazy 减少首屏负担 -->
<img src="/og/cover.png" width="1200" height="630" loading="lazy" decoding="async">

<!-- 字体不阻塞渲染 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

⚠️ 不要给首屏图片加 loading="lazy" ——它会推迟加载,反而拖慢 LCP。只对首屏之外的图片用。


三、INP:让交互有响应

INP 主要受主线程长任务影响。内容型站点最常见的两个来源:

① Markdown 渲染 / 代码高亮

几百行 Markdown 同步解析会阻塞主线程。本站的做法是:

// 把渲染结果缓存,避免每次路由切换重解析
const { data } = useQuery({
  queryKey: ['lesson', id],
  queryFn: () => fetchLesson(id),
  staleTime: Infinity,       // 内容不变就不重新取
});

配合 TanStack Query 的缓存,切回已读章节是即时的。

② 大量 DOM 操作

一个真实案例:给每个代码块挂"复制"按钮时用了 MutationObserver 守护,每次内容重渲染都重新扫描全部 pre 元素——在长文页面上造成明显卡顿。

修法是缩小观察范围(只观察新增节点)并在完成后断开:

const observer = new MutationObserver((records) => {
  for (const r of records) r.addedNodes.forEach(attachIfCodeBlock);
});
observer.observe(container, { childList: true, subtree: true });
// 组件卸载时 observer.disconnect()

原则:任何 O(n) 的 DOM 遍历,都要问一句"这个 n 会不会变成几百"。


四、CLS:别让页面跳

布局偏移的三个常见来源与解法:

来源 解法
图片/iframe 无尺寸 显式 width / height,或用 aspect-ratio
字体加载导致文字重排 font-display: optional 或预加载字体
动态插入内容(公告条、骨架屏尺寸不符) 为异步内容预留等尺寸占位

最后一条在真实项目里踩过:进入路由时先显示一个矮骨架屏,内容加载后换成高得多的真实区块,页面"跳"了一下。

解法是骨架屏尺寸必须与真实内容一致,至少高度一致:

.skeleton-lesson { min-height: 420px; }   /* 与真实卡片等高 */

CLS 是最容易一次修好的指标——它几乎总是"尺寸没预留"这一个原因。


五、缓存与传输

nginx 侧的静态资源策略

# 带 hash 的资源:长缓存 + immutable
location /assets/ {
    expires 30d;
    add_header Cache-Control "public, max-age=2592000, immutable";
}

# index.html:每次校验(否则发版后用户拿不到新版本)
location = /index.html {
    add_header Cache-Control "no-cache";
}

# 内容 JSON:短缓存,保证更新可见
location /content/ {
    add_header Cache-Control "public, max-age=300";
}

# 文本压缩(gzip 或 brotli)
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;

immutable 只能给带 hash 的文件用。给 index.html 用会导致发版后用户长时间看到旧页面。

检查是否生效

curl -sI https://www.xxxxxx.cn/learning/assets/index-abc123.js | grep -i cache-control
curl -sI -H 'Accept-Encoding: gzip' https://www.xxxxxx.cn/learning/ | grep -i content-encoding

传输层

HTTPS 已开启的情况下,确认 HTTP/2 生效(多路复用对多资源页面帮助明显):

curl -sI --http2 https://www.xxxxxx.cn/learning/ | head -1   # 应显示 HTTP/2

六、体积:先看是不是真的有问题

不要盲目做代码分割。先量:

# 构建产物体积分布
du -sh dist/assets/* | sort -rh | head -10

# 首屏实际加载了多少
curl -s https://www.xxxxxx.cn/learning/ | grep -oE 'assets/[A-Za-z0-9._-]+\.(js|css)' | sort -u

内容型站点的合理目标:首屏 JS 总量 < 300KB(gzip 后 < 100KB)。

本站按路由做代码分割后,阅读页与首页各自只加载自己需要的 chunk。判断分割是否生效:

curl -s https://www.xxxxxx.cn/learning/ | grep -oE 'assets/ReaderPage-[A-Za-z0-9_-]+\.js'
# 阅读页专属 chunk 不在首页 HTML 里 → 分割生效

⚠️ 不要为了分割而分割:如果所有 chunk 都在首屏被同时加载,分割只是增加了请求数。先看路由粒度是否真的被用上。


七、本章验收

# 1. HTTP/2 与压缩
curl -sI --http2 https://www.xxxxxx.cn/learning/ | head -1
curl -sI -H 'Accept-Encoding: gzip' https://www.xxxxxx.cn/learning/ | grep -i content-encoding

# 2. 缓存策略
curl -sI https://www.xxxxxx.cn/learning/assets/index-abc123.js | grep -i cache-control

# 3. 首屏资源数量与体积
du -sh dist/assets/* | sort -rh | head -5

最后一章把这些动作变成可长期执行的例行检查。

进入 keel 阅读