Docs
Environments
Server & Client Components

Internationalization of Server & Client Components in Next.js

⚠️

This page contains background information about the advantages of moving internationalization to Server Components. Note that this is currently only available in the Server Components beta version of next-intl. If you're on a stable release, you currently have to use Client Components for internationalization.

With the introduction of the App Router in Next.js 13, React Server Components (opens in a new tab) became publicly available. This new paradigm allows components that don’t require React’s interactive features, such as useState and useEffect, to remain server-side only.

If internationalization is implemented entirely on the server side, we can achieve new levels of performance for our apps, leaving the client side for interactive features.

Benefits of server-side internationalization

  1. Your messages never leave the server and don't need to be serialized for the client side

  2. Library code for internationalization doesn't need to be loaded on the client side

  3. No need to split your messages based on routes or components
  4. No runtime cost on the client side
  5. No need to handle environment differences like different time zones on the server and client

Passing translations to Client Components

If you need to use translations or other functionality from next-intl in Client Components, the best approach is to pass the processed labels as props or children from a Server Component.

[locale]/faq/page.tsx
import {useTranslations} from 'next-intl';
import Expandable from './Expandable';
 
export default function FAQEntry() {
  const t = useTranslations('FAQEntry');
  return (
    <Expandable title={t('title')}>
      <FAQContent content={t('description')} />
    </Expandable>
  );
}
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 && <div>{children}</div>}
    </div>
  );
}

As you can see, we can use interactive features from React like useState on translated content, even though the translation only runs on the server side.

Using interactive state in translations

You might run into cases where you have dynamic state, such as pagination, that should be reflected in translated messages.

Pagination.tsx
function Pagination({curPage, totalPages}) {
  const t = useTranslations('Pagination');
  return <p>{t('info', {curPage, totalPages})}</p>;
}

You can still manage your translations on the server side by using page- or search params (opens in a new tab). There's an article on Smashing Magazine about using next-intl in Server Components (opens in a new tab) which explores this topic in more detail, specifically the section about adding interactivity (opens in a new tab).

Apart from page- or search params, you can also use cookies (opens in a new tab) or database state (opens in a new tab) for storing state that can be read on the server side.

If you absolutely need to use functionality from next-intl on the client side, you can wrap the respective components with NextIntlClientProvider.

Counter.tsx
import pick from 'lodash/pick';
import {useLocale, NextIntlClientProvider} from 'next-intl';
import ClientCounter from './ClientCounter';
 
async function Counter() {
  const locale = useLocale();
  const messages = (await import(`../../../../messages/${locale}.json`))
    .default;
 
  return (
    <NextIntlClientProvider
      locale={locale}
      messages={
        // Only provide the minimum of messages
        pick(messages, 'ClientCounter')
      }
    >
      <ClientCounter />
    </NextIntlClientProvider>
  );
}

(working example (opens in a new tab))

💡

NextIntlClientProvider doesn't automatically inherit configuration from i18n.ts, therefore make sure to provide all relevant props on the component. If you're configuring non-serializable values like functions, you have to mark the component that renders NextIntlClientProvider with 'use client'; (example (opens in a new tab)).