Lifecycle
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:
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("request", (event) => {
console.log(`Incoming request on ${event.path}`);
});
});
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).
import { defineNitroConfig } from "nitro/config";
export default defineNitroConfig({
routeRules: {
'/**': { headers: { 'x-nitro': 'first' } }
}
})
Global middleware
Any global middleware defined in the middleware/ directory will be run:
import { defineHandler } from "nitro";
export default defineHandler((event) => {
event.context.info = { name: "Nitro" };
});
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.
export default (event) => ({ world: true })
If serverEntry is defined it will catch all requests not matching any other route acting as /** route handler.
import { defineHandler } from "nitro";
export default defineHandler((event) => {
if (event.path === "/") {
return "Home page";
}
});
Renderer
If no route is matched, Nitro will look for a renderer handler (defined or auto-detected) to handle the request.
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:
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("response", (res, event) => {
console.log(`Response ${res.status} for ${event.path}`);
});
});
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.
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:
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:
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().
| Hook | Signature | When 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? }) => void | When any error is captured during the lifecycle. |
close | () => void | When the Nitro server is shutting down. |
NitroRuntimeHooks interface is augmentable. Deployment presets (such as Cloudflare) can extend it with platform-specific hooks.