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

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 (or .js, .mjs), which Astro resolves at startup through the build manifest.

In 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 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 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 (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 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 (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, 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 (re-exported via astro:middleware from 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:

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

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

// 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

Frequently Asked Questions

Where do I define middleware in an Astro project?

Create a file named 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.

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

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →