CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-astro

Astro is a modern site builder with web best practices, performance, and DX front-of-mind.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

middleware.mddocs/

Middleware

Astro's middleware system enables intercepting and modifying requests and responses with support for chaining multiple handlers.

Capabilities

Define Middleware

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;
});

Sequence Middleware

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);

Create Context

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: {},
});

Try Serialize 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');
}

API Context

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;

Cookie Management

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 Configuration

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);

Locals Type Safety

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;
    };
  }
}

Common Middleware Patterns

Authentication

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();
});

Logging

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;
});

Adding Headers

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;
});

Route Protection

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();
});

Common Issues

  • Response modification: Headers and other response properties must be modified on the response object returned from next().
  • Locals serialization: context.locals should only contain serializable data if you need to pass it between server and client.
  • Middleware order: Middleware runs in the order defined in sequence(). Ensure authentication runs before route handlers.

docs

assets.md

cli-and-build.md

configuration.md

container.md

content-collections.md

content-loaders.md

dev-toolbar.md

environment.md

i18n.md

index.md

integrations.md

middleware.md

server-actions.md

ssr-and-app.md

transitions.md

tile.json