Documentation Index Fetch the complete documentation index at: https://mintlify.com/withastro/astro/llms.txt
Use this file to discover all available pages before exploring further.
Middleware in Astro allows you to intercept requests and responses, modify data, add authentication, log requests, and perform other server-side operations before rendering pages.
Basic Middleware
Create middleware by defining a middleware.ts file in your src/ directory:
import { defineMiddleware } from 'astro:middleware' ;
export const onRequest = defineMiddleware (( context , next ) => {
console . log ( 'Request:' , context . request . url );
// Continue to the next middleware or route
return next ();
});
Middleware runs on every request in SSR mode, and during build time for pre-rendered pages.
The Context Object
Middleware receives a context object with request information and helpful utilities:
import { defineMiddleware } from 'astro:middleware' ;
export const onRequest = defineMiddleware (( context , next ) => {
// Access request properties
console . log ( 'URL:' , context . url . pathname );
console . log ( 'Method:' , context . request . method );
console . log ( 'Params:' , context . params );
// Access cookies
const userId = context . cookies . get ( 'userId' );
// Access client IP
const ip = context . clientAddress ;
// Modify locals (shared with pages)
context . locals . user = { id: userId };
return next ();
});
Available Properties
The incoming HTTP request
Route parameters from dynamic routes
Cookie utilities for reading/writing cookies
Shared data object passed to pages
redirect
(path: string, status?: number) => Response
Helper to create redirect responses
rewrite
(path: string) => Promise<Response>
Internally rewrite to a different URL
Authentication Example
Implement authentication in middleware:
import { defineMiddleware } from 'astro:middleware' ;
import { verifySession } from './lib/auth' ;
export const onRequest = defineMiddleware ( async ( context , next ) => {
const sessionId = context . cookies . get ( 'session' )?. value ;
if ( ! sessionId ) {
// No session - allow unauthenticated access
context . locals . user = null ;
return next ();
}
try {
// Verify session and get user
const user = await verifySession ( sessionId );
context . locals . user = user ;
return next ();
} catch ( error ) {
// Invalid session - clear cookie and redirect
context . cookies . delete ( 'session' );
return context . redirect ( '/login' );
}
});
Access the user in your pages:
src/pages/dashboard.astro
---
const { user } = Astro . locals ;
if ( ! user ) {
return Astro . redirect ( '/login' );
}
---
< html >
< body >
< h1 > Welcome, { user . name } ! </ h1 >
</ body >
</ html >
Use context.locals to share data between middleware and your pages. Type it by creating src/env.d.ts.
Sequencing Middleware
Chain multiple middleware functions using sequence:
import { sequence } from 'astro:middleware' ;
import { auth } from './middleware/auth' ;
import { logger } from './middleware/logger' ;
import { cors } from './middleware/cors' ;
export const onRequest = sequence (
logger ,
cors ,
auth
);
Each middleware in the sequence:
import { defineMiddleware } from 'astro:middleware' ;
export const logger = defineMiddleware (( context , next ) => {
const start = Date . now ();
console . log ( `[ ${ new Date (). toISOString () } ] ${ context . request . method } ${ context . url . pathname } ` );
const response = next ();
response . then (() => {
const duration = Date . now () - start ;
console . log ( `Completed in ${ duration } ms` );
});
return response ;
});
import { defineMiddleware } from 'astro:middleware' ;
export const cors = defineMiddleware ( async ( context , next ) => {
const response = await next ();
response . headers . set ( 'Access-Control-Allow-Origin' , '*' );
response . headers . set ( 'Access-Control-Allow-Methods' , 'GET, POST, OPTIONS' );
return response ;
});
Middleware executes in the order specified:
logger runs first
cors runs second
auth runs last
Page handler executes
Response bubbles back through middleware
Any middleware can return early, skipping subsequent middleware: export const auth = defineMiddleware (( context , next ) => {
if ( ! context . locals . user ) {
// Skip remaining middleware and redirect
return context . redirect ( '/login' );
}
return next ();
});
Modifying Responses
Modify responses before they’re sent:
import { defineMiddleware } from 'astro:middleware' ;
export const onRequest = defineMiddleware ( async ( context , next ) => {
const response = await next ();
// Clone response to modify headers
const modifiedResponse = new Response ( response . body , {
status: response . status ,
statusText: response . statusText ,
headers: response . headers
});
// Add custom headers
modifiedResponse . headers . set ( 'X-Custom-Header' , 'value' );
modifiedResponse . headers . set ( 'X-Response-Time' , String ( Date . now ()));
return modifiedResponse ;
});
Redirects and Rewrites
Redirects
Return redirect responses to send users to different URLs:
import { defineMiddleware } from 'astro:middleware' ;
export const onRequest = defineMiddleware (( context , next ) => {
// Redirect old URLs to new ones
if ( context . url . pathname === '/old-page' ) {
return context . redirect ( '/new-page' , 301 );
}
// Redirect based on user state
if ( context . url . pathname . startsWith ( '/admin' ) && ! context . locals . user ?. isAdmin ) {
return context . redirect ( '/forbidden' , 403 );
}
return next ();
});
Rewrites
Internally rewrite requests to different routes without changing the URL:
import { defineMiddleware } from 'astro:middleware' ;
export const onRequest = defineMiddleware ( async ( context , next ) => {
// Rewrite API versioning
if ( context . url . pathname . startsWith ( '/api/v1' )) {
return context . rewrite ( context . url . pathname . replace ( '/v1' , '/v2' ));
}
// Rewrite based on user preferences
if ( context . locals . user ?. locale === 'fr' ) {
return context . rewrite ( `/fr ${ context . url . pathname } ` );
}
return next ();
});
Rewrites keep the original URL in the browser while serving different content. Use redirects to change the browser URL.
Cookie Management
Work with cookies in middleware:
import { defineMiddleware } from 'astro:middleware' ;
export const onRequest = defineMiddleware (( context , next ) => {
// Read cookies
const theme = context . cookies . get ( 'theme' )?. value ?? 'light' ;
const consent = context . cookies . get ( 'consent' );
// Set cookies
context . cookies . set ( 'last-visit' , new Date (). toISOString (), {
path: '/' ,
httpOnly: true ,
secure: true ,
maxAge: 60 * 60 * 24 * 365 // 1 year
});
// Delete cookies
if ( context . url . pathname === '/logout' ) {
context . cookies . delete ( 'session' );
}
context . locals . theme = theme ;
return next ();
});
Cookie Options
Cookie path. Default: ’/’
HTTP-only flag (not accessible via JavaScript)
sameSite
'strict' | 'lax' | 'none'
SameSite attribute
Type Safety
Type your locals object for better TypeScript support:
/// < reference types = "astro/client" />
declare namespace App {
interface Locals {
user : {
id : string ;
name : string ;
email : string ;
isAdmin : boolean ;
} | null ;
theme : 'light' | 'dark' ;
}
}
Now context.locals and Astro.locals are fully typed:
import { defineMiddleware } from 'astro:middleware' ;
export const onRequest = defineMiddleware (( context , next ) => {
// TypeScript knows the shape of locals
context . locals . user = {
id: '123' ,
name: 'John' ,
email: 'john@example.com' ,
isAdmin: false
};
return next ();
});
API Routes
Middleware also runs for API routes:
import type { APIRoute } from 'astro' ;
export const GET : APIRoute = ( context ) => {
// Access data from middleware
const user = context . locals . user ;
if ( ! user ) {
return new Response ( JSON . stringify ({ error: 'Unauthorized' }), {
status: 401 ,
headers: { 'Content-Type' : 'application/json' }
});
}
return new Response ( JSON . stringify ({ data: 'secret' }), {
headers: { 'Content-Type' : 'application/json' }
});
};
Common Patterns
Rate Limiting
Localization
src/middleware/rate-limit.ts
import { defineMiddleware } from 'astro:middleware' ;
const requests = new Map < string , number []>();
export const rateLimit = defineMiddleware (( context , next ) => {
const ip = context . clientAddress ;
const now = Date . now ();
const windowMs = 60 * 1000 ; // 1 minute
const maxRequests = 100 ;
const userRequests = requests . get ( ip ) || [];
const recentRequests = userRequests . filter ( time => now - time < windowMs );
if ( recentRequests . length >= maxRequests ) {
return new Response ( 'Too many requests' , { status: 429 });
}
recentRequests . push ( now );
requests . set ( ip , recentRequests );
return next ();
});
import { defineMiddleware } from 'astro:middleware' ;
const supportedLocales = [ 'en' , 'fr' , 'es' ];
export const i18n = defineMiddleware (( context , next ) => {
// Get locale from URL or cookies
const urlLocale = context . url . pathname . split ( '/' )[ 1 ];
const cookieLocale = context . cookies . get ( 'locale' )?. value ;
const locale = supportedLocales . includes ( urlLocale )
? urlLocale
: cookieLocale || 'en' ;
context . locals . locale = locale ;
context . cookies . set ( 'locale' , locale );
return next ();
});
Conditional Middleware
Run middleware conditionally based on the route:
import { defineMiddleware , sequence } from 'astro:middleware' ;
import { auth } from './middleware/auth' ;
import { logger } from './middleware/logger' ;
const conditionalAuth = defineMiddleware (( context , next ) => {
// Only run auth on protected routes
if ( context . url . pathname . startsWith ( '/dashboard' )) {
return auth ( context , next );
}
return next ();
});
export const onRequest = sequence (
logger ,
conditionalAuth
);
Actions Handle form submissions
API Routes Build API endpoints
Authentication Authentication patterns