Learning Next.js - Internationalization (i18n)
Episode 11 of 24

Learning Next.js - Internationalization (i18n)

This episode covers internationalization in the App Router using next-intl, localized routing with language prefixes and locale detection via middleware, content translation, and SEO and metadata for multi-language applications.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

An application that reaches many countries needs more than just translation: you have to handle routing based on language, date and number formatting, text direction, and SEO-friendly URLs for each language. This is the world of internationalization.

Episode 11 covers i18n support in the App Router using next-intl, localized routing and language detection via middleware, managing content translations, and SEO and metadata for multi-language applications.

i18n Support in Next.js

The App Router Approach

The App Router doesn't provide a built-in one-size-fits-all i18n solution; the recommended approach is segment-based routing: create a dynamic [locale] segment at the root of the application, then a library like next-intl or next-i18next manages messages and formatting. next-intl is the most popular choice for the App Router because it supports server components.

First, install the dependency and create the folder structure:

Install next-intl
npm install next-intl

The application structure becomes app/[locale]/layout.tsx and app/[locale]/page.tsx. Each language has a message file in the messages folder, for example messages/id.json and messages/en.json.

Localization with next-intl

Setting Up the Provider

Use getRequestConfig to load messages based on the locale:

next-intl request configuration
import { getRequestConfig } from "next-intl/server"
 
export default getRequestConfig(async ({ requestLocale }) => {
  const locale = await requestLocale
 
  return {
    locale,
    messages: (await import(`../messages/${locale}.json`)).default,
  }
})

The configuration above loads the message file matching the active locale. This function runs per request, so messages are always consistent with the language the user is currently viewing.

Using Messages in Components

In a component, call the useTranslations function to access messages:

Fetching translations in a component
import { useTranslations } from "next-intl"
 
export default function HomePage() {
  const t = useTranslations("Home")
 
  return <h1>{t("title")}</h1>
}

t("title") reads the Home.title key from the active language's message file. When the user switches languages, the text changes without a full page reload.

Localized Routing and Content Translation

Middleware for Language Detection

Middleware in middleware.ts picks a locale based on browser preference or URL, then redirects to the language segment:

Locale detection middleware
import createMiddleware from "next-intl/middleware"
 
export default createMiddleware({
  locales: ["id", "en"],
  defaultLocale: "id",
})
 
export const config = {
  matcher: ["/", "/(id|en)/:path*"],
}

The createMiddleware middleware above routes users arriving at / to /id or /en based on their language preference. The locales and defaultLocale configuration sets the available languages.

Translation Structure

Message files are organized per namespace:

Indonesian message file
{
  "Home": {
    "title": "Selamat datang",
    "description": "Aplikasi multi-bahasa"
  }
}

The Home namespace above separates messages per page or feature. Consistent keys across language files prevent missing translations — the library will throw a warning when a key isn't found.

SEO and Metadata for Multi-Language Apps

hreflang and Per-Language Metadata

For multi-language SEO, every page needs hreflang tags that tell Google how language versions relate. next-intl provides the getAlternateLinks and getLocalizedPathnames functions to generate alternate links automatically. Complete it with localized metadata:

Per-language metadata
export async function generateMetadata({ params }) {
  const { locale } = await params
  return {
    title: locale === "id" ? "Beranda" : "Home",
    alternates: {
      languages: { "id-ID": "/id", "en-US": "/en" },
    },
  }
}

The alternates.languages object generates hreflang tags linking all language versions. Each language also has its own URL — this lets Google index each version correctly, without duplicate content.

Closing

Here's what to take away:

  • The App Router handles i18n through a locale segment and external libraries.
  • next-intl is the primary choice for the App Router.
  • Middleware selects and routes language automatically.
  • Messages are organized per namespace in JSON files.
  • Separate languages produce unique URLs for each version.
  • hreflang tags and per-language metadata matter for global SEO.

In the next episode, episode 12, we'll discuss authentication and authorization — authentication patterns in Next.js, integrating NextAuth.js or Auth.js, protecting routes with middleware and session management, and role-based access control and secure redirects. The access security of your application starts to take shape.

Learning Next.js - Internationalization (i18n) | Learn Next.js