Handling unsupported language codes
Problem
An application uses the URL path (e.g., /en/, /fr/) to determine language, but users can manually type any value, like /xx/about. When this value doesn't match a supported language, the application may crash, show a generic error, or display un-translated content, failing to guide the user back to a valid experience.
Solution
Use a middleware to intercept all incoming requests. This middleware will validate the language code from the URL against a definitive list of supported languages. If the code is not supported, the request will be rewritten to a "Not Found" page before it reaches the application logic.
Steps
1. Define your supported languages
Create a central configuration file to store your list of valid language codes (locales). This makes the list reusable for your middleware and other parts of your app.
// i18n.config.ts
export const locales = ['en', 'es', 'fr'];
2. Create the middleware file
Create a new file named middleware.ts at the root of your project (or in your src/ directory). Next.js will automatically detect this file and run it on requests.
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { locales } from './i18n.config';
export function middleware(request: NextRequest) {
// Logic will go here in the next step
}
3. Add the validation logic
Inside the middleware function, get the pathname from the request. We need to check the first segment of the path (e.g., en in /en/about) and see if it's a valid, supported language.
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { locales } from './i18n.config';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// 1. Handle the root path separately
// (This will be handled by the next recipe: "Detecting a user's
// preferred language"). For now, we just let it pass.
if (pathname === '/') {
return NextResponse.next();
}
// 2. Extract the language code from the path
const langCode = pathname.split('/')[1];
// 3. Check if the language code is in our list
if (locales.includes(langCode)) {
// Language is valid, continue to the requested page
return NextResponse.next();
}
// 4. If the language is not valid, rewrite to a 404 page
// This keeps the invalid URL in the browser bar
const url = request.nextUrl.clone();
url.pathname = `/404`; // Assumes you have an app/404.tsx file
return NextResponse.rewrite(url);
}
This logic checks every request. If the URL is /fr/about, langCode is fr, it's found in locales, and the request continues. If the URL is /xx/about, langCode is xx, it's not found, and the user is shown the 404 page without the app trying to process the invalid request.
4. Configure the middleware matcher
To make your middleware more efficient, you should tell it which paths to run on. We want it to run on page requests but skip static files and API routes.
Add a config object to the bottom of your middleware.ts file.
// middleware.ts
// ... (the middleware function from above)
export const config = {
matcher: [
// Skip all paths that start with:
// - api (API routes)
// - _next/static (static files)
// - _next/image (image optimization files)
// - favicon.ico (favicon file)
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};
This regular expression tells the middleware to run on all paths except for the ones that are typically for static assets or API calls. This prevents unnecessary validation on every image, font, or data request.