Module authoring

Module authoring

What a Nuxt module really is, the three worlds every line of module code lives in, the three primitives everything reduces to, and why a Layers role is a module role in disguise.

A Nuxt module is a function that runs once, in Node, while Nuxt starts or builds. It never executes in the browser and never executes inside the production server. Everything a consumer's app runs at runtime is code the module injected from its runtime/ directory. For a role that builds an internal toolkit, this is the section that separates people who have configured modules from people who can write, version and support one.

The mental model

Three worlds, and every line of code lives in exactly one of them:

  1. Build world: src/module.ts, @nuxt/kit, Node APIs, the file system, the nuxt instance. Runs once per nuxt dev / nuxt build / nuxt generate, sequentially with the other modules, in the order of nuxt.options.modules.
  2. Server runtime: src/runtime/server/** plus the SSR side of plugins and composables. Runs inside Nitro, per request, in a long-lived process.
  3. Client runtime: src/runtime/app/** in the browser, once per tab session.

Most module bugs are boundary bugs: a value that exists in the build world (an option, nuxt.options, a path on disk) is assumed to exist at runtime, where it does not unless the module deliberately carried it across through runtime config, appConfig or a generated template.

The three primitives

Everything a module can do reduces to three operations on the build-world nuxt instance:

  • Mutate nuxt.options: add a CSS entry, set an alias, merge defaults into runtimeConfig, push a Vite plugin. Later modules see the mutation immediately; earlier ones have already run.
  • Register hooks: nuxt.hook('pages:extend', ...), nuxt.hook('nitro:config', ...), nuxt.hook('modules:done', ...). Hooks are how a module reacts to things that have not happened yet and how modules cooperate without importing each other.
  • Generate or register files: plugins, components, composables, server handlers, templates written into .nuxt. Kit helpers such as addPlugin, addImports, addServerHandler and addTemplate are ergonomic wrappers over those three primitives; each one either pushes into nuxt.options or registers a hook that does.

If you can name which primitive a helper uses underneath, you can predict its ordering behaviour, what happens when two modules call it, and where to look when it does not do what you expect.

Why this matters for a Layers role

A layer is declarative: it ships app code and config that Nuxt merges. It cannot execute anything at build time. The moment a toolkit needs to do something (generate a route registry, inject a plugin in the right position, validate options, react to the consumer's config) that logic has to be a module. In practice a company toolkit is a layer that lists its module in modules, so consumers get conventions, app code and build behaviour from a single extends entry (see Layers).

Two mechanics make that combination safe. Layer configs are merged with defu, so modules arrays concatenate; Nuxt then deduplicates modules by their meta.name, so the layer and the project can both list the same module and it runs once. And because module options are resolved as defu(inlineOptions, nuxt.config[configKey], defaults), the project's config beats the layer's, which beats the module's defaults, mirroring how file overrides work between layers.

The pages in this section follow the life of a module from the inside out: what defineNuxtModule does with your definition, the kit helpers and what they do underneath, how an option travels from the build world to the browser, the rules of the runtime directory, hooks, templates and types, composing with other modules, testing, and finally building and publishing without breaking consumers.

Be able to say

Be able to say· What is a Nuxt module, and what can it actually do?

"A module is build-time code: a function Nuxt calls once, in Node, while it starts or builds, in the order of the modules array. It can do three things: mutate nuxt.options, register hooks, and generate or register files. Everything the user's app executes at runtime comes from my runtime/ directory, which I inject with kit helpers like addPlugin or addImports. That boundary is the source of most module bugs: build-world values do not exist at runtime unless I deliberately carry them across through runtime config, appConfig or a template. A layer cannot run code, so a toolkit that needs build-time logic is a layer plus a module, and Nuxt deduplicates the module by name if both list it."

Pages in this section

  1. 01The mental modelWhat defineNuxtModule does with your definition, the three worlds a line of module code can live in, how Nuxt orders and deduplicates modules, and when onInstall and onUpgrade fire.
  2. 02The kit API, groupedThe @nuxt/kit helpers a module author should know from memory, grouped by what they do, with what each one does underneath and the source files worth reading.
  3. 03Crossing the boundaryThe four channels that carry a module option from the build world into the browser — public and private runtime config, appConfig and generated templates — and how to choose between them.
  4. 04Rules of the runtime directoryWhat src/runtime may and may not import, how module-builder transpiles it file by file, the .client/.server suffixes, and how to make Nitro inline your server runtime so
  5. 05HooksHow unjs/hookable powers the three hook families, a goal-to-hook cheat sheet for module authors, and how to declare your own hooks so other modules can cooperate with yours.
  6. 06Templates and typesGenerating code into .nuxt with addTemplate, adding declarations with addTypeTemplate, regenerating only your own templates on watch, and getting the nuxt/schema augmentation right.
  7. 07Dependencies and compositioninstallModule versus the declarative moduleDependencies field, detecting other modules with hasNuxtModule, choosing dependency versus peerDependency, and cooperating through hooks.
  8. 08Testing a moduleThe module test pyramid with @nuxt/test-utils — runtime unit tests, fixture E2E, build-level assertions, type tests, a nightly CI matrix and the non-functional tests a toolkit needs.
  9. 09Building, versioning and publishingWhat nuxt-module-build emits, the package.json fields that decide whether your module resolves, semver for modules, deprecating an option safely, and getting listed on nuxt.com/modules.