Nitro logoNitro

Lifecycle

Understand how Nitro runs and serves incoming requests to your application.

Request lifecycle

A request can be intercepted and terminated (with or without a response) from any of these layers, in this order:

request hook

The request hook is the first code that runs for every incoming request. It is registered via a server plugin:

plugins/request-hook.ts
import { definePlugin } from "nitro";

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook("request", (event) => {
    console.log(`Incoming request on ${event.path}`);
  });
});
Errors thrown inside the request hook are captured by the error hook and do not terminate the request pipeline.

Static assets

When static asset serving is enabled (the default for most presets), Nitro checks if the request matches a file in the public/ directory before any other middleware or route handler runs.

If a match is found, the static file is served immediately with appropriate Content-Type, ETag, Last-Modified, and Cache-Control headers. The request is terminated and no further middleware or routes are executed.

Static assets also support content negotiation for pre-compressed files (gzip, brotli, zstd) via the Accept-Encoding header.

Route rules

The matching route rules defined in the Nitro config will execute. Route rules run as middleware so most of them alter the response without terminating it (for instance, adding a header or setting a cache policy).

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

export default defineNitroConfig({
  routeRules: {
    '/**': { headers: { 'x-nitro': 'first' } }
  }
})
Read more in Routing > Route rules.

Global middleware

Any global middleware defined in the middleware/ directory will be run:

middleware/info.ts
import { defineHandler } from "nitro/h3";

export default defineHandler((event) => {
  event.context.info = { name: "Nitro" };
});
Returning from a middleware will close the request and should be avoided when possible.
Learn more about Nitro middleware.

Routed middleware

Middleware that targets a specific route pattern (defined with a route in middleware/) runs after global middleware but before the matched route handler.

Routes

Nitro will look at defined routes in the routes/ folder to match the incoming request.

routes/api/hello.ts
export default (event) => ({ world: true })
Learn more about Nitro file-system routing.

If serverEntry is defined it will catch all requests not matching any other route acting as /** route handler.

server.ts
import { defineHandler } from "nitro/h3";

export default defineHandler((event) => {
  if (event.path === "/") {
    return "Home page";
  }
});
Learn more about Nitro server entry.

Renderer

If no route is matched, Nitro will look for a renderer handler (defined or auto-detected) to handle the request.

Learn more about Nitro renderer.

response hook

After the response is created (from any of the layers above), the response hook runs. This hook receives the final Response object and the event, and can be used to inspect or modify response headers:

plugins/response-hook.ts
import { definePlugin } from "nitro";

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook("response", (res, event) => {
    console.log(`Response ${res.status} for ${event.path}`);
  });
});
The response hook runs for every response, including static assets, middleware-terminated requests, and error responses.

Error handling

When an error occurs at any point in the request lifecycle, Nitro:

Calls the error hook with the error and context (including the event and source tags).

Passes the error to the error handler which converts it into an HTTP response.

plugins/errors.ts
import { definePlugin } from "nitro";

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook("error", (error, context) => {
    console.error("Captured error:", error);
    // context.event - the H3 event (if available)
    // context.tags  - error source tags like "request", "response", "plugin"
  });
});

Errors are also tracked per-request in event.req.context.nitro.errors for inspection in later hooks.

You can provide a custom error handler in the Nitro config to control error response formatting:

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

export default defineNitroConfig({
  errorHandler: "~/error",
})

Additionally, unhandled promise rejections and uncaught exceptions at the process level are automatically captured into the error hook with the tags "unhandledRejection" and "uncaughtException".

Server shutdown

When the Nitro server is shutting down, the close hook is called. Use this to clean up resources such as database connections, timers, or external service handles:

plugins/cleanup.ts
import { definePlugin } from "nitro";

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook("close", async () => {
    // Clean up resources
  });
});

Hooks reference

All runtime hooks are registered through server plugins using nitroApp.hooks.hook().

HookSignatureWhen it runs
request(event: HTTPEvent) => void | Promise<void>Start of each request, before routing.
response(res: Response, event: HTTPEvent) => void | Promise<void>After the response is created, before it is sent.
error(error: Error, context: { event?, tags? }) => voidWhen any error is captured during the lifecycle.
close() => voidWhen the Nitro server is shutting down.
The NitroRuntimeHooks interface is augmentable. Deployment presets (such as Cloudflare) can extend it with platform-specific hooks.
Learn more about Nitro plugins and hook usage examples.