What Causes This Error?
This error occurs when you set routing: 'manual' for i18n but don’t have a middleware file. Manual routing requires you to handle locale detection and routing logic yourself via middleware.
The Problem
// astro.config.mjs
export default defineConfig({
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
routing: 'manual', // ❌ Requires middleware
},
});
// No src/middleware.ts file exists!
The Fix
Create Middleware File
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware((context, next) => {
// Your manual i18n routing logic here
return next();
});
Common Scenarios
Basic Manual i18n Middleware
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
const locales = ['en', 'es', 'fr'];
const defaultLocale = 'en';
export const onRequest = defineMiddleware((context, next) => {
// Extract locale from URL
const pathname = context.url.pathname;
const segments = pathname.split('/').filter(Boolean);
const firstSegment = segments[0];
// Check if first segment is a locale
const locale = locales.includes(firstSegment)
? firstSegment
: defaultLocale;
// Store locale for pages to use
context.locals.locale = locale;
return next();
});
With Accept-Language Detection
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
const locales = ['en', 'es', 'fr'];
const defaultLocale = 'en';
function getPreferredLocale(acceptLanguage: string | null): string {
if (!acceptLanguage) return defaultLocale;
const preferred = acceptLanguage
.split(',')
.map(lang => lang.split(';')[0].trim().substring(0, 2))
.find(lang => locales.includes(lang));
return preferred || defaultLocale;
}
export const onRequest = defineMiddleware((context, next) => {
const pathname = context.url.pathname;
// Skip for assets
if (pathname.startsWith('/_') || pathname.includes('.')) {
return next();
}
// Check URL for locale
const urlLocale = pathname.split('/')[1];
if (locales.includes(urlLocale)) {
context.locals.locale = urlLocale;
return next();
}
// Detect from browser
const acceptLanguage = context.request.headers.get('accept-language');
const detectedLocale = getPreferredLocale(acceptLanguage);
// Redirect to locale-prefixed URL
if (pathname === '/') {
return context.redirect(`/${detectedLocale}/`);
}
context.locals.locale = defaultLocale;
return next();
});
Cookie-Based Locale
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
const locales = ['en', 'es', 'fr'];
const defaultLocale = 'en';
export const onRequest = defineMiddleware((context, next) => {
// Check cookie first
const cookieLocale = context.cookies.get('locale')?.value;
if (cookieLocale && locales.includes(cookieLocale)) {
context.locals.locale = cookieLocale;
} else {
// Fallback to URL or default
const urlLocale = context.url.pathname.split('/')[1];
context.locals.locale = locales.includes(urlLocale)
? urlLocale
: defaultLocale;
}
return next();
});
Using Astro’s i18n Utilities
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { middleware } from 'astro:i18n';
// Combine custom logic with Astro's i18n middleware
export const onRequest = defineMiddleware(async (context, next) => {
// Your custom logic first
console.log('Processing:', context.url.pathname);
// Then let Astro handle i18n
return middleware(context, next);
});
Type Definitions
// src/env.d.ts
declare namespace App {
interface Locals {
locale: 'en' | 'es' | 'fr';
}
}
Or Use Automatic Routing
If you don’t need custom logic, use automatic routing instead:
// astro.config.mjs
export default defineConfig({
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
// Remove 'manual' - use automatic routing
routing: {
prefixDefaultLocale: false,
},
},
});
When to Use Manual Routing
Use manual routing when you need:
- Custom locale detection logic
- Cookie/session-based locale
- Complex redirect rules
- Integration with external i18n libraries
- Non-standard URL patterns
Use automatic routing for:
- Standard /[locale]/path patterns
- Simpler setups
- Built-in URL helpers
Quick Checklist
-
routing: 'manual'requires middleware - Create
src/middleware.ts - Handle locale detection in middleware
- Store locale in
context.locals - Consider automatic routing if simpler