Back to Blog
Lesson 45 of the Next.js: Build Full-Stack Apps with the App Router course
Next.jsSeptember 1, 20264 min read

Internationalization Basics: Adding i18n Routing to Next.js

Learn how to implement i18n in your Next.js project. We cover configuring locale-based routing and localizing text to reach a global audience effectively.

Next.jsi18nlocalizationroutingweb-developmentfrontend

Previously in this course, we explored Integrating Third-Party Scripts in Next.js to extend our application's functionality. Now, we're expanding our reach by making our blog accessible to a global audience through internationalization (i18n).

Internationalization is the process of designing your application so it can be adapted to various languages and regions without engineering changes to the core codebase. In Next.js, this involves two primary pillars: locale routing and text localization.

Understanding i18n Routing

Next.js provides built-in support for internationalized routing. By configuring locales in your next.config.js, you can automatically handle URL structures like /en/blog or /fr/blog.

This approach is superior to query parameters because it is SEO-friendly—search engines can easily index distinct versions of your content based on the URL path. Before diving into the implementation, it is helpful to understand how Middleware Basics: Intercepting Requests in Next.js can be used to detect a user's preferred language from their accept-language header and redirect them accordingly.

Configuring i18n in Next.js

To get started, update your next.config.js file to include your supported locales. This tells Next.js how to treat your URL structure.

JAVASCRIPT
// next.config.js
const nextConfig = {
  i18n: {
    locales: [CE9178">'en', CE9178">'fr', CE9178">'es'],
    defaultLocale: CE9178">'en',
  },
}

module.exports = nextConfig

Once defined, any request to your domain will be aware of these segments. If a user visits the root /, Next.js will use the defaultLocale. If they visit /fr/about, the router will provide fr as the locale context.

Localizing Text Content

While routing handles the URL, you still need a way to swap out strings. In production-grade apps, you shouldn't hardcode strings directly in your components. Instead, use a structured JSON-based translation approach.

Create a directory structure to hold your translation files:

  • dictionaries/en.json
  • dictionaries/fr.json
JSON
// dictionaries/en.json
{
  "home": {
    "title": "Welcome to my blog",
    "cta": "Read more"
  }
}

To display these in your components, you'll need a helper function that retrieves the correct dictionary based on the locale provided by the Next.js params object.

JAVASCRIPT
// app/[lang]/page.js
import { getDictionary } from CE9178">'@/lib/dictionary'

export default async function Page({ params: { lang } }) {
  const dict = await getDictionary(lang)
  return <h1>{dict.home.title}</h1>
}

Worked Example: A Localized Navigation

To keep our blog consistent, let's update our navigation. We'll use the current locale to prefix our links so that navigation stays within the same language context.

JSX
// components/Navbar.js
import Link from CE9178">'next/link'

export default function Navbar({ lang }) {
  return (
    <nav>
      <Link href={CE9178">`/${lang}/blog`}>
        {lang === CE9178">'en' ? CE9178">'Blog' : CE9178">'Blogue'}
      </Link>
    </nav>
  )
}

While this works for simple cases, for more complex setups, you might want to look into Internationalization (i18n) Architecture: Performance at Scale to handle lazy-loading of translation dictionaries, which prevents your initial bundle from growing too large.

Practice Exercise

  1. Add de (German) to your next.config.js locales.
  2. Create dictionaries/de.json with a localized version of your blog title.
  3. Update your app/[lang]/page.js to dynamically load the German dictionary when the URL includes /de.

Common Pitfalls

  • Mixing Locales: Ensure all your internal links use the lang variable. Hardcoding /blog instead of /${lang}/blog will cause the user to jump back to the default locale.
  • Dictionary Bloat: Don't import all dictionaries at once. Only load the dictionary required for the current request context to keep your server-side performance optimal.
  • Missing Translations: Always provide a fallback mechanism in your getDictionary function. If a key is missing in the fr file, it should ideally fall back to the en translation rather than rendering an empty string or crashing the UI.

Frequently Asked Questions

Q: Does i18n routing work with static generation? Yes, Next.js will generate separate static pages for every locale you define.

Q: Can I change the locale without a full page refresh? Yes, using the next/link component to navigate between locale-prefixed routes will trigger a client-side navigation, which is fast and preserves state.

Q: Where should I store my translation files? For small to medium projects, a dictionaries/ folder in the root is standard. For massive projects with thousands of strings, consider a dedicated CMS or translation management service.

Recap

Internationalization is a requirement for modern global applications. We've covered configuring your next.config.js for locale routing, structuring JSON dictionaries for text, and using the locale param to serve content correctly. These steps ensure your blog remains accessible and professional regardless of where your reader is located.

Up next: We will dive into Accessibility Best Practices, ensuring that our localized content is readable by screen readers and navigable by everyone.

Similar Posts