@fastify/static

Serve Files From a Folder

@fastify/static

Mount a folder as static assets with proper caching headers and an optional single-page application fallback for client-side routing.

4 min read Level 1/5 #fastify#static#files
What you'll learn
  • Install @fastify/static
  • Register with root and optional prefix
  • Add an SPA fallback for client-side routing

@fastify/static serves files from a directory: JS bundles, CSS, images, a built SPA. Behind the scenes it uses the proven send library and stream responses for efficiency.

Install & Register

npm install @fastify/static
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import fastifyStatic from '@fastify/static'

const __dirname = dirname(fileURLToPath(import.meta.url))

await app.register(fastifyStatic, {
  root: join(__dirname, '..', 'public'),
  prefix: '/static/',
  maxAge: '7d',
  immutable: true,
})

immutable: true plus maxAge produces Cache-Control: public, max-age=604800, immutable. Combine with fingerprinted filenames (app.abc123.js) for hands-off caching.

SPA Fallback

When the URL does not match a built file, return index.html so the SPA router can take over.

app.setNotFoundHandler((req, reply) => {
  if (req.raw.url?.startsWith('/api')) {
    return reply.code(404).send({ error: 'not found' })
  }
  return reply.sendFile('index.html')
})

Multiple Roots

You can register the plugin several times with decorateReply: false after the first to serve from multiple directories under different prefixes.

await app.register(fastifyStatic, {
  root: join(__dirname, '..', 'public'),
  prefix: '/',
})
await app.register(fastifyStatic, {
  root: join(__dirname, '..', 'docs-dist'),
  prefix: '/docs/',
  decorateReply: false,
})
@fastify/compress →