前端框架 SSR 性能横向测评

457 阅读6分钟

原文链接:blog.platformatic.dev/ssr-perform…

服务器端渲染(SSR)是使用 Node.js 构建高性能网络应用时经常被忽视的一个方面。

在我从事咨询工作期间,许多业务都是围绕调试 Node.js 性能问题展开的。在这些情况下,罪魁祸首几乎总是 SSR。SSR 是一种占用 CPU 的活动,很容易成为阻塞 Node.js 事件循环的主要原因。在选择前端堆栈时,考虑这一点至关重要。

我们开始了解当今最流行的 SSR 库的性能状况,尤其是那些可以与 Fastify 干净集成的库。

为此,我们需要生成一个包含大量元素的非繁琐样本文档,为测试提供一个非常大的页面,从而有更多的运行时间来捕捉每个库的性能。

因此,我们请 LLM 编写一些代码,使用 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> 元素:

image.png

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()
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> 标记外,无其他 <style> 标记。

测试在配备 8GB 内存的 2020 MacBook Air M1 和 macOS Ventura 上的 Node v22 上进行。

fastify-html

我们先来看看例外情况:Fastify-html 是一个封装 ghtml 的 Fastify 插件,每秒可发送 1088 个请求。如前所述,这种设置与其他设置的不同之处在于它不需要 Vite,因为不需要特殊的语法或转换。

image.png

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

Vue 以每秒 1028 次请求的速度位居第二,如果你希望获得出色的 SSR 性能和真正全面的库生态系统,Vue 可能是最划算的选择。

image.png

用于同步服务器端渲染的 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 性能上打折扣,那么它将是一个极佳的选择。

image.png

用于服务器端渲染的 Svelte API 是 render() ,来自 Svelte 5。

      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 在水合过程中使用了 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">

这意味着大部分性能损失都来自于需要通过线路发送的额外片段。尽管如此,我们还是想验证这一点:框架在正常、真实环境下的表现,这意味着要启用水合等客户端设施。

image.png

对于模板,我们使用 @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 非常相似,但仍有许多不同之处,使其速度更快、更轻量级。

image.png

用于同步服务器端渲染的 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 个请求。

image.png

用于同步服务器端渲染的 React API 是 renderToString() :

    import { renderToString } from 'react-dom/server'

    // ...

    await server.register(FastifyVite, {
      root: import.meta.url,
      createRenderFunction ({ createApp }) {
        return () => {
          return {
            element: renderToString(createApp())
          }
        }
      }
    })

总结

image.png

💡 那么,这些结果意味着什么呢?

排名第一的是 fastify-html 和 Vue,紧随其后的是 Svelte 和 Solid。Vue 和 Svelte 可能是 SSR 性能和生态系统成熟度之间的最佳权衡。

如前所述,fastify-html 测试是作为基线添加的,其目的是为了展示摒弃成熟的前端框架并坚持使用最小模板所能获得的性能。