服务端直出的骨架html必须首字节到达即渲染,占位标签须为静态html而非js动态插入,且需绑定业务维度缓存键、支持流式响应与服务端精准替换。

服务端直出的骨架 HTML 必须在首字节到达浏览器时就能开始渲染,占位标签(如 <!-- skeleton --> 或自定义 <marker name="profile"></marker>)不能依赖 JS 加载后才替换,否则白屏时间没变,只是把阻塞从 HTML 解析挪到了 JS 执行。
占位标签必须是静态 HTML 片段,而非 JS 动态插入
常见错误是用 JS 在 DOMContentLoaded 后创建 div 插入 DOM,再用 innerHTML 填充内容——这等于放弃服务端直出价值,用户仍要等 JS 下载、解析、执行完才看到骨架。
- 正确做法:服务端模板直接输出带语义的占位结构,例如
<section class="skeleton" data-skeleton="user-profile"><div class="avatar-placeholder"></div> <h3></h3> <p></p></section> - 禁止在 HTML 中写
<script>document.querySelector(...).innerHTML = ...</script>类逻辑 - 若用新提案
<marker name="profile"></marker>,需确认当前浏览器支持(截至 2026 年 6 月仅 Chromium 128+ 实验性支持),生产环境必须降级为静态 HTML 占位
流式替换不能靠客户端 JS,而要靠服务端 chunk 分块 + HTTP 流式响应
真正“流式替换”的发生点在服务端响应阶段,不是前端。浏览器拿到第一个 chunk 就开始解析渲染,后续 chunk 按需注入对应占位区域,前提是服务端能按语义块切分并标注上下文。
- 网关或 SSR 层需用
htmlparser2的流式 tokenizer 解析模板,识别opentag如<marker name="profile"></marker>,不构建完整 DOM - 每个数据块返回前注入 HTML 注释元数据:
<!-- chunk-meta: {"name":"profile","offset":12480} -->,下游可精准定位占位位置 - HTTP 响应头必须设
Content-Type: text/html; charset=utf-8且禁用Transfer-Encoding: gzip(流式传输需明文 chunk) - Node.js 中用
res.write()分批输出,避免res.end(htmlString)一次性吐出整页
占位标签命名与缓存键必须绑定业务上下文维度
骨架结构看似静态,但不同用户、设备、地区看到的占位样式可能不同(比如移动端隐藏某些字段),若缓存键不区分这些维度,就会导致骨架错乱或敏感信息泄露。
- 模板碎片缓存键示例:
skeleton:profile:uid_456:region_us:mobile,缺一不可 - 禁止用通用键如
skeleton:profile,否则登录用户看到未登录版占位,或 A 地区用户看到 B 地区促销文案占位 - 占位标签内不得硬编码运行时变量(如
<div data-user-id="{{uid}}">),所有动态值必须在拼装完成后再注入,否则会固化进缓存<li>若占位需加载异步数据(如用户头像尺寸),应在服务端预查并内联到占位 HTML 中,而不是留空等 JS 补充</li> <p>最易被忽略的是:占位标签本身不是性能优化终点,而是服务端流式响应链路的锚点。它必须能被网关 tokenizer 识别、被缓存系统隔离、被 HTTP 分块机制承载——任何一环断开,都会退化成传统 JS 骨架方案,首屏感知时间毫无改善。</p> </div>
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











