diff --git a/docs/pages/docs/_meta.json b/docs/pages/docs/_meta.json
index ec291a489..88e869230 100644
--- a/docs/pages/docs/_meta.json
+++ b/docs/pages/docs/_meta.json
@@ -1,8 +1,8 @@
{
"getting-started": "Getting started",
"usage": "Usage guide",
- "environments": "Environments",
"routing": "Routing",
+ "environments": "Environments",
"workflows": "Workflows & integrations",
"design-principles": "Design principles"
}
\ No newline at end of file
diff --git a/docs/pages/docs/environments/actions-metadata-route-handlers.mdx b/docs/pages/docs/environments/actions-metadata-route-handlers.mdx
index c086a0608..44c6929f7 100644
--- a/docs/pages/docs/environments/actions-metadata-route-handlers.mdx
+++ b/docs/pages/docs/environments/actions-metadata-route-handlers.mdx
@@ -205,7 +205,7 @@ If you're using the [`pathnames`](/docs/routing#pathnames) setting, you can gene
```tsx
import {MetadataRoute} from 'next';
import {locales, defaultLocale} from '@/config';
-import {getPathname} from '@/navigation';
+import {getPathname} from '@/routing';
// Adapt this as necessary
const host = 'https://acme.com';
diff --git a/docs/pages/docs/getting-started/app-router/with-i18n-routing.mdx b/docs/pages/docs/getting-started/app-router/with-i18n-routing.mdx
index c2d88647f..7289211ba 100644
--- a/docs/pages/docs/getting-started/app-router/with-i18n-routing.mdx
+++ b/docs/pages/docs/getting-started/app-router/with-i18n-routing.mdx
@@ -23,40 +23,44 @@ npm install next-intl
Now, we're going to create the following file structure:
```
-├── messages (1)
-│ ├── en.json
+├── messages
+│ ├── en.json (1)
│ └── ...
├── next.config.mjs (2)
└── src
- ├── i18n.ts (3)
+ ├── routing.ts (3)
├── middleware.ts (4)
+ ├── i18n.ts (5)
└── app
└── [locale]
- ├── layout.tsx (5)
- └── page.tsx (6)
+ ├── layout.tsx (6)
+ └── page.tsx (7)
```
+In case you're migrating an existing app to `next-intl`, you'll typically move your existing pages into the `[locale]` folder as part of the setup.
+
**Let's set up the files:**
### `messages/en.json` [#messages]
-Messages can be provided locally or loaded from a remote data source (e.g. a translation management system). Use whatever suits your workflow best.
+Messages represent the translations that are available per language and can be provided either locally or loaded from a remote data source.
-The simplest option is to add JSON files in your project based on locales—e.g. `en.json`.
+The simplest option is to add JSON files in your local project folder:
```json filename="messages/en.json"
{
"HomePage": {
- "title": "Hello world!"
+ "title": "Hello world!",
+ "about": "Go to the about page"
}
}
```
### `next.config.mjs` [#next-config]
-Now, set up the plugin which creates an alias to provide your i18n configuration (specified in the next step) to Server Components.
+Now, set up the plugin which creates an alias to provide a request-specific i18n configuration to Server Components—more on this in the following steps.
@@ -89,20 +93,65 @@ module.exports = withNextIntl(nextConfig);
-### `i18n.ts` [#i18nts]
+### `src/routing.ts` [#i18n-routing]
+
+We'll integrate with Next.js' routing in two places:
+
+1. **Middleware**: Negotiates the locale and handles redirects & rewrites (e.g. `/` → `/en`)
+2. **Navigation APIs**: Lightweight wrappers around Next.js' navigation APIs like ``
+
+This enables you to work with pathnames like `/about`, while i18n aspects like language prefixes are handled behind the scenes.
+
+To share the configuration between these two places, we'll set up `routing.ts`:
+
+```ts filename="src/routing.ts"
+import {defineRouting} from 'next-intl/routing';
+import {createSharedPathnamesNavigation} from 'next-intl/navigation';
+
+export const routing = defineRouting({
+ // A list of all locales that are supported
+ locales: ['en', 'de'],
+
+ // Used when no locale matches
+ defaultLocale: 'en'
+});
+
+// Lightweight wrappers around Next.js' navigation APIs
+// that will consider the routing configuration
+export const {Link, redirect, usePathname, useRouter} =
+ createSharedPathnamesNavigation(routing);
+```
+
+Depending on your requirements, you may wish to customize your routing configuration after completing the setup.
+
+### `src/middleware.ts` [#middleware]
+
+Once we have our routing configuration in place, we can use it to set up the middleware.
+
+```tsx filename="src/middleware.ts"
+import createMiddleware from 'next-intl/middleware';
+import {routing} from './routing';
+
+export default createMiddleware(routing);
+
+export const config = {
+ // Match only internationalized pathnames
+ matcher: ['/', '/(de|en)/:path*']
+};
+```
+
+### `src/i18n.ts` [#i18n-request]
-`next-intl` creates a request-scoped configuration object that can be used to provide messages and other options based on the user's locale for usage in Server Components.
+`next-intl` creates a request-scoped configuration object, which you can use to provide messages and other options based on the user's locale to Server Components.
```tsx filename="src/i18n.ts"
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
-
-// Can be imported from a shared config
-const locales = ['en', 'de'];
+import {routing} from './routing';
export default getRequestConfig(async ({locale}) => {
// Validate that the incoming `locale` parameter is valid
- if (!locales.includes(locale as any)) notFound();
+ if (!routing.locales.includes(locale as any)) notFound();
return {
messages: (await import(`../messages/${locale}.json`)).default
@@ -110,10 +159,10 @@ export default getRequestConfig(async ({locale}) => {
});
```
-
+Can I move this file somewhere else?
-This file is supported out-of-the-box both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`.
+This file is supported out-of-the-box as `./i18n.ts` both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`.
If you prefer to move this file somewhere else, you can optionally provide a path to the plugin:
@@ -126,28 +175,7 @@ const withNextIntl = createNextIntlPlugin(
-### `middleware.ts` [#middleware]
-
-The middleware matches a locale for the request and handles redirects and rewrites accordingly.
-
-```tsx filename="src/middleware.ts"
-import createMiddleware from 'next-intl/middleware';
-
-export default createMiddleware({
- // A list of all locales that are supported
- locales: ['en', 'de'],
-
- // Used when no locale matches
- defaultLocale: 'en'
-});
-
-export const config = {
- // Match only internationalized pathnames
- matcher: ['/', '/(de|en)/:path*']
-};
-```
-
-### `app/[locale]/layout.tsx` [#layout]
+### `src/app/[locale]/layout.tsx` [#layout]
The `locale` that was matched by the middleware is available via the `locale` param and can be used to configure the document language. Additionally, we can use this place to pass configuration from `i18n.ts` to Client Components via `NextIntlClientProvider`.
@@ -178,25 +206,31 @@ export default async function LocaleLayout({
}
```
-Note that `NextIntlClientProvider` automatically inherits configuration from `i18n.ts` here.
+Note that `NextIntlClientProvider` automatically inherits configuration from `i18n.ts` here, but `messages` need to be passed explicitly.
+
+### `src/app/[locale]/page.tsx` [#page]
-### `app/[locale]/page.tsx` [#page]
+And that's it!
-Use translations in your page components or anywhere else!
+Now you can use translations in your components and use navigation APIs to link to other pages.
```tsx filename="app/[locale]/page.tsx"
import {useTranslations} from 'next-intl';
+import {Link} from '@/routing';
export default function HomePage() {
const t = useTranslations('HomePage');
- return
{t('title')}
;
+ return (
+
+
{t('title')}
+ {t('about')}
+
+ );
}
```
-That's all it takes!
-
In case you ran into an issue, have a look at [the App Router example](/examples#app-router) to explore a working app.
@@ -204,12 +238,16 @@ In case you ran into an issue, have a look at [the App Router example](/examples
**Next steps:**
-
[Usage guide](/docs/usage): Format messages, dates and times
- [Routing](/docs/routing): Integrate i18n routing with `` & friends
+ [Usage guide](/docs/usage): Learn how to format messages, dates and times
+
+
+ [Routing](/docs/routing): Set up localized pathnames, domain-based routing &
+ more
- [Workflows](/docs/workflows): Make use of the TypeScript integration & more
+ [Workflows](/docs/workflows): Integrate deeply with TypeScript and other
+ tools
diff --git a/docs/pages/docs/getting-started/app-router/without-i18n-routing.mdx b/docs/pages/docs/getting-started/app-router/without-i18n-routing.mdx
index 58d58de23..3d656576c 100644
--- a/docs/pages/docs/getting-started/app-router/without-i18n-routing.mdx
+++ b/docs/pages/docs/getting-started/app-router/without-i18n-routing.mdx
@@ -23,8 +23,8 @@ npm install next-intl
Now, we're going to create the following file structure:
```
-├── messages (1)
-│ ├── en.json
+├── messages
+│ ├── en.json (1)
│ └── ...
├── next.config.mjs (2)
└── src
@@ -40,9 +40,9 @@ Now, we're going to create the following file structure:
### `messages/en.json` [#messages]
-Messages can be provided locally or loaded from a remote data source (e.g. a translation management system). Use whatever suits your workflow best.
+Messages represent the translations that are available per language and can be provided either locally or loaded from a remote data source.
-The simplest option is to add JSON files in your project based on locales—e.g. `en.json`.
+The simplest option is to add JSON files in your local project folder:
```json filename="messages/en.json"
{
@@ -54,7 +54,7 @@ The simplest option is to add JSON files in your project based on locales—e.g.
### `next.config.mjs` [#next-config]
-Now, set up the plugin which creates an alias to provide your i18n configuration (specified in the next step) to Server Components.
+Now, set up the plugin which creates an alias to provide a request-specific i18n configuration to Server Components (specified in the next step).
@@ -87,9 +87,9 @@ module.exports = withNextIntl(nextConfig);
-### `i18n.ts` [#i18nts]
+### `i18n.ts` [#i18n-request]
-`next-intl` creates a request-scoped configuration object that can be used to provide messages and other options based on the user's locale for usage in Server Components.
+`next-intl` creates a request-scoped configuration object, which you can use to provide messages and other options based on the user's locale to Server Components.
```tsx filename="src/i18n.ts"
import {getRequestConfig} from 'next-intl/server';
@@ -106,17 +106,17 @@ export default getRequestConfig(async () => {
});
```
-
+Can I move this file somewhere else?
-This file is supported out-of-the-box both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`.
+This file is supported out-of-the-box as `./i18n.ts` both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`.
If you prefer to move this file somewhere else, you can optionally provide a path to the plugin:
```js filename="next.config.mjs"
const withNextIntl = createNextIntlPlugin(
// Specify a custom path here
- './somewhere/else/i18n.ts'
+ './somewhere/else/request.ts'
);
```
@@ -153,7 +153,7 @@ export default async function RootLayout({
}
```
-Note that `NextIntlClientProvider` automatically inherits configuration from `i18n.ts` here.
+Note that `NextIntlClientProvider` automatically inherits configuration from `i18n.ts` here, but `messages` need to be passed explicitly.
### `app/page.tsx` [#page]
@@ -182,9 +182,12 @@ In case you ran into an issue, have a look at a working example:
**Next steps:**
-
[Usage guide](/docs/usage): Format messages, dates and times
- [Workflows](/docs/workflows): Make use of the TypeScript integration & more
+ [Usage guide](/docs/usage): Learn how to format messages, dates and times
+
+
+ [Workflows](/docs/workflows): Integrate deeply with TypeScript and other
+ tools
diff --git a/docs/pages/docs/routing.mdx b/docs/pages/docs/routing.mdx
index ca73c0dea..bcac0c2fc 100644
--- a/docs/pages/docs/routing.mdx
+++ b/docs/pages/docs/routing.mdx
@@ -13,73 +13,53 @@ import Callout from 'components/Callout';
`next-intl` integrates with the routing system of Next.js in two places:
-1. [**Middleware**](/docs/routing/middleware): Negotiates the locale and handles redirects & rewrites
-2. [**Navigation APIs**](/docs/routing/navigation): Provides APIs to navigate between pages
+1. [**Middleware**](/docs/routing/middleware): Negotiates the locale and handles redirects & rewrites (e.g. `/` → `/en`)
+2. [**Navigation APIs**](/docs/routing/navigation): Lightweight wrappers around Next.js' navigation APIs like ``
This enables you to express your app in terms of APIs like ``, while aspects like the locale and user-facing pathnames are automatically handled behind the scenes (e.g. `/de/ueber-uns`).
-## Shared configuration
+## Define routing
-While the middleware provides a few more options than the navigation APIs, the majority of the configuration is shared between the two and should be used in coordination. Typically, this can be achieved by moving the shared configuration into a separate file like `src/config.ts`:
+The routing configuration that is shared between the [middleware](/docs/routing/middleware) and [the navigation APIs](/docs/routing/navigation) can be defined with the `defineRouting` function.
-```
-src
-├── config.ts
-├── middleware.ts
-└── navigation.ts
-```
-
-This shared module can be set up like this:
-
-```tsx filename="config.ts"
-// A list of all locales that are supported
-export const locales = ['en', 'de'] as const;
-
-// ...
-```
+```tsx filename="src/routing.ts"
+import {defineRouting} from 'next-intl/routing';
-… and imported into both `middleware.ts` and `navigation.ts`:
-
-```tsx filename="middleware.ts"
-import createMiddleware from 'next-intl/middleware';
-import {locales, /* ... */} from './config';
-
-export default createMiddleware({
- locales,
- // ...
+export const routing = defineRouting({
+ // A list of all locales that are supported
+ locales: ['en', 'de'],
// Used when no locale matches
defaultLocale: 'en'
});
-
-export const config = {
- // Match only internationalized pathnames
- matcher: ['/', '/(de|en)/:path*']
-};
```
-```tsx filename="src/navigation.ts"
-import {createSharedPathnamesNavigation} from 'next-intl/navigation';
-import {locales, /* ... */} from './config';
+Depending on your routing needs, you may wish to consider further settings.
-export const {Link, redirect, usePathname, useRouter} =
- createSharedPathnamesNavigation({locales, /* ... */});
-```
+
+What if the locales aren't known at build time?
+
+In case you're building an app where locales can be added and removed at runtime, you can provide the routing configuration for the middleware [dynamically per request](/docs/routing/middleware#composing-other-middlewares).
+
+To create the corresponding navigation APIs, you can [omit the `locales` argument](/docs/routing/navigation#locales-unknown) from `createSharedPathnamesNavigation` in this case.
+
+
### Locale prefix
-By default, the pathnames of your app will be available under a prefix that matches your directory structure (e.g. `app/[locale]/about/page.tsx` → `/en/about`). You can however adapt the routing to optionally remove the prefix or customize it per locale by configuring the `localePrefix` setting.
+By default, the pathnames of your app will be available under a prefix that matches your directory structure (e.g. `/en/about` → `app/[locale]/about/page.tsx`). You can however adapt the routing to optionally remove the prefix or customize it per locale by configuring the `localePrefix` setting.
#### Always use a locale prefix (default) [#locale-prefix-always]
By default, pathnames always start with the locale (e.g. `/en/about`).
-```tsx filename="config.ts"
-import {LocalePrefix} from 'next-intl/routing';
-
-export const localePrefix = 'always' satisfies LocalePrefix;
+```tsx filename="routing.ts" {5}
+import {defineRouting} from 'next-intl/routing';
-// ...
+export const routing = defineRouting({
+ // ...
+ localePrefix: 'always'
+});
```
@@ -93,12 +73,13 @@ If you want to redirect unprefixed pathnames like `/about` to a prefixed alterna
If you only want to include a locale prefix for non-default locales, you can configure your routing accordingly:
-```tsx filename="config.ts"
-import {LocalePrefix} from 'next-intl/routing';
-
-export const localePrefix = 'as-needed' satisfies LocalePrefix;
+```tsx filename="routing.ts" {5}
+import {defineRouting} from 'next-intl/routing';
-// ...
+export const routing = defineRouting({
+ // ...
+ localePrefix: 'as-needed'
+});
```
In this case, requests where the locale prefix matches the default locale will be redirected (e.g. `/en/about` to `/about`). This will affect both prefix-based as well as domain-based routing.
@@ -117,12 +98,13 @@ However, you can also configure the middleware to never show a locale prefix in
1. You're using [domain-based routing](#domains) and you support only a single locale per domain
2. You're using a cookie to determine the locale but would like to enable static rendering
-```tsx filename="config.ts"
-import {LocalePrefix} from 'next-intl/routing';
+```tsx filename="routing.ts" {5}
+import {defineRouting} from 'next-intl/routing';
-export const localePrefix = 'never' satisfies LocalePrefix;
-
-// ...
+export const routing = defineRouting({
+ // ...
+ localePrefix: 'never'
+});
```
In this case, requests for all locales will be rewritten to have the locale only prefixed internally. You still need to place all your pages inside a `[locale]` folder for the routes to be able to receive the `locale` param.
@@ -137,19 +119,21 @@ In this case, requests for all locales will be rewritten to have the locale only
If you'd like to customize the user-facing prefix, you can provide a locale-based mapping:
-```tsx filename="config.ts"
-import {LocalePrefix} from 'next-intl/routing';
-
-export const locales = ['en-US', 'de-AT', 'zh'] as const;
-
-export const localePrefix = {
- mode: 'always',
- prefixes: {
- 'en-US': '/us',
- 'de-AT': '/eu/at'
- // (/zh will be used as-is)
+```tsx filename="routing.ts"
+import {defineRouting} from 'next-intl/routing';
+
+export const routing = defineRouting({
+ locales: ['en-US', 'de-AT', 'zh'],
+ defaultLocale: 'en-US',
+ localePrefix: {
+ mode: 'always',
+ prefixes: {
+ 'en-US': '/us',
+ 'de-AT': '/eu/at'
+ // (/zh will be used as-is)
+ }
}
-} satisfies LocalePrefix;
+});
```
**Note that:**
@@ -189,46 +173,51 @@ Many apps choose to localize pathnames, especially when search engine optimizati
- `/en/about`
- `/de/ueber-uns`
-Since you want to define these routes only once internally, you can use the `next-intl` middleware to [rewrite](https://nextjs.org/docs/api-reference/next.config.js/rewrites) such incoming requests to shared pathnames.
-
-```tsx filename="config.ts"
-export const locales = ['en', 'de'] as const;
-
-// The `pathnames` object holds pairs of internal and
-// external paths. Based on the locale, the external
-// paths are rewritten to the shared, internal ones.
-export const pathnames = {
- // If all locales use the same pathname, a single
- // external path can be used for all locales
- '/': '/',
- '/blog': '/blog',
-
- // If locales use different paths, you can
- // specify each external path per locale
- '/about': {
- en: '/about',
- de: '/ueber-uns'
- },
-
- // Dynamic params are supported via square brackets
- '/news/[articleSlug]-[articleId]': {
- en: '/news/[articleSlug]-[articleId]',
- de: '/neuigkeiten/[articleSlug]-[articleId]'
- },
-
- // Static pathnames that overlap with dynamic segments
- // will be prioritized over the dynamic segment
- '/news/just-in': {
- en: '/news/just-in',
- de: '/neuigkeiten/aktuell'
- },
-
- // Also (optional) catch-all segments are supported
- '/categories/[...slug]': {
- en: '/categories/[...slug]',
- de: '/kategorien/[...slug]'
+Since you typically want to define these routes only once internally, you can use the `next-intl` middleware to [rewrite](https://nextjs.org/docs/api-reference/next.config.js/rewrites) such incoming requests to shared pathnames.
+
+```tsx filename="routing.ts"
+import {defineRouting} from 'next-intl/routing';
+
+export const routing = defineRouting({
+ locales: ['en', 'de'],
+ defaultLocale: 'en',
+
+ // The `pathnames` object holds pairs of internal and
+ // external paths. Based on the locale, the external
+ // paths are rewritten to the shared, internal ones.
+ pathnames: {
+ // If all locales use the same pathname, a single
+ // external path can be used for all locales
+ '/': '/',
+ '/blog': '/blog',
+
+ // If locales use different paths, you can
+ // specify each external path per locale
+ '/about': {
+ en: '/about',
+ de: '/ueber-uns'
+ },
+
+ // Dynamic params are supported via square brackets
+ '/news/[articleSlug]-[articleId]': {
+ en: '/news/[articleSlug]-[articleId]',
+ de: '/neuigkeiten/[articleSlug]-[articleId]'
+ },
+
+ // Static pathnames that overlap with dynamic segments
+ // will be prioritized over the dynamic segment
+ '/news/just-in': {
+ en: '/news/just-in',
+ de: '/neuigkeiten/aktuell'
+ },
+
+ // Also (optional) catch-all segments are supported
+ '/categories/[...slug]': {
+ en: '/categories/[...slug]',
+ de: '/kategorien/[...slug]'
+ }
}
-} satisfies Pathnames;
+});
```
Localized pathnames map to a single internal pathname that is created via the file-system based routing in Next.js. In the example above, `/de/ueber-uns` will be handled by the page at `/[locale]/about/page.tsx`.
@@ -254,14 +243,14 @@ app
└── [slug]
```
-… with this middleware configuration:
+… with this routing configuration:
-```tsx filename="middleware.ts"
-import createMiddleware from 'next-intl/middleware';
+```tsx filename="routing.ts"
+import {defineRouting} from 'next-intl/routing';
-export default createMiddleware({
- defaultLocale: 'en',
+export const routing = defineRouting({
locales: ['en', 'de'],
+ defaultLocale: 'en',
pathnames: {
'/news/[slug]': {
en: '/news/[slug]',
@@ -350,25 +339,27 @@ If you want to serve your localized content based on different domains, you can
- `ca.example.com/en`
- `ca.example.com/fr`
-```tsx filename="config.ts"
-import {DomainsConfig} from 'next-intl/routing';
-
-export const locales = ['en', 'fr'] as const;
-
-export const domains: DomainsConfig = [
- {
- domain: 'us.example.com',
- defaultLocale: 'en',
- // Optionally restrict the locales available on this domain
- locales: ['en']
- },
- {
- domain: 'ca.example.com',
- defaultLocale: 'en'
- // If there are no `locales` specified on a domain,
- // all available locales will be supported here
- }
-];
+```tsx filename="routing.ts"
+import {defineRouting} from 'next-intl/routing';
+
+export const routing = defineRouting({
+ locales: ['en', 'fr'],
+ defaultLocale: 'en',
+ domains: [
+ {
+ domain: 'us.example.com',
+ defaultLocale: 'en',
+ // Optionally restrict the locales available on this domain
+ locales: ['en']
+ },
+ {
+ domain: 'ca.example.com',
+ defaultLocale: 'en'
+ // If there are no `locales` specified on a domain,
+ // all available locales will be supported here
+ }
+ ]
+});
```
**Note that:**
diff --git a/docs/pages/docs/routing/middleware.mdx b/docs/pages/docs/routing/middleware.mdx
index 2fbcbe579..3ba0becb2 100644
--- a/docs/pages/docs/routing/middleware.mdx
+++ b/docs/pages/docs/routing/middleware.mdx
@@ -8,19 +8,17 @@ import Details from 'components/Details';
routing](/docs/getting-started/app-router).
-The middleware handles redirects and rewrites based on the detected user locale.
+The middleware receives a [`routing`](/docs/routing#define-routing) configuration and takes care of:
+
+1. Locale negotiation
+2. Applying relevant redirects & rewrites
+3. Providing [alternate links](#alternate-links) for search engines
```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';
-import {locales} from './config';
-
-export default createMiddleware({
- // A list of all locales that are supported
- locales,
+import {routing} from './routing';
- // Used when no locale matches
- defaultLocale: 'en'
-});
+export default createMiddleware(routing);
export const config = {
// Match only internationalized pathnames
@@ -28,8 +26,6 @@ export const config = {
};
```
-In addition to handling i18n routing, the middleware sets the `link` header to inform search engines that your content is available in different languages (see [alternate links](#alternate-links)).
-
## Locale detection
The locale is negotiated based on your [`localePrefix`](/docs/routing#locale-prefix) and [`domains`](/docs/routing#domains) setting. Once a locale is detected, it will be remembered for future requests by being stored in the `NEXT_LOCALE` cookie.
@@ -67,7 +63,7 @@ To illustrate this with an example, let's consider your app supports these local
The "lookup" algorithm works by progressively removing subtags from the user's `accept-language` header until a match is found. This means that if the user's browser sends the `accept-language` header `en-GB`, the "lookup" algorithm will not find a match, resulting in the default locale being used.
-In contrast, the "best fit" algorithm compares the _distance_ between the user's `accept-language` header and the available locales, while taking into consideration regional information. Due to this, the "best fit" algorithm is able to match `en-US` as the best-matching locale in this case.
+In contrast, the "best fit" algorithm compares a _distance_ between the user's `accept-language` header and the available locales, while taking into consideration regional information. Due to this, the "best fit" algorithm is able to match `en-US` as the best-matching locale in this case.
@@ -118,32 +114,17 @@ With this, your domain config for this particular domain will be used.
## Configuration
-The middleware accepts a number of configuration options that are [shared](/docs/routing#shared-configuration) with the [navigation APIs](/docs/routing/navigation). This list contains all options that are specific to the middleware.
-
-### Default locale [#default-locale]
+Apart from the [`routing`](/docs/routing#shared-configuration) configuration that is shared with the [navigation APIs](/docs/routing/navigation), the middleware accepts a few additional options that can be used for customization.
-The `defaultLocale` is used as a fallback when none of the available locales match the user's request.
-
-```tsx filename="middleware.ts" {6}
-import createMiddleware from 'next-intl/middleware';
-
-export default createMiddleware({
- // ... other config
-
- defaultLocale: 'de'
-});
-```
-
-### Turning off locale detection [#locale-detection-false]
+### Turning off locale detection [#locale-detection]
If you want to rely entirely on the URL to resolve the locale, you can set the `localeDetection` property to `false`. This will disable locale detection based on the `accept-language` header and a potentially existing cookie value from a previous visit.
-```tsx filename="middleware.ts" {6}
+```tsx filename="middleware.ts" {5}
import createMiddleware from 'next-intl/middleware';
+import {routing} from './routing';
-export default createMiddleware({
- // ... other config
-
+export default createMiddleware(routing, {
localeDetection: false
});
```
@@ -163,12 +144,11 @@ However, there are cases where you may want to provide these links yourself:
In this case, you can opt-out of this behavior by setting `alternateLinks` to `false`.
-```tsx filename="middleware.ts" {6}
+```tsx filename="middleware.ts" {5}
import createMiddleware from 'next-intl/middleware';
+import {routing} from './routing';
-export default createMiddleware({
- // ... other config
-
+export default createMiddleware(routing, {
alternateLinks: false // Defaults to `true`
});
```
@@ -206,8 +186,9 @@ If you need to customize the alternate links, you can either turn them off and p
import createMiddleware from 'next-intl/middleware';
import LinkHeader from 'http-link-header';
import {NextRequest} from 'next/server';
+import {routing} from './routing';
-const handleI18nRouting = createMiddleware(/* ... */);
+const handleI18nRouting = createMiddleware(routing);
export default async function middleware(request: NextRequest) {
const response = handleI18nRouting(request);
@@ -249,10 +230,9 @@ A [Next.js `matcher`](https://nextjs.org/docs/app/building-your-application/rout
```tsx filename="middleware.ts"
import {NextRequest} from 'next/server';
import createMiddleware from 'next-intl/middleware';
+import {routing} from './routing';
-const intlMiddleware = createMiddleware({
- // ...
-});
+const handleI18nRouting = createMiddleware(routing);
export default function middleware(request: NextRequest) {
const {pathname} = request.nextUrl;
@@ -264,7 +244,7 @@ export default function middleware(request: NextRequest) {
);
if (!shouldHandle) return;
- return intlMiddleware(request);
+ return handleI18nRouting(request);
}
```
@@ -275,7 +255,7 @@ export default function middleware(request: NextRequest) {
There are two use cases where you might want to match pathnames without a locale prefix:
1. You're using a config for [`localePrefix`](/docs/routing#locale-prefix) other than [`always`](/docs/routing#locale-prefix-always)
-2. You want to implement redirects that add a locale for unprefixed pathnames (e.g. `/about` → `/en/about`)
+2. You want to enable redirects that add a locale for unprefixed pathnames (e.g. `/about` → `/en/about`)
For these cases, the middleware should run on requests for pathnames without a locale prefix as well.
@@ -330,15 +310,14 @@ Note that if you're using [localized pathnames](/docs/routing#pathnames), your i
By calling `createMiddleware`, you'll receive a function of the following type:
```tsx
-middleware(request: NextRequest): NextResponse
+function middleware(request: NextRequest): NextResponse;
```
-If you need to incorporate additional behavior, you can either modify the request before the `next-intl` middleware receives it, or modify the response that is returned.
+If you need to incorporate additional behavior, you can either modify the request before the `next-intl` middleware receives it, modify the response or even create the middleware based on dynamic configuration.
```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';
import {NextRequest} from 'next/server';
-import {locales} from './config';
export default async function middleware(request: NextRequest) {
// Step 1: Use the incoming request (example)
@@ -346,7 +325,7 @@ export default async function middleware(request: NextRequest) {
// Step 2: Create and call the next-intl middleware (example)
const handleI18nRouting = createMiddleware({
- locales,
+ locales: ['en', 'de'],
defaultLocale
});
const response = handleI18nRouting(request);
@@ -372,7 +351,6 @@ This example rewrites requests for `/[locale]/profile` to `/[locale]/profile/new
```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';
import {NextRequest} from 'next/server';
-import {locales} from './config';
export default async function middleware(request: NextRequest) {
const [, locale, ...segments] = request.nextUrl.pathname.split('/');
@@ -387,7 +365,7 @@ export default async function middleware(request: NextRequest) {
}
const handleI18nRouting = createMiddleware({
- locales,
+ locales: ['en', 'de'],
defaultLocale: 'en'
});
const response = handleI18nRouting(request);
@@ -408,19 +386,16 @@ Note that if you use a [`localePrefix`](/docs/routing#locale-prefix) other than
```tsx filename="middleware.ts"
import {clerkMiddleware, createRouteMatcher} from '@clerk/nextjs/server';
import createMiddleware from 'next-intl/middleware';
-import {locales} from './config';
+import {routing} from './routing';
-const intlMiddleware = createMiddleware({
- locales,
- defaultLocale: 'en'
-});
+const handleI18nRouting = createMiddleware(routing);
const isProtectedRoute = createRouteMatcher(['/:locale/dashboard(.*)']);
export default clerkMiddleware((auth, req) => {
if (isProtectedRoute(req)) auth().protect();
- return intlMiddleware(req);
+ return handleI18nRouting(req);
});
export const config = {
@@ -478,16 +453,13 @@ Now, we can integrate the Supabase middleware with the one from `next-intl`:
```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';
import {type NextRequest} from 'next/server';
-import {locales, defaultLocale} from '@/app/_config/config';
-import {updateSession} from '@/utils/supabase/middleware';
+import {routing} from './routing';
+import {updateSession} from './utils/supabase/middleware';
-const i18nMiddleware = createMiddleware({
- locales,
- defaultLocale
-});
+const handleI18nRouting = createMiddleware(routing);
export async function middleware(request: NextRequest) {
- const response = i18nMiddleware(request);
+ const response = handleI18nRouting(request);
// A `response` can now be passed here
return await updateSession(request, response);
@@ -510,21 +482,18 @@ For pathnames specified in [the `pages` object](https://next-auth.js.org/configu
import {withAuth} from 'next-auth/middleware';
import createMiddleware from 'next-intl/middleware';
import {NextRequest} from 'next/server';
-import {locales} from './config';
+import {routing} from './routing';
const publicPages = ['/', '/login'];
-const intlMiddleware = createMiddleware({
- locales,
- defaultLocale: 'en'
-});
+const handleI18nRouting = createMiddleware(routing);
const authMiddleware = withAuth(
// Note that this callback is only invoked if
// the `authorized` callback has returned `true`
// and not for pages listed in `pages`.
function onSuccess(req) {
- return intlMiddleware(req);
+ return handleI18nRouting(req);
},
{
callbacks: {
@@ -546,7 +515,7 @@ export default function middleware(req: NextRequest) {
const isPublicPage = publicPathnameRegex.test(req.nextUrl.pathname);
if (isPublicPage) {
- return intlMiddleware(req);
+ return handleI18nRouting(req);
} else {
return (authMiddleware as any)(req);
}
@@ -571,10 +540,10 @@ If you're using the [static export](https://nextjs.org/docs/app/building-your-ap
**Static export limitations:**
-1. There's no default locale that can be used without a prefix (same as [`localePrefix: 'always'`](/docs/routing#locale-prefix-always))
-2. The locale can't be negotiated at runtime (same as [`localeDetection: false`](#locale-detection-false))
+1. Using a locale prefix is required (same as [`localePrefix: 'always'`](/docs/routing#locale-prefix-always))
+2. The locale can't be negotiated at runtime (same as [`localeDetection: false`](#locale-detection))
3. You can't use [pathname localization](/docs/routing#pathnames)
-4. This requires [static rendering](/docs/getting-started/app-router/with-i18n-routing#static-rendering)
+4. [Static rendering](/docs/getting-started/app-router/with-i18n-routing#static-rendering) is required
5. You need to add a redirect for the root of the app
```tsx filename="app/page.tsx"
diff --git a/docs/pages/docs/routing/navigation.mdx b/docs/pages/docs/routing/navigation.mdx
index 5966e00e1..699bae7ff 100644
--- a/docs/pages/docs/routing/navigation.mdx
+++ b/docs/pages/docs/routing/navigation.mdx
@@ -9,44 +9,65 @@ import Details from 'components/Details';
routing](/docs/getting-started/app-router).
-`next-intl` provides drop-in replacements for common Next.js navigation APIs that automatically handle the user locale and pathnames behind the scenes.
+`next-intl` provides lightweight wrappers around Next.js' navigation APIs like [``](https://nextjs.org/docs/app/api-reference/components/link) and [`useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router) that automatically handle the user locale and pathnames behind the scenes.
-Depending on if you're using localized pathnames (i.e. the [`pathnames`](/docs/routing#pathnames) setting), you can pick from one of these functions to create the corresponding navigation APIs:
+Depending on if you're using the [`pathnames`](/docs/routing#pathnames) setting, you can pick from one of these functions to create the corresponding navigation APIs:
- `createSharedPathnamesNavigation`: Pathnames are shared across all locales (default)
- `createLocalizedPathnamesNavigation`: Pathnames are provided per locale (use with `pathnames`)
-These functions are typically called in a central module like `src/navigation.ts` in order to provide easy access to navigation APIs in your components and should receive configuration options that are [shared](/docs/routing#shared-configuration) with the middleware.
+These functions are typically called in a central module like [`src/routing.ts`](/docs/getting-started/app-router/with-i18n-routing#i18n-routing) in order to provide easy access to navigation APIs in your components and should receive a [`routing`](/docs/routing) configuration that is shared with the middleware.
-```tsx filename="navigation.ts"
+```tsx filename="routing.ts"
import {createSharedPathnamesNavigation} from 'next-intl/navigation';
-import {locales, /* ... */} from './config';
+import {defineRouting} from 'next-intl/routing';
+
+const routing = defineRouting(/* ... */);
export const {Link, redirect, usePathname, useRouter} =
- createSharedPathnamesNavigation({locales, /* ... */});
+ createSharedPathnamesNavigation(routing);
```
What if the locales aren't known at build time?
-In case you're building an app where locales can be added and removed at runtime, you can omit the `locales` argument from `createSharedPathnamesNavigation`. The `locale` that can be passed to APIs like `Link` will now accept any string as a valid value.
+In case you're building an app where locales can be added and removed at runtime, `createSharedPathnamesNavigation` can be called without the `locales` argument, therefore allowing any string that is encountered at runtime to be a valid locale.
+
+In this case, you'd not use the `defineRouting` function.
+
+```tsx filename="routing.ts"
+import {createSharedPathnamesNavigation} from 'next-intl/navigation';
+
+export const {Link, redirect, usePathname, useRouter} =
+ createSharedPathnamesNavigation({
+ // ... potentially other routing
+ // config, but no `locales` ...
+ });
+```
-Note however that the `locales` argument for the middleware is mandatory. You can however [create the middleware dynamically](/docs/routing/middleware#composing-other-middlewares) on a per-request basis and provide the `locales` argument e.g. after fetching this from a database.
+Note however that the `locales` argument for the middleware is mandatory. However, you can provide the routing configuration for the middleware [dynamically per request](/docs/routing/middleware#composing-other-middlewares).
-```tsx filename="navigation.ts"
+```tsx filename="routing.ts"
import {createLocalizedPathnamesNavigation} from 'next-intl/navigation';
-import {locales, pathnames, /* ... */} from './config';
+import {defineRouting} from 'next-intl/routing';
+
+const routing = defineRouting({
+ // ...
+ pathnames: {
+ // ...
+ }
+});
export const {Link, redirect, usePathname, useRouter, getPathname} =
- createLocalizedPathnamesNavigation({locales, pathnames, /* ... */});
+ createLocalizedPathnamesNavigation(routing);
```
@@ -74,7 +95,7 @@ This component wraps [`next/link`](https://nextjs.org/docs/app/api-reference/com
```tsx
-import {Link} from '../navigation';
+import {Link} from '@/routing';
// When the user is on `/en`, the link will point to `/en/about`
About
@@ -98,7 +119,7 @@ The [`useSelectedLayoutSegment` hook](https://nextjs.org/docs/app/api-reference/
import {useSelectedLayoutSegment} from 'next/navigation';
import {ComponentProps} from 'react';
-import {Link} from '../navigation';
+import {Link} from '@/routing';
export default function NavigationLink({
href,
@@ -119,7 +140,7 @@ export default function NavigationLink({
}
```
-```tsx filename="Navigation.tsx"
+```tsx