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.
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.jsondictionaries/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
- Add
de(German) to yournext.config.jslocales. - Create
dictionaries/de.jsonwith a localized version of your blog title. - Update your
app/[lang]/page.jsto dynamically load the German dictionary when the URL includes/de.
Common Pitfalls
- Mixing Locales: Ensure all your internal links use the
langvariable. Hardcoding/bloginstead of/${lang}/blogwill 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
getDictionaryfunction. If a key is missing in thefrfile, it should ideally fall back to theentranslation 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.
Work with me

Next.js Full-Stack Web App Development
A fast, SEO-ready full-stack web app built with Next.js 16 — from idea to deployed product, by an engineer who ships to production.

Custom Email & File Storage System on Cloudflare (Google Workspace Alternative)
Your own private email + file storage suite on your domain — unlimited mailboxes, no per-seat fees. A self-owned Google Workspace alternative for a flat ~$5/month.

