在使用 Node.js 构建高性能 Web 应用程序时,服务器端渲染(SSR)是一个经常被忽视的方面。
在我担任顾问期间,许多工作都围绕着调试 Node.js 性能问题展开。在这些情况下,罪魁祸首几乎总是 SSR。SSR 是一种 CPU 密集型活动,很容易成为阻塞 Node.js 事件循环的主要原因。在选择前端堆栈时,考虑这一点至关重要。
我们着手了解当今最流行的库的 SSR 性能状况,特别是那些可以与 Fastify 干净集成的库。
为此,我们需要生成一个包含大量元素的非平凡示例文档,以便为测试提供非常大的页面,从而有更多的运行时间来捕捉每个库的性能。
因此,我们要求一名法学硕士编写一些代码,使用 div 作为 10x10px 的图块,在容器中绘制一个螺旋:
<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/view研究了fastify-html ( ghtml的 Fastify 包装器)和ejs以获得更简单的替代方案。
我们选择不考虑Next.js、Astro和Qwik等工具以及其他成熟的框架,因为它们不提供独立的渲染方法。
对于基于@fastify/vite的测试,我们使用了如下样板:
import Fastify from 'fastify'
import FastifyVite from '@fastify/vite'
const server = Fastify()
await server.register(FastifyVite, /* options */)
await server.vite.ready()
await server.listen({ port: 3000 })
所有测试都是针对生产版本运行的,即运行之后vite build
。
唯一的例外是fastify-html和ejs测试,它们不需要 Vite。
查看包含所有示例的存储库。
确保一致性
我们确保所有示例都具有相同的特征:
-
没有使用客户端反应性功能。
-
所有样式绑定都是使用模板文字完成的,除非对于所讨论的框架来说不合适,就像 React 和 Solid 的情况一样。
-
x和y值是使用toFixed(2)创建的。
-
除了文档外壳中的<style>标签外,没有其他标签。
测试在配备 8GB RAM 和 macOS Ventura 上的 Node v22 的 2020 MacBook Air M1 上运行。
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()
await server.register(fastifyHtml)
server.addLayout(createHtmlFunction(server))
作为参考,我们还添加了一个使用旧式EJS(基于@fastify/view )的测试。它每秒可以处理443 个请求。
Vue
排名第二,每秒可处理 1028 个请求,如果您想要出色的 SSR 性能并想要一个真正全面的库生态系统,Vue 可能是最好的选择。
使用的同步服务端渲染的 Vue API 是renderToString():
import { renderToString } from 'vue/server-renderer'
// ...
await server.register(FastifyVite, {
async createRenderFunction ({ createApp }) {
return async () => ({
element: await renderToString(createApp())
})
}
})
苗条
排名第三的Svelte 5(仍为预发布版)每秒处理高达 968 个请求,考虑到其丰富的功能集,这个成绩相当令人印象深刻。
Svelte 有自己的非 JSX模板语法,而且它的引擎似乎非常高效,如果你需要一个具有成熟库生态系统的框架并且不想在 SSR 性能上妥协,那么它是一个很好的选择。
使用的服务器端渲染的 Svelte API 是render(),从 Svelte 5 开始。
await server.register(FastifyVite, {
root: import.meta.url,
createRenderFunction ({ Page }) {
return () => {
const { body: element } = render(Page)
return { element }
}
}
})
请注意,该render()
函数还将返回头部和身体属性。
坚硬的
排名第四的是SolidJS,每秒处理 907 个请求。它落后于 Svelte 的幅度很小。Solid 是React 的一个非常有前途的替代品,但生态系统仍在不断成熟。
我们注意到的一件事是,SolidJS 实际上在使用 ID 作为其 hydration 过程的一部分时会遇到麻烦。分别比较 Vue 和 Solid 生成的标记:
<div class="tile" style="left: 196.42px; top: 581.77px">
<div data-hk=1c2397 class="tile" style="left: 196.42px; top: 581.77px">
这意味着很多性能损失都来自于需要通过网络发送的额外片段。尽管如此,我们还是想验证这一点:在正常的现实情况下,即启用客户端功能(例如 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)
}
}
}
})
预行动 冷库 www.cqzlsb.com
React 的受欢迎弟弟排在第五位,每秒处理 717 个请求。尽管 Preact 与 React 非常相似,但仍存在许多差异,使其速度更快、更轻量。
用于同步服务端渲染的 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 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())
}
}
}
})
总结
💡那么这些结果意味着什么?
在顶部,我们看到 fastify-html 和 Vue,其次是 Svelte 和 Solid。Vue 和 Svelte 可能在 SSR 性能和生态系统成熟度之间提供了最佳的平衡。
如前所述,fastify-html 测试是作为基线添加的,旨在展示通过放弃成熟的前端框架并坚持使用最少的模板可以获得的性能提升。