Error pages like error.tsx and not-found.tsx don't inherit the locale from your app/[lang]/ pattern. Users see English error messages regardless of their language setting.
This guide shows you how to localise both files using the correct patterns for each.
Different error files have different constraints:
| File | Component type | How to access locale |
|---|---|---|
error.tsx | Client Component (required) | useParams() hook |
not-found.tsx | Server Component | headers() referer |
error.tsx must be a Client Component because React error boundaries require client-side JavaScript. If your main dictionary loader uses async/await and runs only on the server, it won't work in a Client Component.
For not-found.tsx, you can use a Server Component, but it doesn't receive the params prop directly. You need to extract the locale from the request headers.
In Next.js 16, dynamic route params are typed as string because their values aren't known until runtime. Users can type any URL into the address bar. The Next.js docs recommend using runtime validation to handle invalid params:
Use assertValidLocale() early in your layout and pages. Invalid locales trigger a 404 instead of crashing your app:
Most dictionary loaders are async and server-only. That won't work in a Client Component. Create a separate sync loader with just the error strings:
Keep this dictionary minimal. Error pages should load fast, and since this dictionary is bundled into the client, every byte counts.
The error.tsx file handles runtime errors. It must be a Client Component:
The params?.lang ?? "en" fallback: useParams() can return null in error boundaries, so always provide a default.
Skip assertValidLocale() here because error.tsx is a fallback. The dictionary loader already handles unknown locales by falling back to English.
The not-found.tsx file can be a Server Component, so you can use your existing async dictionary loader. Add the not-found key to your JSON dictionaries:
First, add a helper function to extract the locale from URLs:
Then use it in your not-found page to extract the locale from the referer header:
The locale detection from the referer header works because the user navigated from a page within your app. If the referer is empty or contains an invalid locale, it falls back to the default.
To trigger your localised 404 for unknown paths within a locale, add a catch-all route:
This ensures /en/nonexistent shows your localised not-found page instead of a generic 404. Without this catch-all, Next.js might render a default error page that bypasses your translations.
This catch-all only triggers when no other route matches. If you have routes like app/[lang]/blog/[slug]/page.tsx, they match first. The catch-all handles everything else.
Verify everything works:
- Visit
/en/nonexistent→ English "Page Not Found" - Visit
/de/nonexistent→ German "Seite nicht gefunden" - Visit
/invalid/anything→ Default locale 404 (viaassertValidLocale) - Trigger a runtime error → Localised error message with retry button
global-error.tsx handles errors in the root layout. It completely replaces your layout tree, including app/[lang]/layout.tsx. That means it has no access to the locale param.
For most applications, you don't need global-error.tsx. The segment-level error.tsx inside app/[lang]/ handles errors within your localised routes, which covers the majority of cases. global-error.tsx only triggers when the root layout itself throws an error.
Files to create:
app/[lang]/error.tsx- Client Component withuseParams()app/[lang]/not-found.tsx- Server Component withheaders()refererapp/[lang]/[...slug]/page.tsx- Catch-all to trigger localised 404get-error-dictionary.ts- Sync dictionary loader for Client Components
Key patterns:
- Runtime validation with
assertValidLocale()to narrowstringtoLocale - Separate sync dictionary for Client Components
- Referer header extraction for locale detection in not-found pages
- Null-safe
useParams()access with fallback