Skip to content

Project Structure

A conscious effort was made to make @fastify/vue applications feel familiar to users of Nuxt.js and Next.js, especially on the matter of route registration.

A minimal @fastify/vue project may look like the following:

text
├── server.js
├── client/
│    ├── index.js
│    ├── index.html
│    └── pages/
│          └── index.vue
├── vite.config.js
└── package.json
├── server.js
├── client/
│    ├── index.js
│    ├── index.html
│    └── pages/
│          └── index.vue
├── 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/vue project must have at the very least:

ExportDescription

server.js

A Fastify server file that registers @fastify/vite.

Or a plugin file if running fastify-cli or platformatic.

vite.config.js

Your Vite application configuration. file

It needs to import and register @fastify/vue/plugin.

client/index.js

The Vite application module loaded by @fastify/vite.

It must export an object with create, routes and context.

These are detailed further below on this page.

client/index.html

The Vite application HTML template:

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>
<!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>

It needs to have head, element and hydration placeholders.

And it must import /:mount.js as the main module.

client/pages/*.vue

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:

js
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/vue',
})

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/vue',
})

await server.vite.ready()
await server.listen({ port: 3000 })
js
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'

import viteVue from '@vitejs/plugin-vue'
import fastifyVue from '@fastify/vue/plugin'

const path = fileURLToPath(import.meta.url)

export default {
  root: join(dirname(path), 'client'),
  plugins: [
    viteVue(), 
    fastifyVue(),
  ],
}
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'

import viteVue from '@vitejs/plugin-vue'
import fastifyVue from '@fastify/vue/plugin'

const path = fileURLToPath(import.meta.url)

export default {
  root: join(dirname(path), 'client'),
  plugins: [
    viteVue(), 
    fastifyVue(),
  ],
}
js
import routes from '/:routes.js'
import create from '/:create.js'

export default { 
  context: import('/:context.js'), 
  routes,
  create,
}
import routes from '/:routes.js'
import create from '/:create.js'

export default { 
  context: import('/:context.js'), 
  routes,
  create,
}
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>
<!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>
vue
<template>
  <h1>{{ message }}</h1>
  <p><img :src="logo" /></p>
</template>

<script setup>
import logo from '/assets/logo.svg'

const message = 'Welcome to @fastify/vue!'
</script>

<script>
export function getMeta () {
  return {
    title: 'Welcome to @fastify/vue!'
  }
}
</script>

<style scoped>
img {
  width: 100%;
}
</style>
<template>
  <h1>{{ message }}</h1>
  <p><img :src="logo" /></p>
</template>

<script setup>
import logo from '/assets/logo.svg'

const message = 'Welcome to @fastify/vue!'
</script>

<script>
export function getMeta () {
  return {
    title: 'Welcome to @fastify/vue!'
  }
}
</script>

<style scoped>
img {
  width: 100%;
}
</style>

This example is actually provided as the vue-base starter.

In this example, server.js is the Fastify server and also the place where both @fastify/vite and @fastify/vue 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/vue has its own structure for it.

Also notice that in vite.config.js, @fastify/vue/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/vue 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/vue that make all of that (and a bit more) work don't have to exist in your project directory, but are loaded nonetheless:

text
├── server.js
├── client/
│    ├── core.js
│    ├── create.js
│    ├── mount.js
│    ├── routes.js
│    ├── router.vue
│    ├── root.vue
│    ├── context.js
│    ├── index.js
│    ├── index.html
│    ├── layouts/
│    │    └── default.vue
│    └── pages/
│          └── index.vue
├── vite.config.js
└── package.json
├── server.js
├── client/
│    ├── core.js
│    ├── create.js
│    ├── mount.js
│    ├── routes.js
│    ├── router.vue
│    ├── root.vue
│    ├── context.js
│    ├── index.js
│    ├── index.html
│    ├── layouts/
│    │    └── default.vue
│    └── pages/
│          └── index.vue
├── vite.config.js
└── package.json

The way this works is via the /: prefix.

Notice how client/index.html imports the Vue application mounting script from /:mount.js, and client/index.js loads routes from /:routes.js, the application factory function from /:create.js 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/vue package instead.

Below is a quick rundown of all smart imports available.

Smart importDescription

/:core.js

This is used by /:create.js internally to create your Vue application instance, but it's also the location where to import the useRouteContext() hook from.

It also exports the isServer convenience flag.

/:create.js

Where your Vue application factory function is exported from. It must be named create — or client/index.js and client/mount.js need to be changed accordingly.

/:mount.js

The Vite application mount script, imported by index.html.

/:router.vue

The main Vue Router component for your application.

Loaded by /:root.vue.

/:root.vue

The main Vue component for your application.

It can also export a configure({ app, router }) function to let you further extend the Vue and Vue Router instances without having to fully eject other core virtual modules into your application.

/:context.js

The route context initialization file.

/:layouts/default.vue

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/vue applications that are processed differently and in an automated fashion.

Special directoryDescription

/pages

Default route module search location.

/layouts

Files are made available as route layout components.

Released under the MIT License.