Assets

Nitro supports two types of assets: public assets served directly to clients and server assets bundled into the server for programmatic access.

Public Assets

Nitro handles assets via the public/ directory.

All assets in public/ directory will be automatically served. This means that you can access them directly from the browser without any special configuration.

public/
  image.png     <-- /image.png
  video.mp4     <-- /video.mp4
  robots.txt    <-- /robots.txt

Caching and Headers

Public assets are served with automatic ETag and Last-Modified headers for conditional requests. When the client sends If-None-Match or If-Modified-Since headers, Nitro returns a 304 Not Modified response.

For assets served from a non-root baseURL (such as /build/), Nitro prevents fallthrough to application handlers. If a request matches a public asset base but the file is not found, a 404 is returned immediately.

Production Public Assets

When building your Nitro app, the public/ directory will be copied to .output/public/ and a manifest with metadata will be created and embedded in the server bundle.

{
  "/image.png": {
    "type": "image/png",
    "etag": "\"4a0c-6utWq0Kbk5OqDmksYCa9XV8irnM\"",
    "mtime": "2023-03-04T21:39:45.086Z",
    "size": 18956
  },
  "/robots.txt": {
    "type": "text/plain; charset=utf-8",
    "etag": "\"8-hMqyDrA8fJ0R904zgEPs3L55Jls\"",
    "mtime": "2023-03-04T21:39:45.086Z",
    "size": 8
  },
  "/video.mp4": {
    "type": "video/mp4",
    "etag": "\"9b943-4UwfQXKUjPCesGPr6J5j7GzNYGU\"",
    "mtime": "2023-03-04T21:39:45.085Z",
    "size": 637251
  }
}

This allows Nitro to know the public assets without scanning the directory, giving high performance with caching headers.

Custom Public Asset Directories

You can configure additional public asset directories using the publicAssets config option. Each entry supports the following properties:

  • dir -- Path to the directory (resolved relative to rootDir).
  • baseURL -- URL prefix for serving assets (default: "/").
  • maxAge -- Cache max-age in seconds. When set, a Cache-Control: public, max-age=<value>, immutable header is applied via route rules.
  • fallthrough -- Whether requests should fall through to application handlers when the asset is not found. Top-level (baseURL: "/") directories default to true; non-root directories default to false.
  • ignore -- Pass false to disable ignore patterns, or an array of glob patterns to override the global ignore option.
nitro.config.ts
import { defineNitroConfig } from "nitro/config";

export default defineNitroConfig({
  publicAssets: [
    {
      baseURL: "build",
      dir: "public/build",
      maxAge: 3600,
    },
  ],
});

In this example, files in public/build/ are served under /build/ with a one-hour cache and no fallthrough to application handlers.

Compressed Public Assets

Nitro can generate pre-compressed versions of your public assets during the build. When a client sends an Accept-Encoding header, the server will serve the compressed version if available. Supported encodings are gzip (.gz), brotli (.br), and zstd (.zst).

Set compressPublicAssets: true to enable all encodings:

nitro.config.ts
import { defineNitroConfig } from "nitro/config";

export default defineNitroConfig({
  compressPublicAssets: true,
});

Or pick specific encodings:

nitro.config.ts
import { defineNitroConfig } from "nitro/config";

export default defineNitroConfig({
  compressPublicAssets: {
    gzip: true,
    brotli: true,
    zstd: false,
  },
});
Only compressible MIME types (text, JavaScript, JSON, XML, WASM, fonts, SVG, etc.) with a file size of at least 1 KB are compressed. Source map files (.map) are excluded.

Server Assets

All assets in assets/ directory will be added to the server bundle. After building your application, you can find them in the .output/server/chunks/raw/ directory. Be careful with the size of your assets, as they will be bundled with the server bundle.

Unless using useStorage(), assets won't be included in the server bundle.

They can be addressed by the assets:server mount point using the storage layer.

For example, you could store a json file in assets/data.json and retrieve it in your handler:

import { defineHandler } from "nitro";

export default defineHandler(async () => {
  const data = await useStorage("assets:server").get("data.json");

  return data;
});

Custom Server Assets

In order to add assets from a custom directory, you will need to define a path in your nitro config. This allows you to add assets from a directory outside of the assets/ directory.

Each entry in serverAssets supports the following properties:

  • baseName -- Name used as the storage mount point (accessed via assets:<baseName>).
  • dir -- Path to the directory (resolved relative to rootDir).
  • pattern -- Glob pattern for file inclusion (default: "**/*").
  • ignore -- Array of glob patterns to exclude files.
nitro.config.ts
import { defineNitroConfig } from "nitro/config";

export default defineNitroConfig({
  serverAssets: [
    {
      baseName: "templates",
      dir: "./templates",
    },
  ],
});

Then you can use the assets:templates base to retrieve your assets.

handlers/success.ts
import { defineHandler } from "nitro";

export default defineHandler(async (event) => {
  const html = await useStorage("assets:templates").get("success.html");

  return html;
});
During development, server assets are read directly from the filesystem using the fs unstorage driver. In production, they are bundled into the server as lazy imports with pre-computed metadata (MIME type, ETag, modification time).