Front-End Checklist 实践指南:用 Streaming HTML 把 TTFB 从 800ms 压到 10ms 📅 发布时间:2026/9/20 22:38:35 👁 浏览次数: 【免费下载链接】Front-End-Checklist The essential checklist for modern web development, for humans and AI agents项目地址https://gitcode.com/gh_mirrors/fr/Front-End-Checklist点击查看免费下载本文基于 Front-End Checklist 仓库中的streaming-html性能规则规则文档 与 SKILL.md展开系统讲解如何利用 HTTP 分块传输与 ReactrenderToPipeableStream/ WebReadableStream在服务端渲染时先发外壳、后流式补全内容从而显著降低 Time to First ByteTTFB与 First Contentful PaintFCP。读完本文你将掌握三种落地姿势自定义 Node.js/Express 环境的renderToPipeableStream、Next.js App Router 的 Suspense 流式渲染以及非 React 环境的 Web Streams 实现并学会用 WebPageTest 与 DevTools 验证流式是否真正生效。流式渲染为什么能赢分块传输与先发外壳HTML streaming 依赖 HTTP 的分块传输编码Transfer-Encoding: chunked。它允许服务器在不知道响应总长度的情况下把响应分成多个 chunk 陆续发送。浏览器每收到一块就开始解析并在最后一块到达之前就去发现和拉取 CSS、字体、脚本、图片等子资源。传统缓冲式SSR 的时间线是串行的0ms Server starts executing 800ms All database queries complete 800ms HTML generation begins 820ms HTML generation completes 820ms TTFB — first byte arrives in browser 850ms CSS parsed 900ms FCP采用流式渲染后慢查询与页面外壳的交付被解耦0ms Server starts executing 10ms TTFB — shell HTML flushed immediately (head, above-fold skeleton) 10ms Browser fetches CSS, discovers LCP image 300ms Slow data query completes; component HTML streamed 380ms FCP — above-fold content painted在这个例子里浏览器开始工作的时间提前了 790ms。核心原理正如规则文档 references/rule.md 所强调的传统 SSR 必须等所有数据请求完成后才发送第一个字节浏览器在慢查询结束前无法解析 HTML、发现子资源或渲染任何内容流式则把首屏以上内容的交付从慢数据库查询和第三方 API 调用中解放出来直接改善感知性能与 Largest Contentful PaintLCP。判定规则何时该检查、怎么修、怎么评审SKILL.md 为这项规则定义了明确的检查口径可作为 Code Review 与 AI Agent 审查的清单Check检查审查服务端渲染实现判断 HTML 是按块流式发送还是等全部生成后才缓冲发给客户端Fix修复重构 SSR 实现改用renderToPipeableStream 围绕慢数据请求的 Suspense 边界或在 Node.js handler 中使用ReadableStream让首屏以上 HTML 立即冲刷给客户端Explain解释说明流式 HTML 如何通过解耦快速内容与慢速服务端数据依赖来改善 TTFB 和 FCPCode Review评审重点检查服务端入口与页面组件中是否存在renderToString调用、数据请求组件是否缺少 Suspense 边界、响应返回前是否对慢查询进行了不必要的await。React 方案renderToPipeableStream 与 onShellReadyrenderToPipeableStream是 React 的流式 SSR API。它接收一个 React 树把渲染结果按块写入 Node.js 的可写流。最关键的选项是onShellReady——当应用外壳Suspense 边界之外的所有内容可以发送时被调用// server/render.ts (custom Express / Node.js setup) import { renderToPipeableStream } from react-dom/server; import { IncomingMessage, ServerResponse } from http; import App from ./App; export function handleRequest(req: IncomingMessage, res: ServerResponse) { let didError false; const { pipe, abort } renderToPipeableStream( App url{req.url} /, { // bootstrapScripts is optional — use for client hydration bootstrapScripts: [/static/js/main.js], onShellReady() { // The shell is the minimal HTML that does not depend on Suspense // boundaries — flush it immediately for a low TTFB res.statusCode didError ? 500 : 200; res.setHeader(Content-Type, text/html; charsetutf-8); // chunked transfer is automatic when the length is unknown pipe(res); }, onShellError(error) { // Shell rendering failed — fall back to a basic error page res.statusCode 500; res.setHeader(Content-Type, text/html); res.end(h1Something went wrong/h1); }, onError(error) { didError true; console.error(error); }, } ); // Abort streaming after 10 seconds to avoid hanging connections setTimeout(abort, 10_000); }要点解析onShellReady外壳就绪即pipe(res)此时不依赖 Suspense 边界的最小 HTML 会立刻冲刷出去TTFB 只受外壳渲染时间影响onShellError外壳渲染失败时的兜底路径直接返回 500 与基础错误页onError流式过程中任一处渲染出错都会触发通过didError标记让状态码最终反映为 500bootstrapScripts可选用于客户端水合hydration时注入入口脚本abort配合setTimeout设置 10 秒超时避免慢请求挂起连接当响应长度未知时Node.js 会自动使用Transfer-Encoding: chunked无需手动设置。Next.js App Router默认流式 细粒度 SuspenseNext.js App Router 默认就会流式输出。任何包裹数据请求的asyncServer Component 本身就是一个天然的 Suspense 边界而显式使用Suspense可以精确控制哪些内容先被冲刷// app/dashboard/page.tsx import { Suspense } from react; import { DashboardSkeleton, RecentOrdersSkeleton } from /components/skeletons; export default function DashboardPage() { return ( main {/* The page title and navigation are part of the shell — they render immediately without waiting for any data. */} h1Dashboard/h1 nav{/* ... */}/nav {/* KPICards fetches summary stats. If it is slow, show a skeleton rather than blocking the page shell. */} Suspense fallback{DashboardSkeleton /} KPICards / /Suspense {/* RecentOrders fetches a paginated list — isolated in its own boundary so it does not block KPICards. */} Suspense fallback{RecentOrdersSkeleton /} RecentOrders / /Suspense /main ); } // KPICards is a Server Component that fetches its own data async function KPICards() { const stats await fetchKPIStats(); // potentially slow return ul{stats.map((s) KPICard key{s.id} stat{s} /)}/ul; } async function RecentOrders() { const orders await fetchRecentOrders(); // independent query return OrderList orders{orders} /; }Next.js 会立即冲刷h1Dashboard/h1和导航栏的 HTML随后在KPICards、RecentOrders各自的数据解析完成后利用 React 的 slot 机制把它们按任意顺序流式补入。⚠️边界太粗会杀死流式如果把整页包进单个 Suspense 边界那么在所有数据就绪前不会冲刷任何内容——效果与缓冲式 SSR 完全一致。必须围绕每个独立取数的区块建立细粒度边界确保首屏以上内容永远不会被首屏以下的数据阻塞。仓库实例Front-End Checklist 站点的 Suspense 用法这一实践在本仓库的 Next.js 前端中已落地可作为对照参考home-page-content.tsx 将赞助商区块包进Suspense fallback{SponsorsSectionFallback /}同时外层再用ErrorBoundary兜底页面其余区块Hero、分类、清单预览等不与赞助商数据请求互相阻塞sponsors-section-async.tsx 是一个 async Server Component内部await fetchAllSponsors()拉取赞助商数据注释明确要求它必须被 Suspense 包裹以免该请求阻塞页面导航同文件的SponsorsSectionFallbackL21-L48提供了流式等待期间的骨架屏aria-labelledby、spinnerrules/page.tsx/rules/page.tsx#L80) 在规则浏览区使用Suspense fallback{RulesBrowserSkeleton /}配合use cache与cachePublicContent()做内容缓存静态页外壳先行、交互区数据后补。非 React 环境Node.js ReadableStream在不使用 React 的服务器环境纯 Node.js / Deno / Cloudflare Workers中可以用 Web Streams API 直接流式发送 HTML chunk// Plain Node.js / Deno / Cloudflare Workers example export function createStreamingResponse( getSlowData: () Promisestring ): Response { const encoder new TextEncoder(); const stream new ReadableStream({ async start(controller) { // Flush the page shell immediately controller.enqueue( encoder.encode(!DOCTYPE html html langen head meta charsetUTF-8 titleMy App/title link relstylesheet href/styles.css /head body headernav!-- navigation --/nav/header main idcontent div classloading-skeleton aria-busytrue aria-labelLoading content... /div ) ); // Fetch slow data while the browser is already parsing the shell const data await getSlowData(); // Stream the content section once data is ready controller.enqueue( encoder.encode( script // Replace skeleton with real content document.getElementById(content).innerHTML ${JSON.stringify(data)}; /script /main /body /html) ); controller.close(); }, }); return new Response(stream, { headers: { Content-Type: text/html; charsetutf-8, // Disable any response buffering by proxies X-Accel-Buffering: no, }, }); }关键点骨架 HTML 先enqueue冲刷浏览器解析骨架的同时服务端并行等待慢数据数据就绪后再enqueue内容区块并通过内联脚本替换骨架。注意骨架使用aria-busytrue与aria-label即使是无 React 的流式方案也保持无障碍语义。首块冲刷资源提示把 preload 放进第一个 chunk流式最有价值的模式之一是在页面内容尚未完全确定时就把link relpreload提示放进第一块 HTML// First chunk always includes critical resource hints const shellChunk !DOCTYPE html html head !-- Preload the LCP image — browser starts fetching immediately -- link relpreload asimage href/hero.webp fetchpriorityhigh !-- Preload critical fonts -- link relpreload asfont typefont/woff2 href/fonts/inter.woff2 crossorigin link relstylesheet href/critical.css /head body div idapp ;LCP 图片用fetchpriorityhigh的 preload 让浏览器立即开始下载字体用typefont/woff2crossorigin预取——这些请求从 TTFB 时刻就与 HTML 解析并行这正是本仓库render-blocking规则与streaming-html规则互相呼应的结合点见 streaming-html.mdx 的 relatedRules。⚠️CDN 与代理缓冲会抵消流式Nginx、Cloudflare 等代理常会先把响应缓冲完再转发给下游这会让流式彻底失效。Nginx 下设置X-Accel-Buffering: no并确保你的 CDN 对 HTML 响应启用流式直通streaming pass-through。测量与验证让流式可证明流式只有在浏览器真正提前收到并开始解析第一个 chunk 时才有效因此必须验证而不只是相信配置。WebPageTest 瀑布图验证第一条绿色条Time to First Byte应低于 200msCSS 与 LCP 图片请求应在 HTML 条仍处于活动状态时就开始DevTools Performance 中的 Layout 事件应发生在响应完成之前。自动化检查打开 DevTools Network 面板点击页面请求Response 标签应显示 HTML 以多个 chunk 到达响应会在数百毫秒内持续接收后续 chunk而非瞬间完成运行 Lighthouse确认 TTFB 低于 600ms绿色、FCP 低于 1.8s——SKILL.md 的 Quick Reference 也明确提示Lighthouse 中 TTFB 超过 600ms 通常是未使用流式的信号在 DevTools 中以模拟 3G 网络测试即使数据需要 35 秒骨架 UI 也应快速出现。手动检查在 Next.js 中于被 Suspense 包裹的 async Server Component 内添加console.log(rendering KPICards)观察服务器日志页面加载事件应早于该日志出现从而证明外壳先于慢组件冲刷。关联规则与阅读延伸该规则在 Front-End Checklist 规则库 中归属performance大类、rendering子类难度为 advanced预计耗时 45 分钟。与其配套的关联规则包括ttfb当瓶颈是服务端数据拉取时间时流式是降低 TTFB 的首要服务端手段largest-contentful-paint更快地流式输出首屏 HTML通过更早发现并加载 LCP 图片或文本元素直接降低 LCPrender-blocking流式可在初始 chunk 中冲刷 preload 提示让浏览器在完整响应到达前就获取关键资源list-virtualization两者同属performance/rendering区域常一起评审。同时仓库为 Agent/LLM 落地评审提供了结构化的 SKILL.md含 check / fix / explain / code review 四段提示词与完整实操版 references/rule.md在代码评审中可直接按此清单逐项核对。小结让慢数据不再阻塞首屏流式渲染的本质是把生成完整 HTML这一个串行任务拆成外壳 多个独立内容区块的并行流水线浏览器从 TTFB 开始解析、发现并拉取子资源慢查询完成后内容再按块补入。落地时记住三条主线自定义 React SSR 用renderToPipeableStream的onShellReady即时pipeNext.js App Router 用细粒度Suspense隔离每个独立取数区块非 React 环境用ReadableStream配合X-Accel-Buffering: no绕过代理缓冲。最后务必用瀑布图与 Lighthouse 阈值TTFB 600ms、FCP 1.8s证明流式真实生效。赞分享【免费下载链接】Front-End-Checklist The essential checklist for modern web development, for humans and AI agents项目地址https://gitcode.com/gh_mirrors/fr/Front-End-Checklist点击查看免费下载相关推荐yuzu Switch模拟器入门安装步骤、常用设置与故障排查yuzu Switch模拟器入门安装步骤、常用设置与故障排查 想在电脑上玩Switch游戏又不想再买一台掌机yuzu Switch模拟器是一个常见答案。这虚拟化桌面应用图形学Notepad-- 跨平台文本编辑器从装好到跨文件批量替换的完整实战Notepad 跨平台文本编辑器从装好到跨文件批量替换的完整实战 Notepad 是一个同时支持 Windows、Linux、macOS 的轻量级文本编辑器桌面应用全设备 Favicon 实现指南基于 Front-End-Checklist 的 HTML 图标规范与实践全设备 Favicon 实现指南基于 Front End Checklist 的 HTML 图标规范与实践 本指南以 Front End Checklist上一篇一键高清化SeedVR2让AI视频修复变得简单高效下一篇awesome-gpt-image-2 GPT-Image2 信息图模板一次成型指南结构化提示词不靠运气创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考