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) and unlocks many benefits when handling internationalization entirely on the server side. next-intl is adopting the new capabilities and is currently offering a beta version to early adopters, who are already building apps with the app directory.

⚠️

Support for React Server Components is currently in beta. Please use it at your own risk, knowing that you may have to migrate upon a stable release.

Current beta version

npm install next-intl@3.0.0-beta.5

This beta version was tested with next@13.4.0.

Roadmap

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

Full support for static rendering is currently pending, but stopgap solutions are available.

For details, see the pending pull request for Server Components support (opens in a new tab).

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 configuration.

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 the
  // folders "api", "_next" and all files with an extension (e.g. favicon.ico)
  matcher: ['/((?!api|_next|.*\\..*).*)']
};

app/[locale]/layout.tsx

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

app/[locale]/layout.tsx
import {useLocale} from 'next-intl';
import {notFound} from 'next/navigation';
 
export default function LocaleLayout({children, params}) {
  const locale = useLocale();
 
  // Show a 404 error if the user requests an unknown locale
  if (params.locale !== locale) {
    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:

Global request configuration

next-intl supports the following global configuration:

  • formats
  • defaultTranslationValues
  • timeZone
  • now
  • onError
  • getMessageFallback

For the usage in Server Components, these can be configured in i18n.ts.

i18n.ts
import {headers} from 'next/headers';
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async ({locale}) => ({
  messages: (await import(`../messages/${locale}.json`)).default,
 
  // You can read from headers or cookies here
  timeZone: headers().get('x-time-zone') ?? 'Europe/Berlin'
}));

Note that the configuration object will be created once for each request and will then be made available to all Server Components in your app.

Static rendering

The support for using next-intl in React Server Components is currently limited to dynamic rendering and support for static rendering is pending until createServerContext (opens in a new tab) is integrated with Next.js. If you have a strong need for static rendering, you can choose from a set of stopgap solutions depending on your needs.

Temporary workarounds for static rendering:

  1. Handle internationalization in Client Components for now, as static rendering is fully supported in this paradigm without limitations.
app/[locale]/page.tsx
'use client';
 
import {useTranslations} from 'next-intl';
 
export default function Index() {
  const t = useTranslations('Index');
  return <h1>{t('title')}</h1>;
}
  1. The APIs for using internationalization outside of components are integrated with static rendering. As a temporary solution, you can use these APIs in components too, but note that you have to "drill-down" the locale that is received via params to all components.
app/[locale]/page.tsx
import {getTranslator} from 'next-intl/server';
 
export default async function Index({params: {locale}}) {
  const t = await getTranslator(locale, 'Index');
  return <h1>{t('title')}</h1>;
}
  1. Use CDN caching (opens in a new tab) to get the same performance characteristics from your dynamic pages as static ones. Note however that this is not supported in Next.js itself (opens in a new tab) (except for if you apply a patch (opens in a new tab)), therefore this needs to be configured on your hosting solution (e.g. Netlify (opens in a new tab), Cloudflare (opens in a new tab)).
💡

Note that these are temporary workarounds that will no longer be necessary once createServerContext is integrated with Next.js. As soon as this is the case, there will be a stable release of next-intl with Server Components support.

Providing feedback

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