AstroShowcase
Routing Intermediate Astro 6.3

Middleware

Intercept every request and response — add auth checks, inject locals, rewrite URLs, and set headers globally.

What is Astro Middleware?

Middleware is a function that runs on every request, before any page or API route is rendered. It receives the full RequestContext and a next() callback. You can:

  • Inject data into Astro.locals (e.g. current user)
  • Redirect unauthenticated users
  • Add/modify response headers
  • Rewrite request URLs
  • Log requests for analytics
▶ Request Inspector Demo

Astro middleware runs on every request before any page renders. It can inject data into Astro.locals, redirect users, or add response headers.

middleware/index.ts · Locals injected for this request
method GET path /demos/middleware requestId J1JX5N49 timestamp 2026-05-08T03:15:47.986Z locale en-US authenticated false ip 216.73.216.78 userAgent Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatibl…
See middleware/index.ts skeleton
import { defineMiddleware } from 'astro:middleware'

export const onRequest = defineMiddleware(async (context, next) => {
  context.locals.requestId = Math.random().toString(36)
  context.locals.authenticated = await checkAuth(context)
  return next() // continues to the page
})

Creating middleware

1

Create src/middleware.ts

Astro auto-discovers src/middleware.ts (or .js). No configuration needed.

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware'

export const onRequest = defineMiddleware(async (context, next) => {
  // Runs before EVERY page and API route

  // 1. Inject a request ID into locals
  context.locals.requestId = crypto.randomUUID()

  // 2. Check auth (example)
  const session = context.cookies.get('session')
  context.locals.user = session ? await getUserFromSession(session.value) : null

  // 3. Guard a route
  if (context.url.pathname.startsWith('/admin') && !context.locals.user) {
    return context.redirect('/login')
  }

  // 4. Continue to the page
  const response = await next()

  // 5. Add a custom header to every response
  response.headers.set('X-Request-Id', context.locals.requestId)

  return response
})
2

Use locals in your page

Anything you set on context.locals is available as Astro.locals in every .astro file and API route.

---
// Any page or layout
const { user, requestId } = Astro.locals
---
{user ? <p>Hello, {user.name}!</p> : <p>Guest</p>}
3

Type your locals (optional but recommended)

Extend the App.Locals interface in src/env.d.ts for full TypeScript support.

// src/env.d.ts
declare namespace App {
  interface Locals {
    requestId: string
    user: { id: number; name: string; email: string } | null
  }
}

Middleware sequence

Multiple middleware functions can be chained using sequence():

import { defineMiddleware, sequence } from 'astro:middleware'

const logging = defineMiddleware(async (ctx, next) => {
  console.log(`→ ${ctx.request.method} ${ctx.url.pathname}`)
  return next()
})

const auth = defineMiddleware(async (ctx, next) => {
  // ...auth logic
  return next()
})

export const onRequest = sequence(logging, auth)
Tip

Middleware runs on every request, including API routes. Keep it fast — avoid blocking I/O unless necessary.

Info

Middleware only works with output: 'server' or output: 'hybrid'. Static pages are pre-built and never pass through middleware at request time.

Further reading