Internationalization in Next.js error files
The Next.js App Router's file convention provides two files that can be used for error handling:
This page provides practical guides for these cases.
Have a look at the App Router example to explore a working app with error handling.
not-found.js
Next.js renders the closest not-found
page when a route segment calls the notFound
function (opens in a new tab). We can use this mechanism to provide a localized 404 page by adding a not-found
file within the [locale]
folder.
import {useTranslations} from 'next-intl';
export default function NotFoundPage() {
const t = useTranslations('NotFoundPage');
return <h1>{t('title')}</h1>;
}
Note however that Next.js will only render this page when the notFound
function is called from within a route, not for all unknown routes in general.
Catching unknown routes
To catch unknown routes too, you can define a catch-all route that explicitly calls the notFound
function.
import {notFound} from 'next/navigation';
export default function CatchAllPage() {
notFound();
}
After this change, all requests that are matched within the [locale]
segment will render the not-found
page when an unknown route is encountered (e.g. /en/unknown
).
Catching non-localized requests
When the user requests a route that is not matched by the next-intl
middleware, there's no locale associated with the request (depending on your matcher
config, e.g. /unknown.txt
might not be matched).
You can add a root not-found
page to handle these cases too.
'use client';
import Error from 'next/error';
export default function NotFound() {
return (
<html lang="en">
<body>
<Error statusCode={404} />
</body>
</html>
);
}
Note that the presence of app/not-found.tsx
requires that a root layout is available, even if it's just passing children
through.
// Since we have a root `not-found.tsx` page, a layout file
// is required, even if it's just passing children through.
export default function RootLayout({children}) {
return children;
}
For the 404 page to render, we need to call the notFound
function in i18n.ts
when we detect an incoming locale
param that isn't a valid locale.
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
// Can be imported from a shared config
const locales = ['en', 'de'];
export default getRequestConfig(async ({locale}) => {
// Validate that the incoming `locale` parameter is valid
if (!locales.includes(locale as any)) notFound();
return {
// ...
};
});
Note that next-intl
will also call the notFound
function internally when it tries to resolve a locale for component usage, but can not find one attached to the request (either from the middleware, or manually via unstable_setRequestLocale
(opens in a new tab)).
error.js
When an error
file is defined, Next.js creates an error boundary within your layout (opens in a new tab) that wraps pages accordingly to catch runtime errors:
Since the error
file must be defined as a Client Component, you have to use NextIntlClientProvider
to provide messages in case the error
file renders.
import pick from 'lodash/pick';
import {NextIntlClientProvider, useMessages} from "next-intl";
export default async function LocaleLayout({children}) {
// ...
const messages = useMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider
locale={locale}
messages={pick(messages, 'Error')}
>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
Once NextIntlClientProvider
is in place, you can use functionality from next-intl
in the error
file:
'use client';
import {useTranslations} from 'next-intl';
export default function Error({error, reset}) {
const t = useTranslations('Error');
return (
<div>
<h1>{t('title')}</h1>
<button onClick={reset}>{t('retry')}</button>
</div>
);
}
Note that error.tsx
is loaded right after your app has initialized. If your app is performance-senstive and you want to avoid loading translation functionality from next-intl
as part of the initial bundle, you can export a lazy reference from your error
file:
'use client';
import {lazy} from 'react';
// Move error content to a separate chunk and load it only when needed
export default lazy(() => import('./Error'));