Project Structure
A conscious effort was made to make @fastify/react
applications feel familiar to users of Nuxt.js and Next.js, especially on the matter of route registration.
A minimal @fastify/react
project may look like the following:
├── server.js
├── client/
│ ├── index.js
│ ├── index.html
│ └── pages/
│ └── index.jsx
├── vite.config.js
└── package.json
├── server.js
├── client/
│ ├── index.js
│ ├── index.html
│ └── pages/
│ └── index.jsx
├── vite.config.js
└── package.json
There are of course many other boilerplate files that comprise the setup, but they don't need to exist in your project directory for your application to run.
If they do exist, they override the defaults. This is made possible via smart imports, covered later in this document. It is a clean and straightforward way to avoid a massive number of boilerplate files for projects who do well with the defaults, while still easily allowing for customization and extensibility.
Essential files
A @fastify/react
project must have at the very least:
Export | Description |
---|---|
| A Fastify server file that registers Or a plugin file if running |
| Your Vite application configuration. file It needs to import and register |
| The Vite application module loaded by It must export an object with These are detailed further below on this page. |
| The Vite application HTML template: html
It needs to have And it must import |
| Your application's route modules. If you don't have any, no client routes are rendered. |
Explore the main files in the various tabs of the snippet below:
import Fastify from 'fastify'
import FastifyVite from '@fastify/vite'
const server = Fastify({
logger: {
transport: {
target: '@fastify/one-line-logger',
}
}
})
await server.register(FastifyVite, {
root: import.meta.url,
renderer: '@fastify/react',
})
await server.vite.ready()
await server.listen({ port: 3000 })
import Fastify from 'fastify'
import FastifyVite from '@fastify/vite'
const server = Fastify({
logger: {
transport: {
target: '@fastify/one-line-logger',
}
}
})
await server.register(FastifyVite, {
root: import.meta.url,
renderer: '@fastify/react',
})
await server.vite.ready()
await server.listen({ port: 3000 })
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
import viteReact from '@vitejs/plugin-react'
import fastifyReact from '@fastify/react/plugin'
const path = fileURLToPath(import.meta.url)
export default {
root: join(dirname(path), 'client'),
plugins: [
viteReact(),
fastifyReact(),
],
}
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
import viteReact from '@vitejs/plugin-react'
import fastifyReact from '@fastify/react/plugin'
const path = fileURLToPath(import.meta.url)
export default {
root: join(dirname(path), 'client'),
plugins: [
viteReact(),
fastifyReact(),
],
}
import routes from '/:routes.js'
import create from '/:create.jsx'
export default {
context: import('/:context.js'),
routes,
create,
}
import routes from '/:routes.js'
import create from '/:create.jsx'
export default {
context: import('/:context.js'),
routes,
create,
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="./base.css">
<!-- head -->
</head>
<body>
<div id="root"><!-- element --></div>
</body>
<!-- hydration -->
<script type="module" src="/:mount.js"></script>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="./base.css">
<!-- head -->
</head>
<body>
<div id="root"><!-- element --></div>
</body>
<!-- hydration -->
<script type="module" src="/:mount.js"></script>
</html>
import logo from '/assets/logo.svg'
export function getMeta () {
return {
title: 'Welcome to @fastify/react!'
}
}
export default function Index () {
const message = 'Welcome to @fastify/react!'
return (
<>
<p>{message}</p>
<img src={logo} />
</p>
)
}
import logo from '/assets/logo.svg'
export function getMeta () {
return {
title: 'Welcome to @fastify/react!'
}
}
export default function Index () {
const message = 'Welcome to @fastify/react!'
return (
<>
<p>{message}</p>
<img src={logo} />
</p>
)
}
This example is actually provided as the react-base starter.
In this example, server.js
is the Fastify server and also the place where both @fastify/vite
and @fastify/react
are imported to set up your application.
Like in any @fastify/vite
application, client/index.js
are the portions of your client code that get loaded by the server. It exports your application's factory function (create
), the application routes and the route context initialization module, all loaded via smart imports, covered later on this page.
Notice that client/index.html
needs to exist as the front-and-central entry point of your application, and @fastify/react
has its own structure for it.
Also notice that in vite.config.js
, @fastify/react/plugin
needs to be registered so that smart imports can work.
Smart imports
What you saw above already is the minimal boilerplate for a fully functioning @fastify/react
application. As you can imagine though, there's a lot going on under the hood. What makes just placing files under pages/
work to get them loaded as route modules? How is the application mounted on the client? Where is the hydration logic covered in the @fastify/vite
examples?
The core files of @fastify/react
that make all of that (and a bit more) work don't have to exist in your project directory, but are loaded nonetheless:
├── server.js
├── client/
│ ├── core.jsx
│ ├── create.jsxx
│ ├── mount.js
│ ├── resource.js
│ ├── root.jsx
│ ├── context.js
│ ├── index.js
│ ├── index.html
│ ├── layouts/
│ │ └── default.jsx
│ └── pages/
│ └── index.jsx
├── vite.config.js
└── package.json
├── server.js
├── client/
│ ├── core.jsx
│ ├── create.jsxx
│ ├── mount.js
│ ├── resource.js
│ ├── root.jsx
│ ├── context.js
│ ├── index.js
│ ├── index.html
│ ├── layouts/
│ │ └── default.jsx
│ └── pages/
│ └── index.jsx
├── vite.config.js
└── package.json
The way this works is via the /:
prefix.
Notice how client/index.html
imports the React application mounting script from /:mount.js
, and client/index.js
loads routes from /:routes.js
, the application factory function from /:create.jsx
and the route context initialization module from /:context.js
.
What this prefix does is first check if the file exists in your Vite project root directory, and if not, provide the default versions stored inside the @fastify/react
package instead.
Below is a quick rundown of all smart imports available.
Smart import | Description |
---|---|
| This is used by It also exports the |
| Where your React application factory function is exported from. It must be named |
| The Vite application mount script, imported by |
| Utilities to enable suspensed state in data fetching. |
| The main React component for your application. |
| The route context initialization file. |
| The default route layout component. |
They are covered in more detail with the contents of the actual default files in the Virtual Modules section of the Configuration section.
The graph below indicates the relationships between them:
Special directories
Even though this is implied in previous references in the documentation, for completeness sake, below is a list of all the special directories in @fastify/react
applications that are processed differently and in an automated fashion.
Special directory | Description |
---|---|
| Default route module search location. |
| Files are made available as route layout components. |