Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Astro's middleware system enables intercepting and modifying requests and responses with support for chaining multiple handlers.
Type-safe helper for defining middleware functions.
/**
* Defines a middleware function with type safety
*/
function defineMiddleware(fn: MiddlewareHandler): MiddlewareHandler;
type MiddlewareHandler = (
context: APIContext,
next: MiddlewareNext
) => Promise<Response> | Response | Promise<void> | void;
type MiddlewareNext = (rewritePayload?: RewritePayload) => Promise<Response>;import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware(async (context, next) => {
console.log('Request to:', context.url.pathname);
context.locals.user = await getUser(context.request);
const response = await next();
response.headers.set('X-Custom-Header', 'value');
return response;
});Chains multiple middleware handlers to run in sequence.
/**
* Chains multiple middleware handlers
*/
function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler;import { sequence } from 'astro:middleware';
const auth = async (context, next) => {
if (!context.cookies.get('session')) {
return new Response('Unauthorized', { status: 401 });
}
return next();
};
const logging = async (context, next) => {
console.log('Request:', context.request.url);
return next();
};
const headers = async (context, next) => {
const response = await next();
response.headers.set('X-Custom-Header', 'value');
return response;
};
export const onRequest = sequence(auth, logging, headers);Creates an API context object for use in middleware or custom handlers.
/**
* Creates an API context object
*/
function createContext(options: CreateContext): APIContext;
interface CreateContext {
request: Request;
params?: Params;
userDefinedLocales?: string[];
defaultLocale: string;
locals: App.Locals;
}import { createContext } from 'astro/middleware';
const context = createContext({
request: new Request('https://example.com/page'),
params: { slug: 'my-page' },
defaultLocale: 'en',
locals: {},
});Attempts to serialize the locals object to JSON.
/**
* Attempts to serialize locals to JSON
* @throws Error if value is not serializable
*/
function trySerializeLocals(value: unknown): string;import { trySerializeLocals } from 'astro/middleware';
try {
const serialized = trySerializeLocals(context.locals);
} catch (error) {
console.error('Locals contain non-serializable values');
}The context object passed to middleware and API routes.
interface APIContext {
request: Request;
params: Params;
props: Props;
locals: App.Locals;
cookies: AstroCookies;
url: URL;
clientAddress: string;
site: URL | undefined;
generator: string;
redirect(path: string, status?: number): Response;
rewrite(payload: RewritePayload): Promise<Response>;
isPrerendered: boolean;
routePattern: string;
preferredLocale: string | undefined;
preferredLocaleList: string[] | undefined;
currentLocale: string | undefined;
originPathname: string;
session?: AstroSession;
getActionResult<T>(action: Action<T>): ActionResult<T> | undefined;
callAction<T>(action: Action<T>, input: unknown): Promise<ActionResult<T>>;
csp: AstroSharedContextCsp;
}
type Params = Record<string, string | undefined>;
type Props = Record<string, any>;
type RewritePayload = string | URL | Request;interface AstroCookies {
get(key: string, options?: AstroCookieGetOptions): AstroCookie | undefined;
has(key: string): boolean;
set(key: string, value: string | number | boolean | Record<string, any>, options?: AstroCookieSetOptions): void;
delete(key: string, options?: AstroCookieDeleteOptions): void;
headers(): Iterator<string>;
}
interface AstroCookie {
value: string;
json<T = any>(): T;
number(): number;
boolean(): boolean;
}
interface AstroCookieGetOptions {
decode?: (value: string) => string;
}
interface AstroCookieSetOptions {
domain?: string;
expires?: Date;
httpOnly?: boolean;
maxAge?: number;
path?: string;
sameSite?: boolean | 'lax' | 'strict' | 'none';
secure?: boolean;
encode?: (value: string) => string;
}
interface AstroCookieDeleteOptions {
domain?: string;
path?: string;
}export const onRequest = defineMiddleware(async (context, next) => {
const sessionCookie = context.cookies.get('session');
if (sessionCookie) {
const sessionData = sessionCookie.json();
}
context.cookies.set('user_id', '123', {
httpOnly: true,
secure: true,
maxAge: 60 * 60 * 24 * 7,
path: '/',
});
context.cookies.delete('old_cookie');
return next();
});Middleware is configured in src/middleware.ts or src/middleware/index.ts:
// src/middleware.ts
import { defineMiddleware, sequence } from 'astro:middleware';
const auth = defineMiddleware(async (context, next) => {
return next();
});
const logging = defineMiddleware(async (context, next) => {
return next();
});
export const onRequest = sequence(auth, logging);Define types for context.locals in src/env.d.ts:
/// <reference types="astro/client" />
declare namespace App {
interface Locals {
user?: {
id: string;
name: string;
email: string;
};
session?: {
id: string;
expiresAt: Date;
};
}
}export const onRequest = defineMiddleware(async (context, next) => {
const token = context.cookies.get('auth_token')?.value;
if (!token) {
return new Response('Unauthorized', { status: 401 });
}
const user = await verifyToken(token);
if (!user) {
return new Response('Invalid token', { status: 401 });
}
context.locals.user = user;
return next();
});export const onRequest = defineMiddleware(async (context, next) => {
const start = Date.now();
const response = await next();
const duration = Date.now() - start;
console.log(
`${context.request.method} ${context.url.pathname} - ${response.status} (${duration}ms)`
);
return response;
});export const onRequest = defineMiddleware(async (context, next) => {
const response = await next();
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
return response;
});export const onRequest = defineMiddleware(async (context, next) => {
if (context.url.pathname.startsWith('/admin')) {
const user = context.locals.user;
if (!user || !user.isAdmin) {
return context.redirect('/login');
}
}
return next();
});next().context.locals should only contain serializable data if you need to pass it between server and client.sequence(). Ensure authentication runs before route handlers.