服务器端渲染(SSR)是在构建高性能的Node.js Web应用程序时经常被忽视的一个方面。
在我担任咨询顾问期间,许多项目都集中在调试Node.js性能问题上。在这些情况下,罪魁祸首几乎总是SSR。SSR是一种CPU密集型活动,很容易成为阻塞Node.js事件循环的主要原因。在选择前端技术栈时,考虑这一点至关重要。
我们开始探索当今最受欢迎的库的SSR性能状态,特别是那些可以与Fastify干净集成的库。
为此,我们需要生成一个非平凡的样本文档,包括大量的元素,以便有一个非常大的页面进行测试,从而有更多的运行时间来捕捉每个库的性能。
因此,我们请一个大型语言模型(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)和通过@fastify/view的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>
标签。
测试是在2020年MacBook Air M1,8GB RAM,macOS Ventura上的Node v22上运行的。
fastify-html
我们从异常值开始:fastify-html,一个Fastify插件,包装了ghtml,每秒提供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
排在第三位的是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()
函数还将返回头部和主体属性。
Solid
排在第四位的是SolidJS,交付了每秒907个请求。它与Svelte的差距非常小。Solid是React的一个非常有希望的替代品,但生态系统仍在成熟中。
我们注意到的一件事是,SolidJS实际上因为在其水合过程中使用ID而受到影响。比较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">
这意味着很多性能损失来自于需要通过线路发送的这个额外片段。尽管如此,我们还是想验证一下:框架在正常、现实世界的情况下会如何表现,这意味着拥有客户端设施,如水合功能。
对于样板代码,我们使用了@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的受欢迎的小弟排在第五位,交付了每秒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
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测试被添加为基线,以展示通过放弃完整的前端框架并坚持使用最小模板,可以获得多少性能提升。