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

> Add server-side logic that runs before page rendering with Astro middleware

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:

```typescript src/middleware.ts theme={null}
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();
});
```

<Note>
  Middleware runs on every request in SSR mode, and during build time for pre-rendered pages.
</Note>

## The Context Object

Middleware receives a `context` object with request information and helpful utilities:

```typescript src/middleware.ts theme={null}
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

<ParamField path="request" type="Request">
  The incoming HTTP request
</ParamField>

<ParamField path="url" type="URL">
  Parsed URL object
</ParamField>

<ParamField path="params" type="Record<string, string>">
  Route parameters from dynamic routes
</ParamField>

<ParamField path="cookies" type="AstroCookies">
  Cookie utilities for reading/writing cookies
</ParamField>

<ParamField path="locals" type="App.Locals">
  Shared data object passed to pages
</ParamField>

<ParamField path="redirect" type="(path: string, status?: number) => Response">
  Helper to create redirect responses
</ParamField>

<ParamField path="rewrite" type="(path: string) => Promise<Response>">
  Internally rewrite to a different URL
</ParamField>

<ParamField path="clientAddress" type="string">
  Client's IP address
</ParamField>

## Authentication Example

Implement authentication in middleware:

```typescript src/middleware.ts theme={null}
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:

```astro src/pages/dashboard.astro theme={null}
---
const { user } = Astro.locals;

if (!user) {
  return Astro.redirect('/login');
}
---

<html>
  <body>
    <h1>Welcome, {user.name}!</h1>
  </body>
</html>
```

<Tip>
  Use `context.locals` to share data between middleware and your pages. Type it by creating `src/env.d.ts`.
</Tip>

## Sequencing Middleware

Chain multiple middleware functions using `sequence`:

```typescript src/middleware.ts theme={null}
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:

```typescript src/middleware/logger.ts theme={null}
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;
});
```

```typescript src/middleware/cors.ts theme={null}
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;
});
```

<AccordionGroup>
  <Accordion title="Execution Order">
    Middleware executes in the order specified:

    1. `logger` runs first
    2. `cors` runs second
    3. `auth` runs last
    4. Page handler executes
    5. Response bubbles back through middleware
  </Accordion>

  <Accordion title="Early Returns">
    Any middleware can return early, skipping subsequent middleware:

    ```typescript theme={null}
    export const auth = defineMiddleware((context, next) => {
      if (!context.locals.user) {
        // Skip remaining middleware and redirect
        return context.redirect('/login');
      }
      return next();
    });
    ```
  </Accordion>
</AccordionGroup>

## Modifying Responses

Modify responses before they're sent:

```typescript src/middleware.ts theme={null}
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:

```typescript src/middleware.ts theme={null}
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:

```typescript src/middleware.ts theme={null}
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();
});
```

<Warning>
  Rewrites keep the original URL in the browser while serving different content. Use redirects to change the browser URL.
</Warning>

## Cookie Management

Work with cookies in middleware:

```typescript src/middleware.ts theme={null}
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

<ParamField path="path" type="string">
  Cookie path. Default: '/'
</ParamField>

<ParamField path="domain" type="string">
  Cookie domain
</ParamField>

<ParamField path="expires" type="Date">
  Expiration date
</ParamField>

<ParamField path="maxAge" type="number">
  Max age in seconds
</ParamField>

<ParamField path="httpOnly" type="boolean">
  HTTP-only flag (not accessible via JavaScript)
</ParamField>

<ParamField path="secure" type="boolean">
  Secure flag (HTTPS only)
</ParamField>

<ParamField path="sameSite" type="'strict' | 'lax' | 'none'">
  SameSite attribute
</ParamField>

## Type Safety

Type your locals object for better TypeScript support:

```typescript src/env.d.ts theme={null}
/// <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:

```typescript src/middleware.ts theme={null}
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:

```typescript src/pages/api/data.ts theme={null}
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

<Tabs>
  <Tab title="Rate Limiting">
    ```typescript src/middleware/rate-limit.ts theme={null}
    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();
    });
    ```
  </Tab>

  <Tab title="Localization">
    ```typescript src/middleware/i18n.ts theme={null}
    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();
    });
    ```
  </Tab>

  <Tab title="Security Headers">
    ```typescript src/middleware/security.ts theme={null}
    import { defineMiddleware } from 'astro:middleware';

    export const security = 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');
      response.headers.set(
        'Content-Security-Policy',
        "default-src 'self'; script-src 'self' 'unsafe-inline'"
      );

      return response;
    });
    ```
  </Tab>
</Tabs>

## Conditional Middleware

Run middleware conditionally based on the route:

```typescript src/middleware.ts theme={null}
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
);
```

## Related Resources

<CardGroup cols={2}>
  <Card title="Actions" icon="bolt" href="/features/actions">
    Handle form submissions
  </Card>

  <Card title="SSR" icon="server" href="/features/ssr-and-ssg">
    Server-side rendering
  </Card>

  <Card title="API Routes" icon="code" href="/concepts/routing">
    Build API endpoints
  </Card>

  <Card title="Authentication" icon="lock" href="/features/middleware">
    Authentication patterns
  </Card>
</CardGroup>
