Remember the SSR Performance Showdown? And Theo's coverage of it?
Now that @fastify/vue and @fastify/react reached 1.0.0
, it's time for a revisit.
Especially because we didn't test metaframeworks back then. We were just looking at the raw performance of frontend frameworks.
Now, how do Vue and React fare when used in Fastify, Nuxt and Next?
Let's find out.
Creating the Next version
For the Next version, I just used create-next-app
with most default settings, the App Router, and then copied over the spiral code from the vanilla React benchmark. I also had to make the HTML shell a React component:
export default function RootLayout ({ children }) {
return (
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
And finally, I had to export dynamic
set to force-dynamic
from my route module, otherwise Next will perhaps correctly identify it as a static component, and prerender it at build time. This is necessary to ensure even a simple component like this gets rendered dynamically every time:
export const dynamic = 'force-dynamic'
export default function Home () {
const wrapperWidth = 960
const wrapperHeight = 720
const cellSize = 10
const centerX = wrapperWidth / 2
const centerY = wrapperHeight / 2
Creating the @fastify/react version
For the @fastify/react
version, all I had to do was copy over the original code from the vanilla React example and update server.js
and vite.config.js
to use @fastify/react
. If you compare these folders, you'll see no other change.
Note that in the original version, @fastify/vite
was used directly. In this version we're using @fastify/vite
with @fastify/react
, which is just a renderer package for the former. The only difference is that @fastify/react
, like Next, bundles an application shell some framework-y features.
Creating the Nuxt version
For the Nuxt version, like the Next version, I just used create-nuxt-app with most default settings. Then again, I copied over the code from the original Vue example and I also had to turn the HTML shell into a Vue component:
<template>
<Head>
<Meta charset="utf-8" />
<Meta name="viewport" content="width=device-width, initial-scale=1.0" />
<Style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
margin: 0;
}
Note how you have to use Nuxt's special <Head>
, <Meta>
and <Style>
components, which presumably use useHead()
under the hood.
Creating the @fastify/vue version
For the @fastify/vue
version, I followed the same steps as I did for the @fastify/react
version, copying over the spiral code from the original Vue example and updating server.js
and vite.config.js
to use @fastify/vue
.
Results

This is what I'm seeing:
- @fastify/vue:
717
requests per second - Nuxt:
561
requests per second - @fastify/react:
347
requests per second - Next:
49
requests per second
Which basically means @fastify/react
is 7x faster than Next.js.
I am, however, a bit surprised with that result. What is it this time? Next automatically sets NODE_ENV
to production
, so that can't be it. Where is the overhead coming from? Could it be the App Router and its requirement of having a React component as HTML shell? Nuxt does the same and performs well.
I am genuinely confused. Take a look at the source, let me know if I could be doing anything differently, but I don't mean special optimizations — the point was to test the vanilla setup of these frameworks.
Also, this is of course not a fair comparison. With @fastify/vue
and @fastify/react
, you get the absolute minimum necessary. They're as minimal as it can get. Nuxt and Next are behemoths that do and account for everything.
They're Swiss Army knives.
At some point things get big enough that they stop being tools, and become products. You're supposed to embrace the Next way or the Nuxt way, and that's fair, but it comes at a cost. Suddenly you're using something that is prepared for all edge cases, includes all batteries and then some, when in most cases you just need a tiny piece of it — the seamless transition from CSR to SSR — the magic that happens when you SSR some client code and when it loads on the browser, it starts working as an SPA. In the end, this simple thing is the main functionality of all frameworks. Coupled with a small number of other goodies, that's what @fastify/vue
and @fastify/react
provide.
Update: Theo breaks it down.