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
Thrown by the middleware vite plugin's load handler for the NOOP_MIDDLEWARE virtual module when i18n.routing is set to 'manual' and the user has not provided a middleware file. With manual routing, Astro does not inject its own i18n middleware, so the user must supply src/middleware.{ts,js} that wires up locale detection/routing.
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 d081033d5f)
Solutions
- Create src/middleware.ts that defines and exports an onRequest handler (or sequences i18n middleware).
- If you did not intend manual routing, change i18n.routing back to 'automatic' (or remove the setting).
- Import and use Astro's i18n helpers (e.g. from astro:i18n) inside your middleware to implement routing manually.
Example fix
// before - astro.config.mjs: i18n: { routing: 'manual' }, no middleware file
// after - create src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware((context, next) => {
// your locale detection/routing logic
return next();
}); Defensive patterns
Strategy: validation
Validate before calling
// Ensure a middleware file exists when i18n.routing is 'manual'.
import { existsSync } from 'node:fs';
import { resolve } from 'node:path';
const hasMiddleware = existsSync(resolve('src/middleware.ts')) ||
existsSync(resolve('src/middleware.js'));
if (config.i18n?.routing === 'manual' && !hasMiddleware) {
throw new Error('Create src/middleware.ts for manual i18n routing');
} Type guard
function requiresI18nMiddleware(config: { i18n?: { routing?: string } }, hasMiddlewareFile: boolean): boolean {
return config.i18n?.routing === 'manual' && !hasMiddlewareFile;
} Prevention
- Create src/middleware.ts whenever i18n.routing is 'manual'.
- Run a config sanity check in CI for the i18n+middleware pairing.
- Document the requirement when enabling manual routing.
When it happens
Trigger: Configuring astro.config with i18n.routing: 'manual' but having no src/middleware.ts file. When the bundler loads the NOOP_MIDDLEWARE module and detects no user middleware, it throws MissingMiddlewareForInternationalization.
Common situations: Switching i18n.routing from 'automatic' (or default) to 'manual' to customize locale handling but forgetting to create the middleware file; setting up i18n for the first time with manual routing.
Related errors
- i18nNotEnabled
- MissingIndexForInternationalizationError
- SessionStorageInitError
- EnvPrefixConflictsWithSecret
- EnvInvalidVariables
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/b5d98304d2504558.
Report an issue: GitHub.