服务器端渲染性能大比拼

原文信息: 查看原文查看原文

An SSR Performance Showdown

- Matteo Collina

服务器端渲染(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所需的最小样板代码,并比较五个主要前端库的性能:ReactVueSolidSveltePreact。我们还看了fastify-html(一个Fastify包装器,用于ghtml)和通过@fastify/viewejs,作为更简单的替代方案。

我们选择不考虑像Next.jsAstroQwik这样的工具,以及其他完整的框架,因为它们不提供孤立的渲染方法。

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

查看包含所有示例的仓库

确保一致性

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

  • 不使用客户端响应性特性。

  • 除非对于所讨论的框架不适当,否则所有样式绑定都是使用模板文字完成的,就像React和Solid的情况一样。

  • xy值是用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测试被添加为基线,以展示通过放弃完整的前端框架并坚持使用最小模板,可以获得多少性能提升。

分享于 2024-09-07

访问量 12

预览图片