# How Astro's Middleware System Intercepts and Modifies Requests and Responses

> Discover how Astro's middleware system intercepts and modifies requests and responses. Read, rewrite, or replace content within a composable pipeline for flexible request handling.

- Repository: [Astro/astro](https://github.com/withastro/astro)
- Tags: internals
- Published: 2026-03-06

---

**Astro's middleware system runs on every incoming HTTP request and provides an `onRequest` hook to read, rewrite, or replace both the request and the resulting response through a composable pipeline.**

Astro's middleware architecture in the withastro/astro repository gives developers granular control over HTTP traffic before it reaches the rendering layer. By leveraging a small runtime API centered around `onRequest`, `sequence`, and `callMiddleware`, you can inspect headers, rewrite URLs internally, or return custom responses without ever hitting the page component.

## Middleware Registration and Resolution

Every Astro middleware implementation starts with a single entry point. You export an `onRequest` handler from [`src/middleware.ts`](https://github.com/withastro/astro/blob/main/src/middleware.ts) (or `.js`, `.mjs`), which Astro resolves at startup through the build manifest.

In [`packages/astro/src/core/app/middlewares.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/app/middlewares.ts), the framework locates your handler and stores it for the application lifecycle. Astro may prepend **internal middlewares**—such as origin-check validation or internationalization (i18n) routing—to your user-defined handler before execution begins. These internal handlers are combined with your code using the `sequence` utility to create a single composed function.

## Composing Handlers with sequence

The `sequence` function in [`packages/astro/src/core/middleware/sequence.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/middleware/sequence.ts) chains multiple middleware handlers into a unified pipeline. It accepts any number of `MiddlewareHandler` functions and returns a single handler that executes them in order.

This composition is critical because it creates the `next` callback pattern. Each handler in the sequence receives a `next` parameter that, when invoked, either proceeds to the next middleware in the chain or ultimately triggers the rendering pipeline. The type definitions in [`packages/astro/src/types/public/common.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/types/public/common.ts) define the core interfaces: `MiddlewareHandler`, `MiddlewareNext`, and `RewritePayload`.

## Executing Requests with callMiddleware

For every incoming request, Astro invokes the composed middleware through `callMiddleware` in [`packages/astro/src/core/middleware/callMiddleware.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/middleware/callMiddleware.ts) (lines 60-104). This function orchestrates the execution flow by:

1. Creating the `next` callback that wraps the remaining middleware chain or the final render step
2. Validating return types to ensure handlers return either a `Response` object or the result of `next()`
3. Handling the three possible execution paths: returning a response directly, calling `next()` to continue, or calling `next(payload)` to rewrite

The `Pipeline` class in [`packages/astro/src/core/base-pipeline.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/base-pipeline.ts) retrieves this composed handler via `getMiddleware()` and executes it for each request.

## Intercepting and Rewriting Requests

Astro's middleware system modifies requests through the **rewrite payload** mechanism. When you call `next()` with a `string`, `URL`, or `Request` object as an argument, the pipeline intercepts the original URL and re-resolves the route.

In [`packages/astro/src/core/middleware/sequence.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/middleware/sequence.ts) (lines 35-86), the logic updates the `APIContext` object—specifically `ctx.url`, `ctx.params`, and the internal route pattern—before continuing to the next middleware. This allows internal rewrites without sending HTTP redirects to the client.

## Modifying and Short-Circuiting Responses

Middleware can intercept the response at two critical points. First, any handler can **short-circuit** the entire pipeline by returning a `Response` object directly, preventing the rendering layer from executing. This is useful for authentication gates or rate limiting.

Second, if `manifest.checkOrigin` is enabled in [`packages/astro/src/core/app/middlewares.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/app/middlewares.ts), Astro automatically prepends `createOriginCheckMiddleware()` to the chain. This internal middleware validates that non-GET/HEAD/OPTIONS requests originate from the same site, returning a `403 Forbidden` response for cross-site form submissions if the origin check fails.

## Practical Code Examples

The `defineMiddleware` helper in [`packages/astro/src/core/middleware/defineMiddleware.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/middleware/defineMiddleware.ts) (re-exported via `astro:middleware` from [`packages/astro/src/virtual-modules/middleware.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/virtual-modules/middleware.ts)) provides type-safe wrapper for your handlers.

### Logging Incoming Requests

This middleware logs every URL and continues to the rendering pipeline:

```typescript
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';

export const onRequest = defineMiddleware(async (context, next) => {
  console.log('Incoming request:', context.request.url);
  return await next();
});

```

### Blocking Unauthenticated Requests

Return a custom response to prevent access to protected routes:

```typescript
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';

export const onRequest = defineMiddleware((ctx, next) => {
  const auth = ctx.request.headers.get('Authorization');
  if (!auth) {
    return new Response('Unauthorized', { status: 401 });
  }
  return next();
});

```

### Rewriting URLs Internally

Redirect `/old-path` to `/new-path` server-side without a client-side redirect:

```typescript
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';

export const onRequest = defineMiddleware(async (ctx, next) => {
  if (ctx.url.pathname === '/old-path') {
    return await next('/new-path');
  }
  return await next();
});

```

## Summary

- Astro resolves middleware from [`src/middleware.ts`](https://github.com/withastro/astro/blob/main/src/middleware.ts) via the manifest in [`packages/astro/src/core/app/middlewares.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/app/middlewares.ts), combining user handlers with internal ones using `sequence`.
- The `callMiddleware` function in [`packages/astro/src/core/middleware/callMiddleware.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/middleware/callMiddleware.ts) orchestrates execution, creating the `next` callback that controls flow through the pipeline.
- Calling `next()` without arguments continues execution; calling `next(payload)` with a URL triggers an internal rewrite that updates `APIContext` in [`packages/astro/src/core/middleware/sequence.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/middleware/sequence.ts).
- Returning a `Response` directly from middleware short-circuits the render, while internal middleware like `createOriginCheckMiddleware` can automatically reject cross-origin requests.
- The `defineMiddleware` utility in [`packages/astro/src/core/middleware/defineMiddleware.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/middleware/defineMiddleware.ts) provides TypeScript support for the `onRequest` handler pattern.

## Frequently Asked Questions

### Where do I define middleware in an Astro project?

Create a file named [`src/middleware.ts`](https://github.com/withastro/astro/blob/main/src/middleware.ts) (or `.js`, `.mjs`) in your project root and export an `onRequest` function. Astro automatically detects this file during the build process and registers it via the manifest resolution logic in [`packages/astro/src/core/app/middlewares.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/app/middlewares.ts).

### Can I modify the request URL in Astro middleware?

Yes. Pass a string, `URL`, or `Request` object to the `next()` function, such as `await next('/new-path')`. The `sequence` function in [`packages/astro/src/core/middleware/sequence.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/middleware/sequence.ts) handles this rewrite payload by updating the `APIContext` and re-resolving the route before continuing the middleware chain.

### How do I prevent a request from reaching the page component?

Return a `Response` object directly from your `onRequest` handler instead of calling `next()`. The `callMiddleware` implementation in [`packages/astro/src/core/middleware/callMiddleware.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/core/middleware/callMiddleware.ts) checks for returned responses and short-circuits the pipeline, preventing the rendering layer from executing.

### Does Astro middleware run on prerendered static pages?

No. Astro's middleware executes only on server-rendered requests. Prerendered assets and static files bypass the middleware pipeline entirely, as the framework serves them directly from the build output without invoking the `callMiddleware` execution path.