流式传输能压低首屏白屏时间,是因为服务端分块输出骨架HTML(如header、nav、main),使浏览器在TTFB后几毫秒内即可开始解析、发现资源并绘制,而非等待整页渲染完成;实测FCP可从800ms+降至200ms内,前提是骨架轻量、内联关键样式、无阻塞脚本与外部资源。

流式传输为什么能压低首屏白屏时间
服务端等整个 HTML 拼完再发响应,用户得等到 TTFB + 全量渲染耗时才看到任何内容;而启用 Transfer-Encoding: chunked 后,骨架结构(比如 <header>、<nav>、首屏 <main>)可在几毫秒内抵达浏览器,触发解析、资源发现和初步绘制。
关键不是“传输快”,而是“浏览器能早一点开始干活”。实测中,未开启流式时 FCP(首次内容绘制)常卡在 800ms+,开启后可压到 200ms 内——前提是骨架 HTML 真的轻、内联、无阻塞。
- Node.js(如 Express)需显式调用
res.flush()或使用res.write()分段输出,不能依赖模板引擎默认行为 - Nginx 默认支持 chunked,但若上游(如 SSR 服务)未分块写入,Nginx 仍会缓冲整页;需确认后端已禁用
buffering并设置Content-Length为 unset - PHP 需开启
ob_flush()+flush(),且 Web 服务器(如 Apache)不能启用mod_deflate缓冲
骨架 HTML 结构必须满足哪些硬约束
流式传输只是管道,骨架 HTML 才是内容。如果骨架里塞了同步 <script>、外部 <link rel="stylesheet"> 或没尺寸的 <img>,浏览器照样卡住,流式反而放大阻塞感知。
- 首屏区块只保留语义化标签:
<header>、<nav>、<main>、<section>,剔除所有非可视占位(如空<div class="skeleton">) - 关键样式必须内联进
<style>,且仅限首屏 DOM 节点用到的规则;避免@import、@font-face、CSS 变量定义(它们仍会触发额外请求或计算延迟) - 所有
<img>必须带width和height(或aspect-ratio),否则 layout shift 会拖慢 FCP 判定 - 禁止在骨架中出现
document.write()、同步<script>、或未加defer/async的外部脚本引用
如何验证流式 + 骨架是否真正生效
别只看 Network 面板里 document 请求的“Size”变小了——那可能是压缩导致的假象。真正在意的是浏览器是否在收到第一块 HTML 后立刻开始解析并触发资源加载。
立即学习“前端免费学习笔记(深入)”;
- Chrome DevTools → Network → 右键表头勾选 “Chunk Size”,观察 document 请求是否出现多行数据块(每块几十到几百字节),而非单行大体积响应
- Performance 面板录制页面加载,检查
Parse HTML是否在First Paint前就已启动,且持续时间短( - 在骨架末尾插入
<script>console.time('skeleton-parsed')</script>,并在完整 DOM 构建后打点,差值超过 50ms 就说明骨架内有隐性阻塞(如未察觉的 CSSOM 依赖或 JS 执行) - 用 curl 测试:
curl -v https://yoursite.com | head -n 50,确认前几十行已含<body>和首屏结构,而非满屏注释或空格
非首屏内容该用什么方式延迟挂载
骨架只负责“让用户立刻知道页面没挂”,后续内容必须彻底与初始解析解耦。用 display: none 包裹只是隐藏,DOM 和 CSSOM 依然构建,浪费内存和时间。
- 推荐用
<template>标签存放评论区、推荐列表等非首屏 HTML 字符串,它不参与解析也不触发资源加载 - Tab 类切换内容,初始只渲染激活态 tab 的 HTML,其余存为
data-html属性字符串,切换时用DOMParser().parseFromString()动态创建节点 - 滚动触达区域的内容,用
IntersectionObserver监听,触发后再fetch()模板片段或直接插入预置的<template> - 慎用
innerHTML = '<div>...</div>'拼接——XSS 风险高,且无法复用已编译的 DOM 节点;优先走document.createElement()+appendChild()路径



















