React 17+

npm i fastify fastify-vite fastify-vite-react --save

Minimal Boilerplate

Below is a minimal script to boot a Fastify server with an integrated Vite app:

import Fastify from 'fastify'
import FastifyVite from 'fastify-vite'
import renderer from 'fastify-vite-react'

const root = import.meta.url
const app = Fastify({ logger: true })

await app.register(FastifyVite, { root, renderer })
await app.vite.commands()
await app.listen(3000)

With that, create a view at views/index.jsx:

export default function Index () {
  return <h1>Hello World</h1>

export const path = '/'


View files can be named anything, in the examples index.jsx is always associated to / as a convention.

And then, assuming you have saved the first snippet as app.mjs:

node app.mjs

Take note that root and renderer are fastify-vite's only required plugin options. The first is the Vite application root and the second determines what renderer adapter to use.


All examples in the documentation use ESM, but it's not required. If you use CJS, use __dirname instead of import.meta.url when setting the root option. It will know the difference.

Index HTML template

Vite's required index.html is provided by fastify-vite-react automatically on the first run if you don't provide one yourself. See the default template below:

<!DOCTYPE html>
<div id="app">${element}</div>
<script type="module" src="@app/entry/client.jsx"></script>

As you can probably imagine, these variable names cannot be changed because they are used by fastify-vite-react's internal rendering functions. The contents of index.html itself are compiled into a function loaded into memory for maximum performance.

The @app/ import prefix is used to load the client entry from the project blueprint provided by fastify-vite-vue. If you create a entry/client.jsx file at the root of your Vite application, that will be used instead. See Project Blueprint for more info on this works.

Blueprint Files

The fastify-vite-vue package will provide nearly all your starting boilerplate. The script where you actually register fastify-vite in your Fastify application being the only exception (you're expected to write it yourself). The files provided by fastify-vite-vue are listed below.

client.mjsMust export a createApp function returning a React application instance.
client.jsxMust export the main React component for your app.

That would be the one where you set a layout, a router view etc.
routes.jsMust have a default export with the Vite application's routes array.
entry/client.jsxVite application client entry point (DOM element mount).
entry/server.jsxVite application server entry point (render function and routes).
index.htmlVite application main entry point (loads client entry point).


To quote Evan You, "Vite requires .jsx extension for JSX processing [...] because in most cases plain .js files shouldn't need full AST transforms to work in the browser. Allowing JSX in .js files means every served file must be full-AST-processed just in case it contains JSX."

Global Data

You can also access Global Data via the context object returned by the useHydration hook.

import { useHydration } from 'fastify-vite-react/client'

export const path = '/global-data'

export default function GlobalData (props) {
  const [ctx] = useHydration()
  return <p>{JSON.stringify(ctx.$global)}</p>