热门前端 SSR 框架性能大比拼:React 倒数第一?

来源于code秘密花园 ,作者ConardLi

Web 技术的蓬勃发展,服务器端渲染(SSR)逐渐成为了当下前端页面追求高性能的主流技术,用 SSR 构建的项目往往首屏渲染会非常丝滑。但是 SSR 渲染也有可能带来一些其他的性能问题。

我之前做过的很多项目都围绕调试 Node.js 性能问题展开。在这些场景中,导致性能问题的罪魁祸首基本上都是 SSRSSR 是一种占用 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> 元素:

图片

FastifyVite 集成设置是检验各种框架 SSR 性能的理想测试平台。

下面,我们将实现执行 SSR 所需的最少样板代码,并比较五大前端库的性能:React、Vue、Solid、SveltePreact

同时,我们还测试了 fastify-html(一个 Fastify 封装的 ghtml)和 ejs 通过 @fastify/view 提供的简单替代方案。

我们本次的测试不考虑像 Next.js、AstroQwik 等工具,以及其他完整的框架,因为它们不提供单独的渲染方法。

对于基于 @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-htmlejs 测试,它们不需要 Vite

下面是包含所有示例的仓库:https://github.com/platformatic/ssr-performance-showdown

保证一致性

我们确保所有示例都具有相同的特征:

  • 不使用客户端的响应式特性;
  • 所有样式绑定都使用模板字面量,除非对于具体框架不合适,例如 React 和 Solid;
  • xy 值都使用 toFixed(2) 创建;
  • 除文档壳中的 <style> 标签外,不使用其他 <style> 标签。

测试是在 2020 款 MacBook Air M1 上运行的,配置为 8GB RAM,操作系统为 macOS Ventura,Node.js 版本为 v22。

fastify-html

图片

我们首先介绍一个特例:fastify-html,这是一个封装了 ghtmlFastify 插件,每秒可以处理 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 APIrenderToString()

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 5render()

  await server.register(FastifyVite, {
    root: import.meta.url,
    createRenderFunction ({ Page }) {
      return () => {
        const { body: element } = render(Page)
        return { element }
      }
    }
  })

注意,render() 函数还会返回 headbody 属性。

Solid

图片

排名第四的是 SolidJS,每秒处理 907 个请求。它仅以微小的差距落后于 Svelte

Solid 是一个非常有前景的 React 替代品,但其生态系统仍在成长中。

我在测试的时候注意到 SolidJS 在其 hydration 过程中使用 ID 其实会导致一些性能问题。对比 VueSolid 生成的标记如下:

<!-- 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/vitecreateRenderFunction 钩子来捕获 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 个请求。尽管 PreactReact 非常相似,但很多细节上的差异使得 Preact 更快且更轻量。

图片

用于同步服务器端渲染的 Preact APIrenderToString()

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 APIrenderToString()

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
THE END