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:
- Creating the
nextcallback that wraps the remaining middleware chain or the final render step - Validating return types to ensure handlers return either a
Responseobject or the result ofnext() - Handling the three possible execution paths: returning a response directly, calling
next()to continue, or callingnext(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
- Astro resolves middleware from
src/middleware.tsvia the manifest inpackages/astro/src/core/app/middlewares.ts, combining user handlers with internal ones usingsequence. - The
callMiddlewarefunction inpackages/astro/src/core/middleware/callMiddleware.tsorchestrates execution, creating thenextcallback that controls flow through the pipeline. - Calling
next()without arguments continues execution; callingnext(payload)with a URL triggers an internal rewrite that updatesAPIContextinpackages/astro/src/core/middleware/sequence.ts. - Returning a
Responsedirectly from middleware short-circuits the render, while internal middleware likecreateOriginCheckMiddlewarecan automatically reject cross-origin requests. - The
defineMiddlewareutility inpackages/astro/src/core/middleware/defineMiddleware.tsprovides TypeScript support for theonRequesthandler pattern.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →