Skip to content
Docs
Getting started
App Router (Server Components)

Next.js 13: Internationalization (i18n) in Server Components

Next.js 13 introduces support for React Server Components (opens in a new tab) with the App Router and unlocks many benefits when handling internationalization entirely on the server side. next-intl is adopting the new capabilities and is currently offering a preview version to early adopters, who are already building apps with Server Components.

💡

This page covers a getting started guide for the React Server Components beta that was running over the last couple of months. It's recommended to try out the next-intl 3.0 release candidate if you'd like to use next-intl in Server Components.

Current version

npm install next-intl@3.0.0-beta.19

This version was tested with next@13.5.1.

Roadmap

FeatureStatus
Usage of all next-intl APIs in Server Components
Dynamic rendering
Static rendering (i.e. generateStaticParams)🏗️
🏗️

Support for static rendering is currently available via a stopgap solution (see static rendering).

Getting started

If you haven't done so already, create a Next.js 13 app that uses the App Router (opens in a new tab). All pages should be moved within a [locale] folder so that we can use this segment to provide content in different languages (e.g. /en, /en/about, etc.).

Start by running npm install next-intl and create the following file structure:

├── messages (1)
│   ├── en.json
│   └── ...
├── i18n.ts (2)
├── next.config.js (3)
├── middleware.ts (4)
└── app
    └── [locale]
        ├── layout.tsx (5)
        └── page.tsx (6)

Now, set up the files as follows:

messages/en.json

Messages can be provided locally or loaded from a remote data source (e.g. a translation management system). Use whatever suits your workflow best.

The simplest option is to create JSON files locally based on locales, e.g. en.json.

messages/en.json
{
  "Index": {
    "title": "Hello world!"
  }
}

i18n.ts

next-intl creates a configuration once per request and makes it available to all Server Components. Here you can provide messages and other options depending the locale of the user.

i18n.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async ({locale}) => ({
  messages: (await import(`./messages/${locale}.json`)).default
}));

next.config.js

Now, set up the plugin and provide the path to your i18n.ts file.

next.config.js
const withNextIntl = require('next-intl/plugin')(
  // This is the default (also the `src` folder is supported out of the box)
  './i18n.ts'
);
 
module.exports = withNextIntl({
  // Other Next.js configuration ...
});

middleware.ts

The middleware matches a locale for the request and handles redirects and rewrites accordingly.

middleware.ts
import createMiddleware from 'next-intl/middleware';
 
export default createMiddleware({
  // A list of all locales that are supported
  locales: ['en', 'de'],
 
  // If this locale is matched, pathnames work without a prefix (e.g. `/about`)
  defaultLocale: 'en'
});
 
export const config = {
  // Skip all paths that should not be internationalized. This example skips
  // certain folders and all pathnames with a dot (e.g. favicon.ico)
  matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};

Note: If you have pages that contain the character . in the pathname (e.g. /users/jane.doe), you might want to consider them in your matcher config.

app/[locale]/layout.tsx

The locale that was matched by the middleware is available via the locale param and can be used to configure the document language.

app/[locale]/layout.tsx
import {useLocale} from 'next-intl';
import {notFound} from 'next/navigation';
 
const locales = ['en', 'de'];
 
export default function LocaleLayout({children, params: {locale}}) {
  // Validate that the incoming `locale` parameter is valid
  const isValidLocale = locales.some((cur) => cur === locale);
  if (!isValidLocale) notFound();
 
  return (
    <html lang={locale}>
      <body>{children}</body>
    </html>
  );
}

app/[locale]/page.tsx

Use translations in your page components or anywhere else!

app/[locale]/page.tsx
import {useTranslations} from 'next-intl';
 
export default function Index() {
  const t = useTranslations('Index');
  return <h1>{t('title')}</h1>;
}

That's all it takes! Now you can internationalize your apps on the server side.

💡

Next steps:

Static rendering

By using APIs like useTranslations from next-intl in Server Components, your pages will currently opt into dynamic rendering. 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 to app/[locale]/layout.tsx

Since we use a dynamic route segment for the [locale] param, we need to provide all possible values via generateStaticParams (opens in a new tab) to Next.js, so the routes can be rendered at build time.

app/[locale]/layout.tsx
const locales = ['en', 'de'];
 
export function generateStaticParams() {
  return locales.map((locale) => ({locale}));
}

Add unstable_setRequestLocale to all layouts and pages

next-intl provides a temporary API that can be used to distribute the locale that is received via params in a layout or page for usage in all Server Components that are rendered as part of the request.

app/[locale]/layout.tsx
import {unstable_setRequestLocale} from 'next-intl/server';
 
const locales = ['en', 'de'];
 
export default async function LocaleLayout({
  children,
  params: {locale}
}) {
  // Validate that the incoming `locale` parameter is valid
  const isValidLocale = locales.some((cur) => cur === locale);
  if (!isValidLocale) notFound();
 
  unstable_setRequestLocale(locale);
 
  return (
    // ...
  );
}
app/[locale]/page.tsx
import {unstable_setRequestLocale} from 'next-intl/server';
import {locales} from '..';
 
export default async function IndexPage({
  params: {locale}
}) {
  unstable_setRequestLocale(locale);
 
  return (
    // ...
  );
}

What does "unstable" mean?

unstable_setRequestLocale is meant to be used as a stopgap solution and we aim to remove it in the future in case Next.js adds an API to access parts of the URL (opens in a new tab). If that's the case, you'll get a deprecation notice in a minor version and the API will be removed as part of a major version.

Note that Next.js can render layouts and pages indepently. This means that e.g. when you navigate from /settings/profile to /settings/privacy, the /settings segment might not re-render as part of the request. Due to this, it's important that unstable_setRequestLocale is called not only in the parent settings/layout.tsx, but also in the individual pages profile/page.tsx and privacy/page.tsx.

That being said, the API is expected to work reliably if you're cautious to apply it in all relevant places.

Providing feedback

If you have feedback about using next-intl in the app directory, feel free to leave feedback in the PR that implements the React Server Components support (opens in a new tab).