骨架屏必须是服务端直出的静态html节点,且深度≤6层,内联css需≤10kb并置于head顶部,字体须preload,ssr需流式输出首屏内容。

骨架屏不是加了就有效,关键看它是不是HTML解析器“第一眼”就看到的内容
FCP(首次内容绘制)只认浏览器解析到的第一个文本、图片或<svg></svg>节点,纯CSS灰色块根本不算内容。很多团队在Vue里用v-if="loading"控制骨架,结果JS执行完才挂载,HTML解析器全程没看见它——白屏时间没缩短,反而多占内存。
- 骨架必须是服务端直出的静态
<div>或<code><img>,不能靠JS动态插入 - 结构要扁平:避免嵌套超过6层,比如
<div><div><div><div><p>Hello</p></div></div></div></div>会让解析器多走三轮父子绑定 - 图片占位统一用
data:image/svg+xml内联base64,不触发HTTP请求 - 用
data-skeleton="true"标记骨架节点,客户端只改属性值,不替换DOM树,避免hydration闪动 -
<style></style>必须放在最顶部,紧贴<meta charset>后面 - 禁止在内联
<style></style>里写@import或@font-face,它们会触发同步网络请求 - 用Chrome Coverage面板验证:禁用网络后刷新,只保留实际渲染用到的规则
- 字体加载逻辑必须剥离,用
font-display: optional配合<link rel="preload" as="font"> - 启用
renderToPipeableStream(Next.js 13+)或手动res.write()分块输出 - 首屏静态内容(如页眉、标题)优先flush,动态部分用骨架占位
- 模板中禁用同步操作:比如
fs.readFileSync()或全局正则匹配,它们会拉长TTFB - 检查Node.js模板引擎是否默认缓存整个输出,需显式配置流式开关
- 用
<header></header>、<main></main>、<section></section>替代无意义<div>包装,语义化标签本身就能降深 <li>Flex/Grid布局别为对齐多套一层<code><div>,改用<code>display: contents(Safari 15.4+支持) - 骨架卡片结构建议:
<article class="skeleton-card"><img><div> <h3></h3> <p></p> </div></article>,深度控制在4层内 - 避免在骨架里写
<svg></svg>或伪元素动画——它们不是FCP认可的内容,还增加解析负担
真正难的不是怎么写骨架,而是判断哪些HTML节点在首屏真实参与渲染;这个判断一旦出错,骨架就从优化变成负优化。
内联
很多人把整套Tailwind塞进<style></style>,结果FCP更慢——不是样式没生效,而是HTML解析器被卡在读取15KB CSS文本上。实测低端Android设备上,内联CSS超10KB(gzip后)会多耗80ms+。
流式SSR输出比等数据全齐再吐HTML更能抢出FCP
Next.js默认等所有数据就绪才返回完整HTML,这等于让浏览器干等。FCP优化的本质是“让解析器尽早拿到能画的东西”,哪怕只是<h1>首页</h1>加三个<div class="skeleton-item"></div>。
DOM depth ≥7时,骨架结构再漂亮也拖慢FCP
Chrome DevTools右键任意节点→“Show DOM properties”看到的depth值,就是解析器要走的父子绑定层数。每深一层,不仅增加节点创建开销,还可能触发重排预备计算。











