Back to Blog
Internationalization in Next.js: Beyond the Basic `i18n.js` File
3 min readJul 14, 20260 views

Internationalization in Next.js: Beyond the Basic `i18n.js` File

Setting up i18n in Next.js often starts with a simple JSON file, but real-world applications quickly outgrow that. `next-intl` offers a robust solution that integrates deeply with Next.js features, including Server Components, to manage translations more effectively and avoid common pitfalls.

Next.jsFrontendSoftware DesignWeb Development
Share

by Sunil Band

The Problem with Traditional i18n in Next.js

Building a global application means dealing with internationalization (i18n). For years, I've seen teams struggle with it, especially in React and Next.js projects. The common approach starts innocently enough: a locales folder, a JSON file for each language, and perhaps a basic context provider to pass translations down the component tree. This works for a small demo, but it falls apart under the weight of real applications.

First, there's bundle size. Shipping every single translation string for every language to the client, even if the user only needs one, is a performance killer. Then there's developer experience. How do you manage keys, ensure type safety, and handle dynamic content? Most solutions involve string interpolation that feels clunky and error-prone. And with the rise of Server Components in Next.js, how do you even load translations on the server efficiently without duplicating logic or introducing client-side waterfall issues?

I've seen projects try to build custom solutions, often ending up with complex, untyped translation functions and a massive technical debt load. It's a problem that should be solved once, robustly, and with a good developer experience. This is where next-intl comes in, offering a genuinely opinionated and well-integrated approach for Next.js applications.

Why next-intl is Different

Many i18n libraries focus on the raw translation mechanism. next-intl, however, is designed specifically for Next.js, which is its greatest strength. It deeply integrates with the app router, Server Components, and even Static Site Generation (SSG), allowing you to fetch translations where they make the most sense: on the server.

This isn't just about convenience; it's about performance and maintainability. By loading translations on the server, you reduce the client-side bundle size significantly. You can also leverage Next.js's data fetching capabilities, like async/await in Server Components, to fetch only the necessary translation messages for the current page and locale, often at build time or request time, without ever hitting the client bundle. It's a game-changer for large, content-heavy applications.

Setting Up next-intl in the App Router

Let's get into how this actually works. First, you'll install the package:

bash
npm install next-intl

The core idea is to create a root [locale] layout that wraps your entire application. This layout will be responsible for loading the messages for the current locale and making them available to all children, including Server Components. It leverages Next.js's file-based routing to achieve this.

Here’s a simplified setup:

typescript
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server'; // Server-side utility

interface RootLayoutProps {
  children: React.ReactNode;
  params: { locale: string };
}

export default async function RootLayout({ children, params: { locale } }: RootLayoutProps) {
  // Fetch messages from a server component. This is critical for performance.
  // 'next-intl' automatically caches these messages by locale.
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        {/* 
          NextIntlClientProvider makes messages available to client components. 
          Server Components can access them directly without this wrapper. 
        */}
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

Notice the getMessages() call. This function, provided by next-intl/server, allows you to load your translation files on the server. You'll typically have your messages organized in a messages directory like messages/en.json, messages/de.json, etc. next-intl automatically resolves the correct file based on the locale from your URL.

Creating Your Message Files

Your message files are standard JSON. However, next-intl supports some powerful features beyond simple key-value pairs, like rich text and plurals. Let's create a couple:

json
// messages/en.json
{
  
More from the blog
Available for projectsReady to make something fun 🎈

Ready to build the next system?Wanna build something awesome together?

Currently accepting high-impact opportunities in frontend engineering and scalable web applications.Got a cool idea rattling around? Let's grab a virtual coffee and turn it into something people love. ☕