热门前端 SSR 框架性能大比拼:React 倒数第一?
来源于code秘密花园 ,作者ConardLi
Web 技术的蓬勃发展,服务器端渲染(SSR)逐渐成为了当下前端页面追求高性能的主流技术,用 SSR 构建的项目往往首屏渲染会非常丝滑。但是 SSR 渲染也有可能带来一些其他的性能问题。
我之前做过的很多项目都围绕调试 Node.js
性能问题展开。在这些场景中,导致性能问题的罪魁祸首基本上都是 SSR
。SSR
是一种占用 CPU
的活动,很容易成为阻塞 Node.js
事件循环的主要原因。
所以,我对当今最流行的前端库在 SSR 性能方面的现状进行了测试。为此,我构建了一个包含大量元素的样本网站,然后捕获每个库的性能表现。
因此,我们请一个 LLM 编写了一些代码,在容器中使用 div
元素作为 10x10
像素的瓷砖来绘制螺旋:
<script>
const wrapper = document.getElementById('wrapper')
const width = 960
const height = 720
const cellSize = 5
function drawSpiral() {
let centerX = width / 2
let centerY = height / 2
let angle = 0
let radius = 0
const step = cellSize
while (radius < Math.min(width, height) / 2) {
let x = centerX + Math.cos(angle) * radius
let y = centerY + Math.sin(angle) * radius
if (x >= 0 && x <= width - cellSize && y >= 0 && y <= height - cellSize)
{
const tile = document.createElement('div')
tile.className = 'tile'
tile.style.left = `${x}px`
tile.style.top = `${y}px`
wrapper.appendChild(tile)
}
angle += 0.2
radius += step * 0.015
}
}
drawSpiral()
</script>
随后,我们要求它使用我们计划测试的所有库创建相应版本,并将实现适配为使用每个库的渲染引擎,而不是依赖于原始示例的 DOM 方法。
这是我们的样本文档的样子,包括所有的 2398 个 <div>
元素:
Fastify
与 Vite
集成设置是检验各种框架 SSR
性能的理想测试平台。
下面,我们将实现执行 SSR
所需的最少样板代码,并比较五大前端库的性能:React、Vue、Solid、Svelte
和 Preact
。
同时,我们还测试了 fastify-html
(一个 Fastify
封装的 ghtml
)和 ejs
通过 @fastify/view
提供的简单替代方案。
我们本次的测试不考虑像 Next.js、Astro
和 Qwik
等工具,以及其他完整的框架,因为它们不提供单独的渲染方法。
对于基于 @fastify/vite
的测试,我们使用了如下所示的样板代码:
import Fastify from 'fastify'
import FastifyVite from '@fastify/vite'
const server = Fastify()
// 注册 FastifyVite 插件
await server.register(FastifyVite, /* options */)
// 等待 Vite 准备就绪
await server.vite.ready()
// 监听 3000 端口
await server.listen({ port: 3000 })
所有测试都是在生产构建后运行的,也就是说,在运行 vite build
之后。
唯一的例外是 fastify-html
和 ejs
测试,它们不需要 Vite
。
下面是包含所有示例的仓库:https://github.com/platformatic/ssr-performance-showdown
保证一致性
我们确保所有示例都具有相同的特征:
-
不使用客户端的响应式特性; -
所有样式绑定都使用模板字面量,除非对于具体框架不合适,例如 React 和 Solid; -
x
和y
值都使用toFixed(2)
创建; -
除文档壳中的 <style>
标签外,不使用其他<style>
标签。
测试是在 2020 款 MacBook Air M1 上运行的,配置为 8GB RAM,操作系统为 macOS Ventura,Node.js 版本为 v22。
fastify-html
我们首先介绍一个特例:fastify-html
,这是一个封装了 ghtml
的 Fastify
插件,每秒可以处理 1088
个请求。就像前面所说的,这种设置不同于其他设置,因为它不需要 Vite
,因为不需要特殊语法或转换。
fastify-html
被添加到测试中作为一个基准。由于它只是一个简单的 HTML 模板库的封装,没有其他库的高级特性,所以它与其他库的对比并不是非常恰当。基于它的简单特性,我们已经预期它会表现得更好,并想看看其他功能齐全的库与它相比会差多少。
下面是我们使用的样板代码 — createHtmlFunction
(模拟 @fastify/vite
)用于注册渲染文档壳的布局函数:
import Fastify from 'fastify'
import fastifyHtml from 'fastify-html'
import { createHtmlFunction } from './client/index.js'
const server = Fastify()
// 注册 fastify-html 插件
await server.register(fastifyHtml)
// 增加布局函数
server.addLayout(createHtmlFunction(server))
作为参考,我们还添加了一个使用旧版 EJS
(基于 @fastify/view
)的测试,它每秒可以处理 443
个请求。
Vue
位居第二,如果您想要出色的 SSR
性能并想要一个真正全面的库生态系统,Vue
每秒可处理 1028
个请求,它可能是最好的选择。
用于同步服务器端渲染的 Vue API
是 renderToString()
:
import { renderToString } from 'vue/server-renderer'
// ...
await server.register(FastifyVite, {
async createRenderFunction ({ createApp }) {
return async () => ({
element: await renderToString(createApp())
})
}
})
Svelte
位居第三的是 Svelte 5
(仍是 pre-release
版本)每秒发送高达 968
个请求,考虑到其丰富的功能集,这个表现还是相当不错的。
Svelte
拥有自己的非 JSX
模板语法,并且其引擎非常高效,如果你需要一个具有成熟库生态系统的框架并且不想在 SSR
性能上妥协,那么它也是一个绝佳的选择。
用于服务器端渲染的 Svelte API
是来自 Svelte 5
的 render()
。
await server.register(FastifyVite, {
root: import.meta.url,
createRenderFunction ({ Page }) {
return () => {
const { body: element } = render(Page)
return { element }
}
}
})
注意,render()
函数还会返回 head
和 body
属性。
Solid
排名第四的是 SolidJS
,每秒处理 907 个请求。它仅以微小的差距落后于 Svelte
。
Solid
是一个非常有前景的 React
替代品,但其生态系统仍在成长中。
我在测试的时候注意到 SolidJS
在其 hydration
过程中使用 ID
其实会导致一些性能问题。对比 Vue
和 Solid
生成的标记如下:
<!-- Vue 生成的标记 -->
<div class="tile" style="left: 196.42px; top: 581.77px"></div>
<!-- Solid 生成的标记 -->
<div data-hk=1c2397 class="tile" style="left: 196.42px; top: 581.77px"></div>
这意味着性能开销很大一部分来自于这些需要传输的额外片段。不过,我们希望验证的也正是这一点:在常见、真实的使用情况下(启用 hydration
客户端功能)框架的表现。
在样板代码中,我们使用了 @fastify/vite
的 createRenderFunction
钩子来捕获 Solid
组件函数(createApp
):
import { renderToString } from 'solid-js/web'
// ...
await server.register(FastifyVite, {
root: import.meta.url,
createRenderFunction ({ createApp }) {
return () => {
return {
element: await renderToString(createApp)
}
}
}
})
Preact
React
的 “弟弟” Preact
排名第五,每秒处理 717
个请求。尽管 Preact
与 React
非常相似,但很多细节上的差异使得 Preact
更快且更轻量。
用于同步服务器端渲染的 Preact API
是 renderToString()
:
import { renderToString } from 'preact-render-to-string'
// ...
await server.register(FastifyVite, {
root: import.meta.url,
createRenderFunction ({ createApp }) {
return () => {
return {
element: renderToString(createApp())
}
}
}
})
React
React 19 RC
排名第六,每秒处理 572
个请求。
用于同步服务器端渲染的 React API
是 renderToString()
:
import { renderToString } from 'react-dom/server'
// ...
await server.register(FastifyVite, {
root: import.meta.url,
createRenderFunction ({ createApp }) {
return () => {
return {
element: renderToString(createApp())
}
}
}
})
总结
-
参考:https://blog.platformatic.dev/ssr-performance-showdown