# 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:

<steps>

### `request` hook

The `request` hook is the first code that runs for every incoming request. It is registered via a [server plugin](/docs/plugins):

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook("request", (event) => {
    console.log(`Incoming request on ${event.path}`);
  });
});
```

<note>

Errors thrown inside the `request` hook are captured by the [`error` hook](#error-handling) and do not terminate the request pipeline.
</note>

### 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).

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/**': { headers: { 'x-nitro': 'first' } }
  }
})
```

<read-more></read-more>

### Global middleware

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

```ts [middleware/info.ts]
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  event.context.info = { name: "Nitro" };
});
```

<warning>

Returning from a middleware will close the request and should be avoided when possible.
</warning>

<read-more>

Learn more about Nitro middleware.
</read-more>

### 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.

```ts [routes/api/hello.ts]
export default (event) => ({ world: true })
```

<read-more>

Learn more about Nitro file-system routing.
</read-more>

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

```ts [server.ts]
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  if (event.path === "/") {
    return "Home page";
  }
});
```

<read-more>

Learn more about Nitro server entry.
</read-more>

### Renderer

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

<read-more>

Learn more about Nitro renderer.
</read-more>

### `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:

```ts [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}`);
  });
});
```

<note>

The `response` hook runs for every response, including static assets, middleware-terminated requests, and error responses.
</note>
</steps>

## Error handling

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

1. Calls the `error` hook with the error and context (including the event and source tags).
2. Passes the error to the **error handler** which converts it into an HTTP response.

```ts [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:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  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:

```ts [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](/docs/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. |

<note>

The `NitroRuntimeHooks` interface is augmentable. Deployment presets (such as Cloudflare) can extend it with platform-specific hooks.
</note>

<read-more>

Learn more about Nitro plugins and hook usage examples.
</read-more>
