Why Qwik’s Resumable Architecture Beats Traditional Hydration
This article explains how Qwik, a full‑stack SSR framework, uses pre‑compilation, resumable rendering, and fine‑grained event handling to avoid the heavy runtime and hydration overhead of traditional Virtual DOM frameworks, offering superior first‑paint performance and lower JavaScript payloads.
Qwik 是什么
Qwik 是一个前端框架,语法类似 React 使用 JSX 和 Hooks,不过 Qwik 是全栈 SSR 框架,并采用一系列策略优化页面的首屏性能,使得无论应用体积多大,首屏性能在 PageSpeed 测试中基本都能达到满分。
Misko Hevery
Misko Hevery 是 Qwik 的作者,也是 Angular.js 与 Angular 的作者。
他在硅谷多家公司从事数据库/后端工作,2005 年加入谷歌,2009 年与 Adam Abrons 开发 Angular.js,2021 年离职谷歌加入 builder.io 成为 CTO 并启动 Qwik 项目。
一个问题
为什么前端框架层出不穷,出现 Svelte、SolidJS、Astro、Fresh、Marko、Qwik 等?
Big Runtime
React 和 Vue 基于 Runtime,框架代码会被打包到最终产物并在浏览器执行,状态变化时通过 Diff 计算需要更新的 DOM 节点。
Angular、React、Vue 等 Big Runtime 框架体积较大,首屏加载时需要加载并执行大量 JS,导致弱网和低性能手机上的性能开销显著。
Virtual DOM is Pure Overhead
Virtual DOM 并不高效。它通过 diff 算法在状态变化时生成新的 Virtual DOM,再计算真实视图的变化,只更新需要的 DOM 节点,但这仍然需要完整的渲染循环和额外的内存占用。
React 顶层组件的
state更新会导致所有子组件重新渲染(
re‑render),除非使用
shouldComponentUpdate、
PureComponent、
memo、
useMemo等 API 手动优化。
React 16 之前的递归 Diff 性能受限,Fiber 架构将递归改为可中断的循环并优化任务调度。
Virtual DOM 还会产生大量内存占用,例如 100 万个空的 Vue Virtual DOM 对象会占用约 110 MB 内存。
SSR Hydration is Pure Overhead
Hydration 为服务端渲染的 HTML 提供交互能力,需要运行框架代码、恢复应用状态、构建 Virtual DOM 并为元素添加事件监听。
Hydration 的开销主要来自两点:
框架必须下载当前页面所有组件代码并在浏览器解析执行。
框架必须重新执行组件代码以重建事件监听和组件树,即使没有创建新的 DOM。
如果服务端将所有需要的信息序列化传输到浏览器,Hydration 完全可以省略。
社区的探索
Precompile
许多新框架通过预编译直接进行细粒度的 DOM 操作,避免运行时开销。
Svelte 通过静态编译将所有运行时代码内置在组件中,几乎不需要额外的框架运行时。
<code><a>{{ msg }}</a></code> 会被编译成如下代码: <code>function renderMainFragment(root, component, target) {<br/> var a = document.createElement('a');<br/><br/> var text = document.createTextNode(root.msg);<br/> a.appendChild(text);<br/><br/> target.appendChild(a);<br/><br/> return {<br/> update: function (changed, root) {<br/> text.data = root.msg;<br/> },<br/> teardown: function (detach) {<br/> if (detach) a.parentNode.removeChild(a);<br/> }<br/> };<br/>}</code>
SolidJS 也是 Precompile,保留少量运行时并支持 Tree Shaking。
Islands Architecture
Islands 架构模型在 2019 年提出,2021 年由 Preact 作者 Json Miller 推广。它在 SSR/SSG 应用中,只对交互式组件执行 hydration,静态组件直接复用服务器返回的 HTML,形成“岛屿”。
Islands 解决了部分 Runtime 与 Hydration 问题,但仍保留运行时。
React Server Component (RSC)
React 2020 年发布 RSC Demo,允许在 Node.js 上运行组件并将结果以 DSL 形式下发到浏览器,仅 RSC 的依赖不打包到客户端,从而减小前端代码体积。
示例代码需要约 240 KB(gzip 后 74 KB)运行时来渲染 markdown 笔记: <code>// NoteWithMarkdown.js<br/>import marked from 'marked';<br/>import sanitizeHtml from 'sanitize-html';<br/><br/>function NoteWithMarkdown({text}) {<br/> const html = sanitizeHtml(marked(text));<br/> return (/* render */);<br/>}</code>
RSC 部分解决了 Runtime 与 Hydration 的问题。
Resumable
Resumable 是 Qwik 提出的概念,指在 SSR 时执行的应用可以在客户端恢复执行,而无需重新构建和下载所有代码,也不需要完整的 Hydration。
Qwik 是如何实现 Resumable 的?
Precompile
Qwik 使用 optimizer(基于 SWC)对代码进行预编译。
示例 HelloWorld 代码经过
optimizer.transformModules编译后生成 JSON,再处理成 JS 文件。
一个组件会被拆分成处理 DOM 逻辑和事件逻辑的两个文件。
通过
renderToString生成的 SSR HTML 示例:
<code><!DOCTYPE html><br/><html q:container="paused" q:version="0.11.1" q:render="ssr" q:base="/build/"><br/><!--qv q:id=0 q:key=shY4sSSi6wY:hello--> <p on:click="s_9rnzdakbxj8.js#s_9rnZDAkBxj8[0]" q:id="1"> Hello Qwik </p><br/><!--/qv--> <script type="qwik/json"> { "ctx" :{"#1":{"r":"0"}}, "objs":["Qwik"], "subs":[] } </script><br/> <script> window.qwikevents ||= []; window.qwikevents.push("click") </script><br/></html></code>Interactive
Qwik 的事件监听由页面上的 qwikevents 统一处理,而不是为每个 DOM 单独绑定监听器。
<code>window.qwikevents ||= []; window.qwikevents.push("click")</code>qwikevents 监听全局事件,即使没有应用代码也能捕获。
触发事件的 DOM 节点的
on:event属性中包含 QRL,Qwikloader 加载对应 JS 并执行,
q:id与事件数组中的数字指示方法参数。
Component tree
在 SSR 的
renderToString过程中收集组件信息并序列化到 HTML,浏览器无需在运行时重建完整组件树,只在需要时懒加载。
在组件代码缺失的情况下重建组件层次结构,保持惰性。
仅为需要重新渲染的组件重建层次结构,实现细粒度懒加载。
收集 store 与组件的关系,创建订阅模型,序列化到 HTML,以便在状态变化时只更新受影响的组件。
Application state
Qwik 将状态以属性形式保存在 DOM 中,使得每个组件可以独立恢复,无需整体 Hydration。
将状态序列化为 HTML 后可在不同客户端反序列化并恢复,支持随时序列化和多次序列化。
优化
网络延迟下的点击事件是否卡顿?
Prefetching
Qwik 提供
prefetchStrategy方法进行 JS 预取,默认预取页面上所有可见节点的监听器,也可以自定义策略。
<code>export default function (opts) {<br/> return renderToStream(<Root />, {<br/> manifest,<br/> prefetchStrategy: { /* custom config */ },<br/> ...opts<br/> });<br/>}</code>One More Thing
Partytown 是 Builder.io 维护的轻量级工具,通过在 Web Worker 中运行第三方脚本来释放主线程资源,使用 Proxy、Service Worker 和同步 XHR 实现对 DOM API 的同步访问,从而减少第三方脚本导致的页面加载延迟。
它拦截对
window、
document、
localStorage等的访问,使用同步 XHR 与 Service Worker 进行通信,再通过
postMessage将结果返回主线程,实现与主线程相同的同步行为。
Watermelon Frontend Tech Team
We are from ByteDance, the frontend division of Watermelon Video, responsible for its product development. We share business practices from the product to provide valuable experience to the industry, covering areas such as marketing setups, interactive features, engineering capabilities, stability, Node.js, and middle‑back office development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.