Docs
/
Next.js 13
/
Server Components (beta)

Usage in Server Components (beta)

Next.js 13 introduces support for React Server Components in the app directory. 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.

💡

The app directory is currently in beta, patterns are still emerging and APIs may change. Please use this at your own risk, knowing that you might have to face a migration effort when the app directory becomes stable.

Current beta version

npm install next-intl@2.11.0-beta.13

This beta version was tested with next@13.1.6.

Roadmap

  • ✅ All APIs from next-intl can be used in Server Components.
  • 💡 Currently SSR-only (i.e. no generateStaticParams). You can use CDN caching via headers in next.config.js to to get the same level of performance from SSR'd pages (learn more).

For details, see the pending pull request for Server Components support.

Getting started

If you haven't done so already, create a Next.js 13 app that uses the app directory. 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 creating 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:

  1. Provide messages for a language, e.g. in messages/en.json:
{
  "Index": {
    "title": "Hello world!"
  }
}
  1. next-intl creates a configuration once per request and makes it available to all Server Components. Define this in i18n.ts (short for "internationalization"):
import {getRequestConfig} from 'next-intl/server';

export default getRequestConfig(async ({locale}) => ({
  messages: (await import(`./messages/${locale}.json`)).default
}));
  1. Set up the plugin and provide the path to your configuration in 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 ...
  experimental: {appDir: true}
});
  1. Create a middleware in middleware.ts:
import createIntlMiddleware from 'next-intl/middleware';

// The middleware matches a locale for the user request
// and handles redirects and rewrites accordingly.
export default createIntlMiddleware({
  // 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',

  // Optionally configure a list of domains where the `defaultLocale` is changed
  domains: [
    {
      domain: 'example.de',
      defaultLocale: 'de'
    }
  ],

  // Sets the `Link` response header to notify search engines about
  // links to the content in other languages (defaults to `true`).
  alternateLinks: true
});

export const config = {
  // Skip all paths that aren't pages that you'd like to internationalize.
  // If you use the `public` folder, make sure your static assets are ignored
  // (e.g. by moving them to a shared folder that is referenced here).
  matcher: ['/((?!api|_next|favicon.ico|assets).*)']
};
  1. Provide the matched locale to the document in app/[locale]/layout.tsx.
import {useLocale} from 'next-intl';
import {notFound} from 'next/navigation';
import {ReactNode} from 'react';

type Props = {
  children: ReactNode;
  params: {locale: string};
};

export default function LocaleLayout({children, params}: Props) {
  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>
  );
}
  1. Use translations in your page component in app/[locale]/page.tsx or anywhere else!
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 purely on the server side.

If you've encountered an issue, you can explore the code for a working example (demo).

If you're in a transitioning phase, either from the pages directory to the app directory, or from Client Components to the Server Components beta, you can apply NextIntlClientProvider additionally.

Routing

Link

In the pages folder, Next.js automatically considers the current locale for next/link. Since this is no longer the case for the app directory, next-intl provides a drop-in replacement for this use case.

import {Link} from 'next-intl';

// When the user is on `/en`, the link will point to `/en/about`
<Link href="/about">About</Link>

// You can override the `locale` to switch to another language
<Link href="/" locale="de">Switch to German</Link>

useRouter

If you need to navigate programmatically (e.g. in response to a form submission), next-intl provides a convience API that wraps useRouter from Next.js and automatically applies the locale of the user.

'use client';

import {useRouter} from 'next-intl/client';

const router = useRouter();

// When the user is on `/en`, the router will navigate to `/en/about`
router.push('/about');

usePathname

To retrieve the pathname without a potential locale prefix, you can call usePathname.

'use client';

import {usePathname} from 'next-intl/client';

// When the user is on `/en`, this will be `/`
const pathname = usePathname();

redirect

If you want to interrupt the render of a Server Component and redirect to another page, you can invoke the redirect function from next-intl. This wraps the redirect function from Next.js and automatically applies the current locale.

import {redirect} from 'next-intl/server';

export default async function Profile() {
  const user = await fetchUser();

  if (!user) {
    // When the user is on `/en/profile`, this will be `/en/login`
    redirect('/login');
  }

  // ...
}

Global request configuration

If you'd like to apply global configuration like formats, defaultTranslationValues, timeZone, now, or error handling functions like onError and getMessageFallback, you can provide these 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 if necessary
  timeZone: headers().get('x-time-zone') ?? 'Europe/Berlin'
}));

Using internationalization outside of components

If you need to use translated messages in functions like generateMetadata, you can import awaitable versions of the functions that you usually call as hooks from next-intl/server.

// [locale]/layout.tsx

import {getTranslations} from 'next-intl/server';

export async function generateMetadata() {
  const t = await getTranslations('Metadata');

  return {
    title: t('title'),
    description: t('description')
  };
}

These functions are available from next-intl/server for usage outside of components:

import {
  getTranslations, // like `useTranslations`
  getIntl, // like `useIntl`
  getLocale, // like `useLocale`
  getNow, // like `useNow`
  getTimeZone // like `useTimeZone`
} from 'next-intl/server';

Switching to Client Components

If you need to use translations in Client Components, the best approach is to pass the generated labels as props.

import {useTranslations} from 'next-intl';
import Expandable from './Expandable';

export default function FAQ() {
  const t = useTranslations('FAQ');
  return <Expandable title={t('title')}>{t('description')}</Expandable>;
}
// app/[locale]/Expandable.tsx
'use client';

import {useState} from 'react';

function Expandable({title, children}) {
  const [expanded, setExpanded] = useState(false);

  function onToggle() {
    setExpanded(!expanded);
  }

  return (
    <div>
      <button onClick={onToggle}>{title}</button>
      {expanded && <p>{children}</p>}
    </div>
  );
}

This way your messages never leave the server and the client only needs to load the code that is necessary for initializing your interactive components.

If you absolutely need to use functionality from next-intl on the client side, you can wrap the respective components with NextIntlClientProvider (example code). Note however that this will increase your client bundle size.

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.