骨架屏HTML必须与服务端首包同构,否则hydration错位导致闪屏、表单清空、焦点丢失;需严格一致标签名、class、层级、子节点数,禁用动态class和height:100%,图片占位用aspect-ratio或padding-top hack,DOM须卸载而非隐藏。

骨架屏 HTML 必须和服务端首包同构,否则 hydration 会错位
React/Vue 的 SSR 渲染依赖服务端输出的 DOM 结构与客户端 hydrate 时完全一致。如果服务端返回的是 <div class="card"></div>,而客户端真实组件渲染的是 <article class="card real-card"></article>,hydration 就会丢弃整个节点树重挂载——结果就是内容闪一下、表单值清空、焦点丢失。
常见错误包括:
- 服务端用
class="card skeleton",客户端用class="card"(class 不匹配) - 骨架用了
<section>,真实内容用<div>(标签名不一致) - 骨架有 3 个子节点,真实内容只有 2 个(数量差一层)
- 模板中残留注释节点或空格文本节点,客户端忽略导致层级偏移
实操建议:
- 骨架和真实内容共用同一组 class 名,如都用
card、card-title、card-image - 禁用动态拼接 class:
class="card {{ loading ? 'skeleton' : '' }}"是危险写法;应固定为class="card skeleton-card",靠 CSS 控制显隐 - 用工具校验结构一致性:在服务端吐出的 HTML 和客户端 hydrate 后的 DOM 上运行
document.querySelector('.card').outerHTML对比
骨架元素必须设固定尺寸或 aspect-ratio,禁用 height: 100%
height: 100% 在父容器没高度时坍缩为 0,骨架“消失”,真实内容插入后立刻触发 layout shift,CLS 指标直接恶化。这不是视觉问题,是布局塌陷。
立即学习“前端免费学习笔记(深入)”;
图片占位尤其敏感:
- 现代方案:用
aspect-ratio: 16 / 9+width: 100%+overflow: hidden - 兼容旧浏览器:用
padding-top: 56.25%+position: relative+ 绝对定位子元素撑满 - 绝对禁止
<img src="data:image/svg+xml">—— 它仍触发解码逻辑,占用主线程
所有骨架元素必须设 overflow: hidden,否则渐变背景溢出圆角,破坏视觉一致性。
骨架 DOM 必须卸载,不能靠 display: none 或 opacity: 0 隐藏
隐藏 ≠ 移除。v-show、visibility: hidden、opacity: 0 让骨架节点持续参与布局计算、占用无障碍树、拖慢页签性能,更关键的是破坏 hydration 一致性。
真实内容挂载前,骨架 DOM 必须被移除或替换。实操路径:
- SSR 场景下:服务端只输出骨架 HTML;客户端数据就绪后,用
ReactDOM.hydrateRoot或createRoot替换整个#root节点 - Next.js:用
loading.tsx输出骨架,路由切换时框架自动卸载旧 DOM - 纯前端场景:用
replaceChild或innerHTML = realContentHTML,而非 toggle class
过渡动画仅限 opacity 和 transform,禁用 display、visibility、height 等触发重排的属性。
服务端必须 flush 骨架 HTML,不能等模板渲染完才 res.end()
骨架失效的首要原因是 HTML 结构没在首屏渲染前到达浏览器。等 document.readyState === 'interactive' 或 DOMContentLoaded 才用 JS 插入 <div class="skeleton"></div>,此时白屏已发生,LCP 直接劣化。
正确做法是服务端模板在首次 res.write() 或 flush 阶段就输出骨架 DOM:
- Node.js Express:先
res.write('<div id="root"></div>'),再res.flush(),紧接着输出骨架 HTML - Next.js 13+:用
generateStaticParams+loading.tsx,骨架随首包下发 - PHP 模板:禁用
ob_start()全量缓冲,用flush()提前推送
所有骨架节点需设明确 width、height 或 aspect-ratio,否则 height 为 0 导致布局塌陷——这点最容易被忽略,但影响最直接。



















