Setup locale-based routing
Prefer to watch a video?
In order to use unique pathnames for every language that your app supports, next-intl
can be used to handle the following routing setups:
- Prefix-based routing (e.g.
/en/about
) - Domain-based routing (e.g.
en.example.com/about
)
In either case, next-intl
integrates with the App Router by using a top-level [locale]
dynamic segment that can be used to provide content in different languages.
Initial setup
To get started with locale-based routing, we’ll set up the following files:
src/i18n/routing.ts
We’ll use routing.ts
as a central place to define our routing configuration:
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
// A list of all locales that are supported
locales: ['en', 'de'],
// Used when no locale matches
defaultLocale: 'en'
});
Depending on your requirements, you may wish to customize your routing configuration later—but let’s finish with the setup first.
src/middleware.ts
Once we have our routing configuration in place, we can use it to set up the middleware:
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
export default createMiddleware(routing);
export const config = {
// Match all pathnames except for
// - … if they start with `/api`, `/trpc`, `/_next` or `/_vercel`
// - … the ones containing a dot (e.g. `favicon.ico`)
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
};
How can I match pathnames that contain dots like /users/jane.doe
?
If you have pathnames where dots are expected, you can match them with explicit entries:
// ...
export const config = {
matcher: [
// Match all pathnames except for
// - … if they start with `/api`, `/trpc`, `/_next` or `/_vercel`
// - … the ones containing a dot (e.g. `favicon.ico`)
'/((?!api|trpc|_next|_vercel|.*\\..*).*)',
// Match all pathnames within `{/:locale}/users`
'/([\\w-]+)?/users/(.+)'
]
};
This will match e.g. /users/jane.doe
, also optionally with a locale prefix.
src/i18n/navigation.ts
Additionally, we can use our routing configuration to set up the navigation APIs:
import {createNavigation} from 'next-intl/navigation';
import {routing} from './routing';
// Lightweight wrappers around Next.js' navigation
// APIs that consider the routing configuration
export const {Link, redirect, usePathname, useRouter, getPathname} =
createNavigation(routing);
src/i18n/request.ts
Now, we can read the matched locale in our request configuration:
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';
export default getRequestConfig(async ({requestLocale}) => {
// Typically corresponds to the `[locale]` segment
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
return {
locale
// ...
};
});
src/app/[locale]/layout.tsx
To complete our setup, we’ll move all of our existing layouts and pages into the [locale]
segment:
src
└── app
└── [locale]
├── layout.tsx
├── page.tsx
└── ...
The locale
that was matched is now available via the [locale]
param:
import {NextIntlClientProvider, hasLocale} from 'next-intl';
import {notFound} from 'next/navigation';
import {routing} from '@/i18n/routing';
export default async function LocaleLayout({
children,
params
}: LayoutProps<'/[locale]'>) {
// Ensure that the incoming `locale` is valid
const {locale} = await params;
if (!hasLocale(routing.locales, locale)) {
notFound();
}
// ...
}
That’s all it takes! From here, you can configure your routing to cater to your specific needs.
In case you ran into an issue, have a look at the App Router example to explore a working app.
Static rendering
When using locale-based routing, next-intl
will currently opt into dynamic rendering when APIs like useTranslations
are used in Server Components. This is a limitation that we aim to remove in the future, but as a stopgap solution, next-intl
provides a temporary API that can be used to enable static rendering.
Add generateStaticParams
Since we are using a dynamic route segment for the [locale]
param, we need to pass all possible values to Next.js via generateStaticParams
so that the routes can be rendered at build time.
Depending on your needs, you can add generateStaticParams
either to a layout or pages:
- Layout: Enables static rendering for all pages within this layout (e.g.
app/[locale]/layout.tsx
) - Individual pages: Enables static rendering for a specific page (e.g.
app/[locale]/page.tsx
)
Example:
import {routing} from '@/i18n/routing';
export function generateStaticParams() {
return routing.locales.map((locale) => ({locale}));
}
Add setRequestLocale
to all relevant layouts and pages
next-intl
provides an API that can be used to distribute the locale that is received via params
in layouts and pages for usage in all Server Components that are rendered as part of the request.
import {setRequestLocale} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {notFound} from 'next/navigation';
import {routing} from '@/i18n/routing';
export default async function LocaleLayout({
children,
params
}: LayoutProps<'/[locale]'>) {
const {locale} = await params;
if (!hasLocale(routing.locales, locale)) {
notFound();
}
// Enable static rendering
setRequestLocale(locale);
return (
// ...
);
}
import {use} from 'react';
import {setRequestLocale} from 'next-intl/server';
import {useTranslations} from 'next-intl';
export default function IndexPage({params}) {
const {locale} = use(params);
// Enable static rendering
setRequestLocale(locale);
// Once the request locale is set, you
// can call hooks from `next-intl`
const t = useTranslations('IndexPage');
return (
// ...
);
}
Keep in mind that:
- The locale that you pass to
setRequestLocale
should be validated (e.g. in your root layout). - You need to call this function in every page and every layout that you intend to enable static rendering for since Next.js can render layouts and pages independently.
setRequestLocale
needs to be called before you invoke any functions fromnext-intl
likeuseTranslations
orgetMessages
.
How does setRequestLocale work?
next-intl
uses cache()
to create a mutable store that holds the current locale. By calling setRequestLocale
, the current locale will be written to the store, making it available to all APIs that require the locale.
Note that the store is scoped to a request and therefore doesn’t affect other requests that might be handled in parallel while a given request resolves asynchronously.
Why is this API necessary?
Next.js currently doesn’t provide an API to read route params like locale
at arbitrary places in Server Components (see vercel/next.js
#58862). The locale
is fundamental to all APIs provided by next-intl
, therefore passing this as a prop throughout the tree doesn’t stand out as particularly ergonomic.
Due to this, next-intl
uses its middleware to attach an x-next-intl-locale
header to the incoming request, holding the negotiated locale as a value. This technique allows the locale to be read at arbitrary places via headers().get('x-next-intl-locale')
.
However, the usage of headers
opts the route into dynamic rendering.
By using setRequestLocale
, you can provide the locale that is received in layouts and pages via params
to next-intl
. All APIs from next-intl
can now read from this value instead of the header, enabling static rendering.
Use the locale
param in metadata
In addition to the rendering of your pages, also page metadata needs to qualify for static rendering.
To achieve this, you can forward the locale
that you receive from Next.js via params
to the awaitable functions from next-intl
.
import {getTranslations} from 'next-intl/server';
export async function generateMetadata({params}) {
const {locale} = await params;
const t = await getTranslations({locale, namespace: 'Metadata'});
return {
title: t('title')
};
}