手写骨架屏在多页面场景下必然失控,因其骨架结构须与真实DOM一一对应,而各页面组件嵌套、条件渲染、宽高比等差异导致手动维护不可持续,且动态class、SSR hydration错位、AST解析限制及缓存污染等问题加剧失控。

为什么手写骨架屏在多页面场景下必然失控
因为骨架结构必须和真实 DOM 一一对应,而每个页面的组件嵌套、条件渲染(v-if、v-for、React.Fragment)、宽高比逻辑都不同。你手动给 Home.vue 写一套 .skeleton-card,改天加了个 v-show 切换逻辑,或把图片从 16/9 换成 4/3,骨架就错位——不是高度塌陷,就是文字行被遮盖,甚至 hydration 时 React 报 Hydration failed because the server-rendered HTML didn't match。
- 所有手写骨架都依赖开发者“记住”结构变更,但没人能长期盯住十几个页面的 DOM 差异
- 一旦用了动态 class 名(如
class="card card--${type}"),骨架的class就无法匹配,CSS 失效 - SSR 场景下,服务端直出骨架 HTML,客户端 JS 却按新模板 hydrate,错位直接肉眼可见
Vue 项目用 v-skeleton 实现构建时注入的关键配置
它不是运行时插件,而是构建阶段扫描源码、提取结构、生成静态骨架 HTML 并内联进服务端响应。真正起作用的前提是:骨架节点必须可静态分析。
- 组件里要显式标记可骨架化区域,比如加
skeleton="true"属性到容器上,而不是靠 class 名推断 -
defineAsyncComponent必须包裹在骨架容器外层;否则构建时根本看不到内部结构 - 不能在
setup()里用ref或computed动态生成子节点——AST 解析器读不到运行时逻辑 - 配置
vite.config.ts时,include路径必须精确到具体 .vue 文件,不能写src/views/**/*,否则会漏解析
React 中 react-skeleton-generator 的 AST 解析边界
这个工具不执行代码,只解析 JSX 语法树。它能识别 <div className="card"></div>,但对 <div className={cls}></div> 完全无感——变量 cls 的值无法静态推导。
- 所有 props 必须字面量化:
loading={true}可以,loading={isLoading}不行 - 禁止使用
{children}透传;必须显式写出骨架 wrapper,如<SkeletonWrapper>{children}</SkeletonWrapper> - 组件内不能有
useMemo(() => <div>...</div>, [])这类运行时生成 JSX 的逻辑 - 输出的 HTML 片段需手动注入:
document.getElementById('root').insertAdjacentHTML('afterbegin', skeletonHtml),不能等useEffect才执行
SSR 场景下骨架屏缓存污染的真实风险
CDN 或反向代理缓存了带骨架结构的 HTML,用户下次访问时,哪怕数据已就绪,骨架仍卡在页面上——这不是 bug,是缓存策略没隔离骨架节点。
立即学习“前端免费学习笔记(深入)”;
- 必须给所有骨架元素加
data-skeleton="true"属性,JS 加载后立刻执行document.querySelectorAll('[data-skeleton]').forEach(el => el.remove()) - Next.js 的
getServerSideProps中,骨架 HTML 字符串不能拼进props直接透传,得用renderToStaticMarkup生成并插入到html字符串中特定位置 - Nuxt3 的
server/components/下骨架组件,必须确保不被客户端defineAsyncComponent二次加载,否则会重复渲染
最易被忽略的是:骨架屏消失瞬间的真实内容重排。如果骨架用 padding-top hack 实现宽高比,而真实图片用 aspect-ratio,两者计算方式不同,就会导致布局跳动——这种问题不会报错,只会让用户觉得“页面在抖”。



















