Astro's Middleware is a powerful feature that allows you to intercept and handle requests before they reach pages, enabling authentication, redirects, response modification, and more.
Core Concepts:
Middleware runs on the server side, can access the request object, and executes custom logic in the request processing chain.
Creating Middleware:
typescript// src/middleware.ts import { defineMiddleware } from 'astro:middleware'; import type { MiddlewareResponseHandler } from 'astro'; export const onRequest: MiddlewareResponseHandler = async (context, next) => { // Handle request here // Call next() to continue the processing chain const response = await next(); // Can modify response response.headers.set('X-Custom-Header', 'Custom Value'); return response; };
Middleware Context:
typescript// src/middleware.ts export const onRequest = async (context, next) => { // context contains the following properties: console.log(context.request); // Request object console.log(context.url); // URL object console.log(context.cookies); // Cookies console.log(context.locals); // Local data storage console.log(context.site); // Site configuration const response = await next(); return response; };
Use Cases:
- Authentication and Authorization:
typescript// src/middleware.ts export const onRequest = async (context, next) => { const protectedRoutes = ['/dashboard', '/settings', '/profile']; const currentPath = context.url.pathname; if (protectedRoutes.some(route => currentPath.startsWith(route))) { const token = context.cookies.get('auth-token'); if (!token) { return context.redirect('/login'); } // Verify token const user = await verifyToken(token.value); if (!user) { return context.redirect('/login'); } // Store user info in locals context.locals.user = user; } return next(); };
- Redirect Management:
typescript// src/middleware.ts export const onRequest = async (context, next) => { const redirects = { '/old-url': '/new-url', '/old-page': '/new-page', }; const currentPath = context.url.pathname; if (redirects[currentPath]) { return context.redirect(redirects[currentPath], 301); } return next(); };
- Internationalization (i18n):
typescript// src/middleware.ts export const onRequest = async (context, next) => { const supportedLocales = ['en', 'zh', 'ja']; const defaultLocale = 'en'; // Get language from URL const pathSegments = context.url.pathname.split('/'); const locale = pathSegments[1]; // Check if it's a supported language if (supportedLocales.includes(locale)) { context.locals.locale = locale; return next(); } // Get language from Accept-Language header const acceptLanguage = context.request.headers.get('accept-language'); const browserLocale = acceptLanguage?.split(',')[0].split('-')[0] || defaultLocale; const targetLocale = supportedLocales.includes(browserLocale) ? browserLocale : defaultLocale; return context.redirect(`/${targetLocale}${context.url.pathname}`); };
- Logging:
typescript// src/middleware.ts export const onRequest = async (context, next) => { const startTime = Date.now(); const response = await next(); const duration = Date.now() - startTime; console.log(`${context.request.method} ${context.url.pathname} - ${duration}ms`); return response; };
- CORS Handling:
typescript// src/middleware.ts export const onRequest = async (context, next) => { const response = await next(); response.headers.set('Access-Control-Allow-Origin', '*'); response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); return response; };
Conditional Middleware:
typescript// src/middleware.ts export const onRequest = async (context, next) => { // Only apply middleware to specific paths if (context.url.pathname.startsWith('/api/')) { console.log('API request:', context.url.pathname); } return next(); };
Middleware Priority:
Astro supports defining middleware at different levels:
- Global Middleware:
src/middleware.ts- Applied to all requests - Route Middleware: Defined in specific route directories - Applied to that route and its sub-routes
typescript// src/middleware.ts (global) export const onRequest = async (context, next) => { console.log('Global middleware'); return next(); };
typescript// src/pages/dashboard/middleware.ts (route-specific) export const onRequest = async (context, next) => { console.log('Dashboard middleware'); return next(); };
Using locals to Pass Data:
typescript// src/middleware.ts export const onRequest = async (context, next) => { // Set data in middleware context.locals.user = await getUser(context); context.locals.theme = 'dark'; const response = await next(); return response; };
astro--- // Use locals in pages const user = Astro.locals.user; const theme = Astro.locals.theme; --- <h1>Welcome, {user?.name}</h1> <p>Current theme: {theme}</p>
Error Handling:
typescript// src/middleware.ts export const onRequest = async (context, next) => { try { return await next(); } catch (error) { console.error('Middleware error:', error); return new Response('Internal Server Error', { status: 500, headers: { 'Content-Type': 'text/plain' }, }); } };
Performance Optimization:
typescript// src/middleware.ts export const onRequest = async (context, next) => { // Cache frequently accessed data const cacheKey = `user:${context.cookies.get('user-id')?.value}`; const cachedUser = await cache.get(cacheKey); if (cachedUser) { context.locals.user = cachedUser; return next(); } const user = await fetchUser(context.cookies.get('user-id')?.value); await cache.set(cacheKey, user, { ttl: 3600 }); context.locals.user = user; return next(); };
Best Practices:
- Keep middleware concise and efficient
- Avoid time-consuming operations in middleware
- Use caching to optimize performance
- Use locals appropriately to pass data
- Implement proper error handling
- Consider middleware execution order
- Only use redirects when necessary
Astro's middleware provides powerful request processing capabilities for building complex applications, especially suitable for projects requiring authentication, authorization, internationalization, and other features.