---
title: "Creating a Builder"
description: "Learn how Nuxt builders work and how to author your own."
canonical_url: "https://nuxt.com/docs/4.x/guide/going-further/builders"
---
# Creating a Builder

> Learn how Nuxt builders work and how to author your own.

A **builder** is the part of Nuxt responsible for bundling your application. Nuxt ships with three official builders, [Vite](https://vite.dev) (the default), [webpack](https://webpack.js.org) and [Rspack](https://rspack.dev), and you can select one with the [`builder`](https://nuxt.com/docs/4.x/api/nuxt-config#builder) option or supply your own.

This guide explains how builders fit into the Nuxt build, what contract a builder must fulfil, and how to author one.

<note>

Authoring a builder is an advanced topic. Most apps never need a custom builder; if you only want to influence the bundle, a [module](https://nuxt.com/docs/4.x/guide/modules) that registers bundler plugins is usually the right tool.

</note>

## What a Builder Does

Nuxt separates **what** to build from **how** to build it.

The Nuxt core (the `nuxt` context described in [How Nuxt Works](https://nuxt.com/docs/4.x/guide/going-further/internals)) and your modules generate the virtual application: the entrypoints, route table, plugins, component registry, and the rest of the [virtual file system](https://nuxt.com/docs/4.x/guide/going-further/internals#the-nuxt-interface) under `#build`. The builder takes that virtual application and turns it into real JavaScript and CSS bundles, and in development runs a dev server that serves and hot-reloads them.

Concretely, a builder is responsible for:

- Bundling the **client** build (the browser bundle) and, when SSR is enabled, the **server** build (the SSR app entry).
- Producing the artifacts the server runtime needs to render and hydrate: the client manifest, the per-component styles map, and so on (see [The Build Output Contract](#the-build-output-contract)).
- In development, exposing a dev server and triggering reloads when the build changes.

The deployable server itself is produced by [Nitro](https://nitro.build) via `@nuxt/nitro-server`, not by the builder. The builder hands its outputs to Nitro through a typed contract; Nitro bundles them into the final `.output`.

## The Builder Interface

A builder is an object implementing the `NuxtBuilder` interface. The only required method is `bundle`:

```ts
import type { Nuxt } from '@nuxt/schema'

export interface NuxtBuilder {
  bundle: (nuxt: Nuxt) => Promise<void>
  /**
   * Optional. When the user opts in via `experimental.watcher: 'builder'`,
   * Nuxt calls this instead of starting its own dev file watcher, letting
   * the builder reuse its own watcher. The builder should register a
   * `nuxt.hook('close', ...)` to clean up.
   */
  setupWatcher?: (nuxt: Nuxt) => Promise<void> | void
}
```

Nuxt resolves the active builder from the [`builder`](https://nuxt.com/docs/4.x/api/nuxt-config#builder) option. It accepts either a module specifier that default-exports a `NuxtBuilder`, or an inline object:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  // a package that exports `{ bundle }`
  builder: '@nuxt/vite-builder',
})
```

```ts [nuxt.config.ts]
import type { NuxtBuilder } from '@nuxt/schema'

const myBuilder: NuxtBuilder = {
  async bundle (nuxt) {
    // ...
  },
}

export default defineNuxtConfig({
  builder: myBuilder,
})
```

Nuxt calls `bundle(nuxt)` once during `nuxt build` and `nuxt dev`, after the virtual application has been generated and the `build:before` hook has fired. Nuxt wraps your `bundle` so that any thrown error triggers the `build:error` hook automatically.

<note>

Only the three official builder specifiers (`@nuxt/vite-builder`, `@nuxt/webpack-builder`, `@nuxt/rspack-builder`) are recognised by name for builder-specific behaviour elsewhere in Nuxt. A custom builder still works through the generic contract described below.

</note>

## The Build Lifecycle

When you run [`nuxt build`](https://nuxt.com/docs/4.x/api/commands/build) or [`nuxt dev`](https://nuxt.com/docs/4.x/api/commands/dev), Nuxt:

1. Creates the `nuxt` context and runs modules, populating `nuxt.options` and the build hooks.
2. Generates the virtual application (templates, route table, plugins) into the `#build` virtual file system.
3. Fires `build:before`.
4. Resolves the builder and calls `builder.bundle(nuxt)`. **This is where your builder runs.**
5. Fires `build:done`, and in production closes the `nuxt` instance.

Your `bundle` implementation typically branches on `nuxt.options.dev`:

- In **production**, it runs the client and (if `nuxt.options.ssr`) server builds to completion, writing artifacts to `nuxt.options.buildDir` and registering them as build outputs.
- In **development**, it sets up a dev server, starts a watching build, assigns `nuxt.server`, and keeps running.

A builder communicates with the rest of Nuxt almost entirely through [hooks](https://nuxt.com/docs/4.x/api/advanced/hooks). It reads build configuration off `nuxt.options` and, when appropriate, lets modules extend its bundler configuration.

### Letting Modules Extend the Bundler

Modules influence the bundle through [Nuxt Kit](https://nuxt.com/docs/4.x/guide/going-further/kit) helpers. A builder should honour the relevant ones:

- [`addVitePlugin`](https://nuxt.com/docs/4.x/api/kit/builder#addviteplugin) / [`addWebpackPlugin`](https://nuxt.com/docs/4.x/api/kit/builder#addwebpackplugin) register bundler-specific plugins.
- [`addBuildPlugin`](https://nuxt.com/docs/4.x/api/kit/builder#addbuildplugin) registers an [unplugin](https://github.com/unjs/unplugin) factory, so a single plugin works across every builder.
- [`extendViteConfig`](https://nuxt.com/docs/4.x/api/kit/builder#extendviteconfig) / [`extendWebpackConfig`](https://nuxt.com/docs/4.x/api/kit/builder#extendwebpackconfig) mutate the resolved bundler config.

The official builders also emit their own hooks (for example `vite:extendConfig`, `vite:serverCreated`, `webpack:config`) so modules and Nitro can participate in the build. A custom builder may emit its own hooks, but the build output contract below is what makes it interoperate with the Nuxt server runtime.

## The Build Output Contract

The server runtime (`@nuxt/nitro-server`) does not know which builder produced the app. It imports each build artifact through a stable `nuxt/*` subpath, and the active builder populates those subpaths with `nuxt.buildOutputs`. This is the **build output contract**.

The contract is declared by the `NuxtBuildOutputs` interface in `@nuxt/schema`:

```ts
export interface NuxtBuildOutputs {
  /** Module body re-exporting the SSR app entry. */
  serverEntry: () => string | Promise<string>
  /** Path to the emitted per-component SSR styles map, or `undefined` when no inline styles are produced. */
  ssrStyles: string | undefined
  /** Serialized client manifest for `vue-bundle-renderer`. */
  clientManifest: () => string | Promise<string>
  /** Serialized precomputed client dependency data for `vue-bundle-renderer`. */
  clientPrecomputed: () => string | Promise<string>
  /** Module body exporting the hashed entry chunk filename for import maps. */
  entryChunkName: () => string | Promise<string>
  /** Module body exporting the entry module IDs used for inline style extraction. */
  entryIds: () => string | Promise<string>
}
```

Each key maps to a `nuxt/*` subpath that the server runtime imports:

<table>
<thead>
  <tr>
    <th>
      Build output
    </th>
    
    <th>
      Subpath
    </th>
    
    <th>
      Imported as
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        serverEntry
      </code>
    </td>
    
    <td>
      <code>
        nuxt/entry
      </code>
    </td>
    
    <td>
      the SSR app factory passed to <code>
        createRenderer
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        clientManifest
      </code>
    </td>
    
    <td>
      <code>
        nuxt/manifest
      </code>
    </td>
    
    <td>
      the <code>
        vue-bundle-renderer
      </code>
      
       client manifest
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        clientPrecomputed
      </code>
    </td>
    
    <td>
      <code>
        nuxt/precomputed
      </code>
    </td>
    
    <td>
      precomputed dependency data
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ssrStyles
      </code>
    </td>
    
    <td>
      <code>
        nuxt/styles
      </code>
    </td>
    
    <td>
      the per-component inline-styles map
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        entryChunkName
      </code>
    </td>
    
    <td>
      <code>
        nuxt/entry-chunk
      </code>
    </td>
    
    <td>
      the hashed entry chunk filename (import map)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        entryIds
      </code>
    </td>
    
    <td>
      <code>
        nuxt/entry-ids
      </code>
    </td>
    
    <td>
      the entry module IDs for style extraction
    </td>
  </tr>
</tbody>
</table>

Each `nuxt/*` subpath ships a default stub in the `nuxt` package, so the server runtime always type-checks and builds even before a builder runs. A builder overrides the stub by setting the matching build output; the value it provides replaces the stub at build time.

### Two Kinds of Build Output

The keys come in two shapes:

- **Value providers** (`serverEntry`, `clientManifest`, `clientPrecomputed`, `entryChunkName`, `entryIds`) are functions returning the **module body as a string**. The string is inlined verbatim into the server bundle, so it must not depend on the file's location on disk. `serverEntry`, for example, returns a body that re-exports the built SSR entry by absolute specifier:```ts
setBuildOutput('serverEntry', () => `export { default } from ${JSON.stringify(serverEntryURL)}`)
```
- **Emitted-file path** (`ssrStyles`) is an absolute path (not code) to a real module the builder emitted. The runtime's `nuxt/styles` import resolves to that file so the deployable's bundler resolves the styles map's relative sibling imports against the file's own location. Modelling it as a code string would strip that directory context and break the relative imports, so this one is a path:```ts
setBuildOutput('ssrStyles', resolve(serverOutDir, 'styles.mjs'))
```

<br />

Leave `ssrStyles` as `undefined` (its default) when the build produces no inline styles; the runtime falls back to an empty styles map.

### Setting Build Outputs

Use the [`setBuildOutput`](https://nuxt.com/docs/4.x/api/kit/builder#setbuildoutput) helper from `@nuxt/kit`:

```ts
import { setBuildOutput } from '@nuxt/kit'

setBuildOutput('clientManifest', () => 'export default ' + serializedManifest)
```

`setBuildOutput` writes to `nuxt.buildOutputs[key]`. From inside a bundler plugin that already holds the `nuxt` instance you can assign `nuxt.buildOutputs[key]` directly; `setBuildOutput` is the convenience for code that resolves `nuxt` via [`useNuxt()`](https://nuxt.com/docs/4.x/api/kit/context#usenuxt).

A provider can be asynchronous and is read lazily, when the server build resolves the corresponding `nuxt/*` import. This lets a builder register the provider early (for example before the client build has finished) and have it return the finalised value once the data exists.

### A Minimal Example

The skeleton of a builder that fulfils the contract for a production build:

```ts
import { resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import { setBuildOutput } from '@nuxt/kit'
import type { NuxtBuilder } from '@nuxt/schema'

export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
  const serverDir = resolve(nuxt.options.buildDir, 'dist/server')

  // ...run your client and server bundles here, writing artifacts to disk...
  const { serializedClientManifest } = await runBundles(nuxt, serverDir)

  if (nuxt.options.ssr) {
    // Point `nuxt/entry` at the built SSR app entry.
    const serverEntryURL = pathToFileURL(resolve(serverDir, 'server.mjs')).href
    setBuildOutput('serverEntry', () => `export { default } from ${JSON.stringify(serverEntryURL)}`)

    // Provide the client manifest produced by your client build.
    setBuildOutput('clientManifest', () => `export default ${serializedClientManifest}`)

    // If you emit a per-component styles map alongside its CSS chunks:
    setBuildOutput('ssrStyles', resolve(serverDir, 'styles.mjs'))
  }
}
```

<note>

You rarely need to provide every key. The defaults are sensible (an empty manifest, no inline styles, an undefined entry chunk), so provide only what your build produces. When SSR is disabled, the `serverEntry` default is a no-op app and most other outputs are unused.

</note>

## The Development Server

In development, a builder is also responsible for serving and hot-reloading the app. Two things matter:

- **nuxt.server** holds the running dev server. Nuxt's CLI consumes it, and the official builders expose a `handler` (a Node request listener), a `fetch` (a web `fetch` handler), and `reload` / `close` methods on it. Whether you build your own dev server or delegate to Nitro's (`createDevServer` from `nitro/builder`) is up to the builder.
- **Reloads** are signalled by calling `nuxt.server.reload()` when a compilation finishes. The official builders emit a `compiled` hook (for example `vite:compiled`, `webpack:compiled`) that the server integration listens to in order to reload.

In dev, the build outputs are typically wired to live, in-memory sources rather than on-disk files. For instance, the SSR entry may be served from the bundler's in-memory output, and the client manifest may be computed from the dev module graph, rather than read from `nuxt.options.buildDir`.

## Describing the Build to a Deploy Target

A server builder describes what it produced, so that tooling that deploys or previews the build does not have to know which builder ran. The description lives on `nuxt.serverBuild`, and is read with `useServerBuild()` from `@nuxt/kit/internal`:

```ts
import { useServerBuild } from '@nuxt/kit/internal'

const build = useServerBuild()

build.output.root() // the project root: where a target's own config file lives
build.output.dir() // the build output directory
build.output.publicDir() // the deployable static assets within it
```

Every path is a function, because a builder may only resolve it while initialising, so call them from a hook that runs after the server builder has run.

The description also says how the server build relates to the app build. A server builder sets `buildsSeparately` to declare whether its build is a pass of its own, run after the app build (as `@nuxt/nitro-server` does), or whether the app builder is building everything and there is no separate pass (as with `@nuxt/vite-server`). Anything that needs to know reads it from the description rather than sniffing for a Nitro instance.

`output.root()` is deliberately not a bundler root. Nuxt points Vite's `root` at [`srcDir`](https://nuxt.com/docs/4.x/api/nuxt-config#srcdir), so a deploy target that resolves its configuration file (or the paths written inside it) from the bundler's root looks inside your app directory rather than at the root of your project. A target that reads the description resolves both correctly.

<warning>

`nuxt.serverBuild` and the `setServerBuild()` / `useServerBuild()` helpers are experimental and exported from `@nuxt/kit/internal`. The shape will change without a major release while a second server builder is being built out.

</warning>

<read-more to="https://nuxt.com/docs/4.x/guide/going-further/internals">

Learn more about the Nuxt interface and the build vs. runtime split.

</read-more>

<read-more to="https://nuxt.com/docs/4.x/api/kit/builder">

Browse the Nuxt Kit builder utilities used to register bundler plugins and build outputs.

</read-more>


## Sitemap

See the full [sitemap](https://nuxt.com/sitemap.md) for all pages.
