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

  1. Create src/middleware.ts that defines and exports an onRequest handler (or sequences i18n middleware).
  2. If you did not intend manual routing, change i18n.routing back to 'automatic' (or remove the setting).
  3. 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

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


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/b5d98304d2504558. Report an issue: GitHub.