在过去的十年里,Web 开发领域一直试图说服自己:浏览器应该承担起所有的重任。我们从文档和表单开始,然后稳步地将各种 conceivable 的操作迁移到客户端。路由、状态管理、数据获取、渲染逻辑,甚至通过 GraphQL 进行的数据库查询编排——这一切都转移到了 JavaScript 包中,而这些包随着每次发布都变得越来越臃肿。框架层出不穷,构建流水线日益复杂,最初为了让应用感觉响应迅速而采取的做法,演变成了一种架构:在下载、解析并执行了数兆字节的代码之前,页面甚至无法渲染出任何有意义的像素。

这种转变解决了实际问题。带有少量 jQuery 点缀的服务端渲染页面,很难提供用户所期望的流畅、类 App 的过渡效果。单页应用(SPA)为我们带来了即时导航、持久状态和丰富的交互。但代价也在不断累积。团队现在需要管理复杂的客户端状态存储、处理庞大的 JavaScript 包、维护棘手的数据同步层,并调试那些有时感觉就像是一份全职工作般的构建流水线。我们用一套问题换取了另一套问题,现在许多开发者都在质疑,是否每个应用程序都需要支付这种“税收”。

有两个发展正在让这个问题变得更容易回答。

HTMX 与超媒体的回归

第一个是 HTMX。从表面上看,它似乎只是一个小型库,但其架构意义却非常重大。HTMX 将 HTML 视为应用逻辑的原生格式,而不是将其视为必须由 JavaScript 填充的静态外壳。

以下是实际应用中的变化。传统上,当用户点击按钮以加载更多评论时,前端会发出一个 fetch 请求,接收 JSON 负载,将其规范化到客户端存储中,通过组件模板运行,对虚拟 DOM 进行差异对比(diff),最后对页面进行补丁处理。HTMX 简化了这一链路。按钮本身包含属性,告诉浏览器将请求发送到哪里以及替换哪个页面元素。服务器返回一个 HTML 片段——仅仅是包裹在 div 中的新评论。浏览器将其直接替换进去。这里没有 JSON,没有前端状态树,没有协调算法,也不需要命令式 JavaScript 来保持 UI 与服务器同步。

这并不是对现代开发的否定,而是对不必要抽象的否定。HTMX 证明了超媒体(Hypermedia)——这种驱动早期 Web 的架构风格——在与现代易用性相结合时,仍然可以支持复杂的界面。任何元素都可以发起请求,而不仅仅是表单和链接。任何事件都可以触发更新。服务器仍然是数据和呈现的单一事实来源(Source of Truth)。

Chrome 的声明式局部更新

第二个转变更近,且存在于浏览器内部。Chrome 正在引入声明式局部更新(Declarative Partial Updates,简称 DPU)。该功能允许浏览器在字节到达时,流式传输 HTML 并将其直接插入页面的目标部分。

在 DPU 出现之前,如果你想向网页流式传输实时数据,通常需要使用 WebSockets、Server-Sent Events 或配合手动 DOM 操作的长轮询。前端必须管理连接、解析负载,并决定具体如何以及在哪里注入标记。DPU 通过使过程声明式化改变了这一局面。开发者只需指定一个目标容器,浏览器就会处理剩下的工作:接收流、解析片段,并在完整的响应关闭之前,将其准确地放置在应有的位置。

想象一下显示服务器日志的监控仪表板,或者实时更新的支持队列。有了 DPU,后端可以在生成时发送纯 HTML 块。浏览器将它们流式传输到表格主体或信息流容器中,而无需编写任何客户端流式传输逻辑。组装过程在原生层面完成。

服务端优先模型

将 HTMX 和 DPU 结合起来,你就会得到一个连贯的架构:服务器拥有状态并生成 UI,而浏览器处理显示和用户输入。像 Rails、Laravel、Django、Go templates 或 ASP.NET 这样的后端框架再次成为了主要的接口层。前端不再是一个消耗 API 的独立应用,而是服务器生成的超媒体接口。

这种模式适用于出人意料的广泛的软件领域。以典型的 SaaS 应用为例:它是带有可排序表格的仪表板;它是带有表单和过滤器的管理面板;它是将记录从一种状态转移到另一种状态的内部工具;它是展示列表、提供详情视图并允许用户编辑字段的 CRUD 工作流。它甚至包括 AI 界面,其中语言模型向用户流式传输 token,每个 token 或段落都可以封装在 HTML 中并追加到对话线程中。对于所有这些场景,厚重的 JavaScript 客户端通常是大材小用了。

其益处是立竿见影且实用的。初始页面加载更快,因为首次有效绘制(first meaningful paint)是以 HTML 形式呈现的,而不是在 hydration 周期完成后才出现。JavaScript 负载减小了,因为不需要传输 virtual DOM、客户端路由或状态管理库。搜索引擎无需执行 bundle 即可看到完整内容,因此 SEO 默认就能生效。复杂度降低了,因为一个代码库即可处理路由、业务逻辑和渲染。调试变得更容易。当出现问题时,你可以检查 Network 标签页,准确地看到服务器发送了哪些 HTML。不存在需要逆向工程的晦涩难懂的客户端状态对象。

那么 React 和重型客户端呢?

这并不意味着 React 已死,也不意味着 SPA 是个错误。复杂的编辑器级应用仍然需要厚客户端。Figma 在浏览器中运行编译为 WebAssembly 的 C++ 引擎,因为服务器往返(round-trips)会让绘图变得无法实现。Canva 利用客户端几何计算以每秒 60 帧的速度操作 canvas。Google Docs 使用 operational transforms 在毫秒级内解决编辑冲突。这些工具本质上是通过浏览器标签页交付的桌面应用程序。它们不会回归到服务器渲染的表单。

但大多数软件并不是 Figma。大多数软件并不是实时图形编辑器。大多数软件是报表界面、配置面板、预订流程或内容管理表单。对于那长尾的应用场景,仅仅为了切换一个模态框或获取记录列表就传输数百 KB 的 JavaScript 框架,从来都不太合理。技术栈的经济效益正在发生变化。我们正在重新发现,得益于边缘计算(edge),服务器可以非常接近用户;同时浏览器本身也已经足够强大,可以在无需框架中介每一个字节的情况下更新片段。

钟摆找到了平衡

Web 架构的轨迹正在向简单化摆动,但这并不是一种天真的向 90 年代的回归。浏览器正在变得越来越聪明。像 DPU 这样的特性并不会取代开发者的创造力;它们将我们过去需要手动实现的模式——流式传输、局部更新、定向 DOM 插入——吸收到了平台本身之中。HTMX 为我们提供了表达这些行为的“词汇”,而无需在前端重建一个微型操作系统。

你不再需要在简单的架构和响应式的用户体验之间做选择。你可以两者兼得。服务器可以驱动界面,浏览器可以组装界面,而你编写的 JavaScript 可以专注于真正的交互性,而不是处理琐碎的底层逻辑。

对于下一代的仪表板、管理工具和 AI 驱动的界面,最聪明的客户端可能就是那个“做得更少”的客户端。