--- name: next-intl-app-router description: Configures and uses next-intl for Next.js App Router with locale-based routing. Use when adding or changing i18n, locale routing, translations, next-intl plugin, middleware/proxy, or message files in Next.js App Router projects. --- # next-intl (App Router) Setup and usage of `next-intl` with **prefix-based locale routing** (e.g. `/en/about`, `/ja/about`). Use this skill in any Next.js App Router project. **Example code:** Copy-paste examples live in this skill's [examples/](examples/) folder. See [examples/README.md](examples/README.md) for where each file goes in your project. ## File layout Keep this structure: ``` ├── messages/ │ ├── en.json │ ├── ja.json │ └── ... ├── next.config.ts └── src/ ├── i18n/ │ ├── request.ts │ ├── routing.ts │ └── navigation.ts ├── proxy.ts # Next.js 16+ (was middleware.ts) └── app/ ├── layout.tsx # Root layout, no NextIntlClientProvider here └── [locale]/ ├── layout.tsx ├── page.tsx └── ... ``` Root layout does **not** wrap with `NextIntlClientProvider`; only `app/[locale]/layout.tsx` does. --- ## 1. Next config Wire the plugin (default path `./i18n/request.ts`): ```ts // next.config.ts import type { NextConfig } from "next"; import createNextIntlPlugin from "next-intl/plugin"; const nextConfig: NextConfig = { /* ... */ }; const withNextIntl = createNextIntlPlugin(); export default withNextIntl(nextConfig); ``` Custom path: `createNextIntlPlugin('./src/i18n/request.ts')`. --- ## 2. Routing config Central config in `src/i18n/routing.ts`: ```ts import { defineRouting } from "next-intl/routing"; export const routing = defineRouting({ locales: ["en", "ja", "zh-CN", "zh-TW"], defaultLocale: "en", }); ``` --- ## 3. Request config `src/i18n/request.ts`: resolve locale from the `[locale]` segment and load messages. ```ts import { getRequestConfig } from "next-intl/server"; import { hasLocale } from "next-intl"; import { routing } from "./routing"; export default getRequestConfig(async ({ requestLocale }) => { const requested = await requestLocale; const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale; return { locale, messages: (await import(`../../messages/${locale}.json`)).default, }; }); ``` --- ## 4. Proxy / middleware (Next.js 16) Next.js 16 uses `proxy.ts` instead of `middleware.ts`. Same API: ```ts // src/proxy.ts import createMiddleware from "next-intl/middleware"; import { routing } from "./i18n/routing"; export const proxy = createMiddleware(routing); export const config = { matcher: "/((?!api|trpc|_next|_vercel|.*\\..*).*)", }; ``` Matcher: all pathnames except `/api`, `/trpc`, `/_next`, `/_vercel`, and paths containing a dot (e.g. `favicon.ico`). --- ## 5. Navigation helpers Use project navigation wrappers so links keep the current locale: ```ts // src/i18n/navigation.ts import { createNavigation } from "next-intl/navigation"; import { routing } from "./routing"; export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing); ``` In components: import `Link` (and others) from `@/i18n/navigation`, **not** from `next/navigation` or `next/link`, for locale-aware URLs. Example: [examples/Nav-client.tsx](examples/Nav-client.tsx), [examples/BackToHomeButton.tsx](examples/BackToHomeButton.tsx). --- ## 6. Locale layout and static rendering `app/[locale]/layout.tsx` must (full file: [examples/app-locale-layout.tsx](examples/app-locale-layout.tsx)): 1. Validate `locale` with `hasLocale` → `notFound()` if invalid. 2. Call `setRequestLocale(locale)` for static rendering. 3. Wrap children with `NextIntlClientProvider` and `getMessages()`. ```tsx // app/[locale]/layout.tsx import { NextIntlClientProvider, hasLocale } from "next-intl"; import { setRequestLocale } from "next-intl/server"; import { notFound } from "next/navigation"; import { routing } from "@/i18n/routing"; import { getMessages } from "next-intl/server"; type Props = { children: React.ReactNode; params: Promise<{ locale: string }>; }; export function generateStaticParams() { return routing.locales.map((locale) => ({ locale })); } export default async function LocaleLayout({ children, params }: Props) { const { locale } = await params; if (!hasLocale(routing.locales, locale)) notFound(); setRequestLocale(locale); const messages = await getMessages(); return ( {children} ); } ``` --- ## 7. Pages under `[locale]` For static rendering, every **page** under `[locale]` that uses next-intl must call `setRequestLocale(locale)` (and use `use(params)` if needed). Examples: [app-locale-page.tsx](examples/app-locale-page.tsx), [app-locale-about-page.tsx](examples/app-locale-about-page.tsx). (and use `use(params)` if needed). Layout already sets it; pages that render server components using locale should set it too. ```tsx // app/[locale]/page.tsx import { use } from "react"; import { setRequestLocale } from "next-intl/server"; export default function IndexPage({ params, }: { params: Promise<{ locale: string }>; }) { const { locale } = use(params); setRequestLocale(locale); return ; } ``` ```tsx // app/[locale]/about/page.tsx import { use } from "react"; import { setRequestLocale } from "next-intl/server"; import AboutContainer from "./components/AboutContainer"; export default function AboutPage({ params, }: { params: Promise<{ locale: string }>; }) { const { locale } = use(params); setRequestLocale(locale); return ; } ``` Call `setRequestLocale` **before** any `next-intl` APIs in that layout/page. --- ## 8. Using translations **Client components:** `useTranslations(namespace)`: ```tsx "use client"; import { useTranslations } from "next-intl"; import { Link } from "@/i18n/navigation"; export default function BackToHomeButton() { const t = useTranslations("BackToHomeButton"); return ( {t("buttonText")} ); } ``` ```tsx "use client"; import { useTranslations } from "next-intl"; import { Link } from "@/i18n/navigation"; export default function Nav() { const t = useTranslations("Navigation"); return {t("links.about")}; } ``` **Server components:** use `getTranslations` from `next-intl/server` (await with locale/namespace as needed). --- ## 9. Messages format One JSON file per locale under `messages/`. Nested keys map to namespaces and keys: ```json { "HomePage": { "title": "Hello world!" }, "LandingPage": { "title": "Tokyo Sounds", "navbar": { "home": "Home", "about": "About" } }, "BackToHomeButton": { "buttonText": "Back to Home", "tooltip": "Return to the main page" } } ``` - `useTranslations("LandingPage")` → `t("title")`, `t("navbar.about")`. - Interpolation: `"selectColor": "Select {color} color"` → `t("selectColor", { color: "Blue" })`. --- ## Checklist - [ ] `next.config.ts`: `createNextIntlPlugin()` wraps config. - [ ] `src/i18n/routing.ts`: `defineRouting` with `locales` and `defaultLocale`. - [ ] `src/i18n/request.ts`: `getRequestConfig` + `hasLocale` + dynamic `messages/${locale}.json`. - [ ] `src/proxy.ts` (or `middleware.ts`): `createMiddleware(routing)` and matcher. - [ ] `src/i18n/navigation.ts`: `createNavigation(routing)` and re-export `Link`, etc. - [ ] `app/[locale]/layout.tsx`: `hasLocale` → `notFound`, `setRequestLocale`, `generateStaticParams`, `NextIntlClientProvider` + `getMessages()`. - [ ] Each `app/[locale]/**/page.tsx`: `setRequestLocale(locale)` when using static rendering. - [ ] Client components: `useTranslations("Namespace")`; links use `Link` from `@/i18n/navigation`. --- ## Reference - **Copy-paste examples:** [examples/](examples/) — standalone files for use in any project. - Extended config (localePrefix, pathnames, etc.): [reference.md](reference.md) - Official: [next-intl App Router](https://next-intl.dev/docs/getting-started/app-router), [Routing setup](https://next-intl.dev/docs/routing/setup)