React Intl with Next.js Pages Router

Next.js Logo

The complete guide for Next.js localization using react-intl

(7 minute read)

Last updated: May 2026

Prefer a different library?

Which library should I choose? Compare them: next-i18next vs react-intl, next-i18next vs next-intl, react-i18next vs react-intl

1. Start with a Pages Router app

This guide is for an existing Next.js Pages Router app. Using app instead of pages? Follow the App Router tutorial.

Next.js supplies locale routing for the Pages Router. React Intl supplies translated messages, and i18nexus manages the strings. React Intl 12 requires React 18+. The snippets use JavaScript, no src directory, and Node.js 22.13+ or 24 LTS.

npm install react-intl@^12

2. Let i18nexus handle your translations

Write your copy once. i18nexus stores your source strings, AI translates them into your project languages, and gives your team a place to review the results. The CLI brings those translations into your app.

Already have an i18nexus project?

Use its project API key instead of creating another project with init. Install @i18nexus/cli as a dev dependency, add the key to .env, and run npx i18nexus pull. Follow the CLI guide to add development and build sync. The library, languages, namespaces, and output path must match the imports in this tutorial.

To create a new i18nexus project, run this from your app directory:

npx @i18nexus/cli@latest init

For this example, use these choices:

  • Sign in or create an account in the browser opened by the CLI.
  • Use react-intl as your library. Installed libraries are detected automatically.
  • For a new app, choose that you do not have translation JSON yet. Set English (en) as the base language and German (de) as an additional language.
  • Use one namespace: common.
  • Accept automatic sync in package.json. This installs the CLI locally and adds development and build sync scripts.
Already have translation JSON?

The CLI can detect and import it into your new i18nexus project. Keep your existing language codes and namespaces, and adjust the examples below to match.

If you keep a custom translation folder, the CLI includes it in your sync scripts. For manual pulls, include that same path: npx i18nexus pull --path ./your-folder.

When setup finishes, add the project API key printed by the CLI to .env in the app root. Add .env to .gitignore:

.env
I18NEXUS_API_KEY="your_project_api_key"

Add your first two strings

Open the dashboard link printed by the CLI and add these English strings in the common namespace. The key is what your code uses; the value is what your users read.

Click + String to enter a key and its English text, then save with the checkmark. This sample project uses the default namespace; select common for this tutorial. Your notes column may say Description or Team Notes, depending on your project settings. Select an image to enlarge it.
  • welcome: Welcome to my app!
  • greeting: Hello, {name}!

Save each string, then expand its row to see the translations. You can review and edit them in the same place as your source text.

Expand a saved string to review its AI translations. This sample project uses different keys and languages; follow the keys above and use English and German for this guide.

Prefer working with an AI coding assistant? Choose MCP setup during init, then ask your assistant to create these same strings through i18nexus. The MCP guide explains that workflow. The dashboard remains available for you and your team.

Give AI translation a moment to finish, then download the generated JSON before adding the imports in the next step:

npx i18nexus pull

You should now have messages/en/common.json and its German equivalent. These are generated files: make future string changes in i18nexus and let the CLI sync them.

3. Enable Next.js locale routing

next.config.js
module.exports = {
  i18n: {
    locales: ['en', 'de'],
    defaultLocale: 'en'
  }
};

Merge this into your existing Next.js config. The Pages Router serves English at / and German at /de. No proxy or extra routing library is needed.

pages/_app.js
import {IntlProvider} from 'react-intl';
import {useRouter} from 'next/router';

export default function App({Component, pageProps}) {
  const {locale, defaultLocale} = useRouter();
  return (
    <IntlProvider locale={locale || defaultLocale || 'en'} defaultLocale="en" messages={pageProps.messages}>
      <Component {...pageProps} />
    </IntlProvider>
  );
}

Keep your existing global CSS import and providers. This provider makes the active page's messages available to its React components.

4. Load messages when the page is built

pages/index.js
import {FormattedMessage} from 'react-intl';
import LanguageSwitcher from '../components/LanguageSwitcher';

export async function getStaticProps({locale}) {
  return {
    props: {
      messages: (await import(`../messages/${locale}/common.json`)).default
    }
  };
}

export default function Home() {
  return (
    <main>
      <h1><FormattedMessage id="welcome" /></h1>
      <p><FormattedMessage id="greeting" values={{name: 'Sam'}} /></p>
      <LanguageSwitcher />
    </main>
  );
}

Next.js runs getStaticProps for each configured locale. Add the message loader to each translated page. The browser receives the messages with the page, so it does not need to call the i18nexus API. If your app has src/pages, the CLI writes React Intl messages to src/messages, and this relative import still works.

5. Change languages

components/LanguageSwitcher.js
import {useRouter} from 'next/router';

export default function LanguageSwitcher() {
  const router = useRouter();

  function changeLanguage(locale) {
    document.cookie = `NEXT_LOCALE=${locale};path=/;max-age=2592000;SameSite=Lax`;
    router.push(
      {pathname: router.pathname, query: router.query},
      router.asPath,
      {locale}
    );
  }

  return (
    <select aria-label="Language" value={router.locale} onChange={e => changeLanguage(e.target.value)}>
      <option value="en">English</option>
      <option value="de">Deutsch</option>
    </select>
  );
}

Use Link from next/link for navigation between pages; Next.js preserves the current locale. The switcher also preserves dynamic route parameters and the current URL.

Run npm run dev and try the language switcher. The welcome message and greeting should change together:

English
Deutsch
The example running in English and German. Your app's styling and AI-generated wording may differ.

Add plurals when you need them

Create a string named items in your common namespace with this ICU value:

{count, plural, one {# item} other {# items}}

After syncing, render it with <FormattedMessage id="items" values={{count: 3}} />. i18nexus manages the translated ICU message for each language.

Keep translations in sync

The sync script added by the CLI runs i18nexus listen alongside your app. It pulls on startup and updates the generated JSON when strings or translations change in i18nexus. Refresh the page if your app still shows a cached translation.

For deployment, set I18NEXUS_API_KEY in your build environment and run npm run build. The CLI-added prebuild script pulls the latest JSON before compilation. Install dev dependencies in the build stage so the CLI is available. Updates to bundled translations require a new build.

The Pages Router's built-in internationalized routing requires a Next.js server deployment; it is not compatible with output: 'export'.

Keep adding source strings in the dashboard or through the i18nexus MCP. Your team can review and refine the AI translations in the same project. React Intl documentation has the details for larger apps.

Level up your localization

It only takes a few minutes to streamline your translations forever.

Get started