Under the hood

Under the hood

The startup sequence, the server engine, the unjs toolbelt, auto-imports and build-time transforms — the machinery a layer and module author has to reason about rather than use.

An application developer can treat Nuxt as a black box with a good API. Someone who builds Layers and modules for other teams cannot: every module is a function that runs inside that box, at a specific point in a sequence, mutating state that later stages read. If you do not know when nuxt.options stops being writable in practice, when Nitro's config is assembled, or why an auto-import works in a component but throws in a server route, you will write code that happens to work in your playground and fails in the consumer's app for reasons neither of you can see.

That is exactly why interviewers probe internals for a tooling role. The question is never "do you know what nitro:config does" — it is whether you can say why the hook exists at that point and what would break if you did the same work in setup(). The answers separate three levels: someone who has read the docs names the API; someone who has shipped a module names the ordering constraint; someone who supports a module in ten apps names the failure mode the consumer will report. Internals questions are cheap to ask and extremely hard to bluff, so they get asked a lot.

The mental model

Nuxt itself is small. Most of what you think of as "Nuxt" is a set of independent unjs packages wired together by a hook system:

  • c12 loads and merges the configuration, including layers and .env; untyped resolves it against a schema so every option has a documented default.
  • hookable is the spine. The whole build is a sequence of hook calls, and a module is simply a function that gets to register more of them.
  • unimport builds the auto-import registry and rewrites your identifiers; unplugin lets one transform serve Vite, webpack and Rspack.
  • Nitro takes your server/ directory plus the Vue SSR renderer and produces a deployable output for whichever platform preset you target; h3 is the request layer inside it, unstorage the storage and cache layer.
  • Vite builds two bundles (client and server) from one config, and the plugin hooks you register are where build-time code generation actually happens.

Two boundaries cause most real-world bugs, and both are covered here. The first is build world versus runtime world: a module runs once in Node and never again, so anything the app needs at runtime must be carried across deliberately. The second is app registry versus Nitro registry: they are different auto-import scopes, different module graphs, and different sets of globals, which is why "it works in a component" proves nothing about a server route.

The pages below follow that machinery in the order you meet it: first what actually happens between typing nuxt dev and getting a URL, then the server half, then the toolbelt you build with, then the two transform systems that rewrite your code, then how pages are rendered and data crosses the wire — and finally the one decision every toolkit author has to defend out loud.

Be able to say

Be able to say· Why does a module author need to know Nuxt's internals when an app developer does not?

"Because a module is not a consumer of the API, it is a participant in the build. My code runs at a particular point in a sequence — after internal modules, before app resolution, before Nitro is configured — and the things I can do depend entirely on where I am in that sequence. If I mutate nuxt.options.nitro after Nitro has been initialised, nothing happens and nothing warns me. If I register an app auto-import and a consumer calls it in a server route, they get an undefined-function error in production with no obvious cause. An app developer can learn the API and be productive; I have to be able to predict the consumer's failure mode before they hit it, which means knowing the hook order, the two auto-import registries and the build-versus-runtime boundary."