@fastify/nextjs: React server-side rendering inside Fastify
React server side rendering support for Fastify with Next
At a glance
- What is it?
- This Fastify plugin mounts Next.js inside an existing Fastify server so that React pages are rendered by the same process that serves your API. The README is careful about the boundary: the plugin lets a Fastify server use Next.js, it does not replace Next's own web server with Fastify. Routes have to be declared after the plugin registers, and the plugin carries a load-shedding option because rendering blocks the event loop.
- Who is it for?
- @fastify/nextjs suits a team that already runs Fastify and wants to add React pages to it without running a second Node process, and who specifically want the same server to own routing, plugins and request state for both the API and the pages.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the plugin does, and the boundary it draws
@fastify/nextjs is React server-side rendering support for Fastify with the Next.js framework, and the first paragraph of the README sets the scope in a way that saves a lot of confusion.
The library is for letting an existing Fastify server utilise Next.js. It is not for replacing Next.js' internal web server with Fastify. Those are different projects with different aims: one keeps a Fastify server and adds React pages to it, the other takes over the whole server layer of a Next application.
So the scenario this fits is a service that has an API and a set of pages, where you want them in one process. The Fastify instance keeps its own routes, its plugins and its lifecycle, and the Next.js pages are registered onto it as additional routes, rendered by the Next instance the plugin exposes.
The package is published on npm as @fastify/nextjs, it is MIT licensed, and the primary language of the repository is JavaScript. The repository topics are fastify, fastify-plugin, next, react and ssr, and the fastify-plugin tag matters because it means the plugin is encapsulated in the Fastify sense: it registers a decorator in its own scope rather than polluting the parent instance.
The code style is standard, the badge says js-standard-style, and the lint script runs standard. The package publishes type definitions at types/index.d.ts, and the test setup includes tstyche for type level tests and c8 for coverage.
The release history is worth noting before you plan around it. v10.0.1 was published in July 2023, v11.0.0 in September 2024, and v11.0.1 on 2026-10-09. So there is one minor version behind, which usually means a peer dependency bump rather than a feature change, and a project on a 0.x-free but slowly moving line.
Install, and the after() rule that catches everybody
The install is one command:
npm i @fastify/nextjs next react react-domNext.js has to be installed alongside, since the plugin drives a Next instance rather than bundling one.
The first rule of using the plugin is in the usage section and it is not optional. Since Next.js needs some time to be ready on the first launch, you must declare your routes inside the after callback, after you registered the plugin. The plugin exposes the next API in Fastify that handles the rendering for you.
const fastify = require('fastify')()
fastify
.register(require('@fastify/nextjs'))
.after(() => {
fastify.next('/hello')
})
fastify.listen({ port: 3000 }, err => {
if (err) throw err
console.log('Server listening on http://localhost:3000')
})The reason is the plugin registration lifecycle. Fastify waits for a plugin to be ready before it starts, and Next.js is not ready during the registration of this plugin because it needs to boot its own server-side machinery first. Declaring the routes in after tells Fastify that the plugin has finished and the routes can now be attached.
The failure mode when you get it wrong is covered further down in the README, and it is not a subtle one: a plugin timeout error, because Fastify is still waiting on a plugin that has not signalled completion.
The server pages themselves live in a folder named pages, as the Next.js documentation for a custom server requires, and the example in the README is a page component that renders hello world.
Three ways to render, from automatic to fully manual
The next method takes a path and, optionally, a callback, and the three documented uses cover most of what an application needs.
The simplest is fastify.next('/hello'), which registers the route and lets the plugin do the rendering.
The second is for when you want to handle the render part yourself. You pass a callback and you get the Next instance as the first argument, and the example shows the full call: app.render(req.raw, reply.raw, '/hello', req.query, {}). The raw request and reply are passed because Next's render function expects Node's raw objects, and the query is passed because Next's server-side props machinery reads the query from it.
The third is for rendering from inside a custom handler, where the reply object carries the helpers. reply.nextRender('/a') renders a page from a handler, and reply.nextRenderError(err) renders the Next.js error page, and the example shows the error form used with a status code taken from the error itself:
app.setErrorHandler((err, req, reply) => {
return reply.status(err.statusCode || 500).nextRenderError(err)
})That error handler pattern is the piece that matters for a real application, because it means an exception thrown anywhere in your Fastify code can end up on a Next error page instead of a bare JSON error.
There is one more option that is easy to miss. For HEAD routes you define the HTTP method explicitly, with fastify.next('/api/*', { method: 'GET' }) and a separate call for { method: 'HEAD' }. Registering a wildcard path for an API route alongside the pages is a common shape, and the method option is what keeps the two from colliding.
Custom options for Next itself go to the plugin registration as a second parameter, so fastify.register(require('@fastify/nextjs'), { dev: true }) is how you turn on development mode, and the README points at the Next.js custom server documentation for the rest.
Asset forwarding, the load shedder and the plugin timeout
Three operational details are documented in detail, and each of them corresponds to a problem you will hit.
The first is asset serving. By default the plugin handles the route ${basePath}/_next/* and forwards those requests to Next.js, which is where the JavaScript chunks and stylesheets of a rendered page come from. If you have custom preprocessing for _next/* requests, you can prevent this handling with the noServeAssets: true property, and then register the path yourself with a callback that uses the Next request handler and calls reply.hijack() once the response is done, so that Fastify does not try to reply again.
The second is under-pressure. The plugin includes the under-pressure module, which can be configured by providing an underPressure property to the plugin options, and the available options are the same as those accepted by under-pressure. The logic behind it is sound: because React server side rendering is a blocking operation for the Node.js server, returning an error to the client allows signalling that the server is under too much load. That is a circuit breaker, and in an SSR workload it is a reasonable design choice rather than a pessimism.
The option takes three shapes. When false, which is the default, under-pressure is not registered at all. When true, it is registered with default options. When it is an object, it is registered with the options you provide, and the example uses that form with exposeStatusRoute: true so the load state can be read from a status route.
The third is the plugin timeout, and it is the mistake the README is most emphatic about. The default timeout for plugins in Fastify is 10000ms, which can be a problem for huge Next.js projects where the initial build time is higher than that. The error you get is ERR_AVVIO_PLUGIN_TIMEOUT: plugin did not start in time. The workaround is to raise the timeout, and the README shows the pattern of doing it only in development:
const isDev = process.env.NODE_ENV !== 'production';
const fastify = Fastify({ pluginTimeout: isDev ? 120_000 : undefined });The point of conditioning on NODE_ENV is that the delay is a development build cost, and a production deployment should not carry a five minute plugin timeout.
Sharing plugins and request state with the React side
The feature that makes this integration more than a page renderer is the ability to share objects with the page request, and the README demonstrates it with a Redis plugin instance.
If you want to share custom objects, for example other Fastify plugin instances such as @fastify/redis, across the server and the client with each page request, you can use the onRequest hook to add it to the request object. The example registers @fastify/redis, registers the Next.js plugin, and then adds an onRequest hook that sets a custom property on request.raw and makes the redis instance available as request.raw.redisInstance.
Two comments in the example explain the design choices. The first says that for performance reasons the hook should not run on every request, and only the Next.js one should run, and the second example registers a scoped plugin with a /hello prefix so the hook applies only to that subtree.
On the client side, the object arrives through the request, and the example shows it being read in getServerSideProps from ctx.req.customProperty and ctx.req.redisInstance and passed into props.
This is the practical difference between running Next.js on its own and running it inside Fastify. In a separate Next server, the React code has no access to the Fastify instance graph, so a database pool or a cache client has to be created again on the other side, with its own configuration and its own connection. Here the same instance is used on both sides of the render, which removes a duplicated configuration and a second pool of connections.
Where this fits, and what the alternative arrangement costs
There is a real alternative to this arrangement, and it is the one most projects start with.
The alternative is to run Next.js on its own server, using its own custom server, and to run Fastify as a separate API process, with the browser calling the API over the network. The difference in approach is process and configuration: here there is one Node process that owns routing, plugins and lifecycle for both the API and the pages, and a page can reach the API's own plugin instances in memory. In the split arrangement there are two processes, two configurations, and the page has to reach the API over HTTP, which is slower per request and duplicates client configuration on both sides.
That difference only matters if you have both an API and pages. If your application is only pages, this plugin buys nothing over the Next.js custom server and adds a dependency. If your application is only an API, it is irrelevant.
The repository layout supports the reading that this is a small, focused plugin. There is index.js at the root, a pages/ directory for the example pages, test/, types/, an examples/example.js file, a .c8rc for coverage configuration, and the standard configuration files. The npm scripts show how it is developed: build runs next build, build:prod runs it with NODE_ENV set to production through cross-env, dev runs the example server, serve builds for production and then runs the example, and the test script runs the unit tests and the TypeScript type tests in turn.
One line in the README is worth flagging for anyone contributing: CI currently runs npm@6, so when upgrading packages, please use this version. That is an unusual constraint to see in a package published in 2026, and it suggests the build pipeline is older than the package.
The project is sponsored by NearForm, with LetzDoIt listed as a past sponsor, and it is MIT licensed.
Editorial conclusion
@fastify/nextjs suits a team that already runs Fastify and wants to add React pages to it without running a second Node process, and who specifically want the same server to own routing, plugins and request state for both the API and the pages. It does not suit a project whose Next.js application is the whole product, since the plugin is explicitly not a way to run Next on Fastify, and it does not suit somebody who wants the latest Next.js features, given the release history has one minor bump since 2024. Check three things before you adopt it: that you declare your routes inside the after callback, since Next.js is not ready at plugin registration time and the failure otherwise is a timeout, whether the pluginTimeout value suits the size of your Next build, since the default of 10000 ms is too short for a large development build, and whether you need the under-pressure circuit breaker, which returns an error to the client rather than queueing work the event loop cannot run. The last push to the repository was on 2026-10-14 and the current release is v11.0.1.
Frequently asked questions
What does @fastify/nextjs do?
It adds React server-side rendering support to an existing Fastify server using Next.js, exposing a next API on the Fastify instance to register pages as routes. The README stresses that it is for letting a Fastify server use Next.js, not for replacing Next.js' internal web server with Fastify.
How do I install @fastify/nextjs?
With npm i @fastify/nextjs next react react-dom, since the plugin drives a Next.js instance you install yourself. The package is MIT licensed and published as @fastify/nextjs.
Why must routes be declared inside the after callback?
Because Next.js needs some time to be ready on the first launch, so the routes must be declared in the after callback after the plugin is registered. The plugin then exposes the next API in Fastify that handles the rendering.
What is the ERR_AVVIO_PLUGIN_TIMEOUT error in @fastify/nextjs?
It comes from the default Fastify plugin timeout of 10000ms being too short for a large Next.js development build. The fix is to raise it, and the README shows Fastify({ pluginTimeout: isDev ? 120_000 : undefined }) so the longer timeout applies only outside production.
What does the underPressure option do in @fastify/nextjs?
The plugin includes the under-pressure module, and the option accepts false to leave it unregistered, true to register it with default options, or an object with the options to use. Because server side rendering blocks the Node.js server, it can return an error to the client when the server is under too much load.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/fastify-fastify-nextjs)Was this useful?