withastro/astro · error · AstroError
MissingMiddlewareForInternationalization
MissingMiddlewareForInternationalization
Error message
Your configuration setting `i18n.routing: 'manual'` requires you to provide your own i18n `middleware` file.
What it means
When no user middleware file exists, Astro's Vite middleware plugin serves a generated noop entry (NOOP_MIDDLEWARE). With i18n.routing set to 'manual', Astro refuses that noop: manual routing means Astro performs no locale routing for you, so a middleware file of your own is mandatory, and AstroErrorData.MissingMiddlewareForInternationalization is thrown from the plugin's load hook.
Solutions
- Create src/middleware.ts that applies Astro's i18n middleware: export const onRequest = sequence(middleware({ redirectToDefaultLocale: true, prefixDefaultLocale: false })) using imports from 'astro:i18n' and 'astro:middleware'
- Or write your own routing middleware exporting onRequest and implement the locale handling manually
- Verify the file location and export: it must live at the srcDir root and export onRequest (or onPreparation)
- If you did not intend manual routing, adjust the i18n config instead of keeping 'manual' without middleware
Example fix
// before: astro.config.mjs
export default defineConfig({ i18n: { defaultLocale: 'en', locales: ['en', 'fr'], routing: 'manual' } });
// and no src/middleware.ts
// after: create src/middleware.ts
import { sequence } from 'astro:middleware';
import { middleware } from 'astro:i18n';
export const onRequest = sequence(
middleware({
redirectToDefaultLocale: true,
prefixDefaultLocale: false,
})
); Defensive patterns
Strategy: validation
Validate before calling
// run as a preflight/CI check in Node
import { existsSync } from 'node:fs';
import config from './astro.config.mjs';
if (config.i18n?.routing === 'manual' && !existsSync('./src/middleware.ts')) {
throw new Error('i18n.routing "manual" requires src/middleware.ts exporting onRequest');
} Prevention
- Create src/middleware.ts in the same commit that enables i18n routing 'manual'
- Keep the file at the srcDir root, named middleware.ts, exporting onRequest
- Commit the middleware file — CI checkouts without it turn the config into a build error
When it happens
Trigger: Configuring i18n with routing: 'manual' (in Astro 5 this is the default once i18n is configured) while the project has no src/middleware.ts/js exporting onRequest; the middleware file was renamed, moved out of srcDir, or lost in a merge.
Common situations: Upgrading to Astro 5, where manual routing became the default and previously optional middleware became required; enabling i18n on an existing project that never had middleware; CI checkout where src/middleware.ts is not committed.
Related errors
- IncorrectStrategyForI18n
- InvalidI18nMiddlewareConfiguration
- MissingIndexForInternationalizationError
- MissingLocaleError
- `Astro.session` was accessed but no session storage is…
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/b5d98304d2504558.
Report an issue: GitHub.
Appendix: source
Thrown at packages/astro/src/core/middleware/vite-plugin.ts:80
userMiddlewareIsPresent = !!middlewareId;
if (middlewareId) {
resolvedMiddlewareId = middlewareId.id;
return MIDDLEWARE_RESOLVED_MODULE_ID;
} else if (hasIntegrationMiddleware) {
return MIDDLEWARE_RESOLVED_MODULE_ID;
} else {
return NOOP_MIDDLEWARE;
}
},
},
load: {
filter: {
id: new RegExp(`^(${NOOP_MIDDLEWARE}|${MIDDLEWARE_RESOLVED_MODULE_ID})$`),
},
async handler(id) {
if (id === NOOP_MIDDLEWARE) {
if (!userMiddlewareIsPresent && settings.config.i18n?.routing === 'manual') {
throw new AstroError(MissingMiddlewareForInternationalization);
}
return { code: 'export const onRequest = (_, next) => next()' };
}
if (id === MIDDLEWARE_RESOLVED_MODULE_ID) {
if (!userMiddlewareIsPresent && settings.config.i18n?.routing === 'manual') {
throw new AstroError(MissingMiddlewareForInternationalization);
}
const preMiddleware = createMiddlewareImports(settings.middlewares.pre, 'pre');
const postMiddleware = createMiddlewareImports(settings.middlewares.post, 'post');
const code = `
${
userMiddlewareIsPresent
? `import { onRequest as userOnRequest } from '${resolvedMiddlewareId}';`
: ''
}
import { sequence } from 'astro:middleware';View on GitHub (pinned to 52e6c34790)