Table of Contents
- Introduction
- Why Internationalization Matters
- Built-in i18n Support in Next.js
- Step-by-Step Setup for i18n in App Router
- Routing and Locale Detection
- Handling Translations with
next-intl
- Dynamic Locale Loading
- Nested Layouts and Localized Content
- SEO Best Practices for i18n
- Handling Forms, Errors, and Dates in Locales
- Tips for Real-World Multilingual Projects
- Conclusion
1. Introduction
Internationalization (i18n) is crucial for applications with a global user base. With Next.js App Router, i18n capabilities are more seamless and powerful, allowing for:
- Per-locale routing (e.g.,
/en
,/fr
,/de
) - Dynamic translation loading
- Localized content components
- SEO enhancements with localized metadata
In this guide, we’ll walk through a practical setup and cover how to build a robust multilingual app.
2. Why Internationalization Matters
Here are some key reasons to invest in i18n:
- User experience: Visitors are more likely to engage with content in their native language.
- SEO: Localized URLs and meta tags help rank content better in regional search engines.
- Market reach: Expand your app’s reach across geographies.
3. Built-in i18n Support in Next.js
Next.js offers built-in i18n support in the configuration via next.config.js
:
jsCopyEdit// next.config.js
module.exports = {
i18n: {
locales: ['en', 'fr', 'de'],
defaultLocale: 'en',
localeDetection: true,
},
};
This does not handle translations — it only provides locale-based routing and detection. For managing translations, we’ll use the next-intl
package.
4. Step-by-Step Setup for i18n in App Router
1. Install Dependencies
bashCopyEditnpm install next-intl
2. Set Up the File Structure
pgsqlCopyEditapp/
[locale]/
layout.tsx
page.tsx
i18n/
en.json
fr.json
de.json
3. Create Your Locale Files
app/i18n/en.json
jsonCopyEdit{
"greeting": "Hello",
"login": "Login",
"welcome": "Welcome to our app!"
}
Repeat for fr.json
, de.json
, etc.
5. Routing and Locale Detection
With the App Router, you can use dynamic segments to scope content per locale:
tsxCopyEdit// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { notFound } from 'next/navigation';
export default async function LocaleLayout({ children, params: { locale } }) {
let messages;
try {
messages = (await import(`../../i18n/${locale}.json`)).default;
} catch (error) {
notFound();
}
return (
<html lang={locale}>
<body>
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
And use localized URLs like:
/en
/fr
/de
6. Handling Translations with next-intl
To use a translation string:
tsxCopyEdit// app/[locale]/page.tsx
'use client';
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations();
return <h1>{t('welcome')}</h1>;
}
You get full type-safety and fallback handling with next-intl
.
7. Dynamic Locale Loading
The locale-specific layout already loads the necessary JSON dynamically using:
tsCopyEditawait import(`../../i18n/${locale}.json`);
This makes the app scalable and optimized for only loading relevant translation payloads.
You can extend this logic to also load:
- Date formatting rules
- Time zone preferences
- Pluralization rules
8. Nested Layouts and Localized Content
Since layouts can be nested per route, you can have a consistent layout while swapping out translated content:
tsxCopyEdit// app/[locale]/dashboard/page.tsx
'use client';
import { useTranslations } from 'next-intl';
export default function Dashboard() {
const t = useTranslations('dashboard');
return <h2>{t('title')}</h2>;
}
Your translation file:
jsonCopyEdit{
"dashboard": {
"title": "Your Dashboard"
}
}
9. SEO Best Practices for i18n
Make sure to update:
1. <html lang="...">
dynamically:
Done in the layout with:
tsxCopyEdit<html lang={locale}>
2. Use the Metadata API to localize meta tags:
tsCopyEdit// app/[locale]/page.tsx
export async function generateMetadata({ params }) {
const messages = (await import(`../../i18n/${params.locale}.json`)).default;
return {
title: messages.meta.title,
description: messages.meta.description
};
}
3. Use <link rel="alternate" hreflang="...">
for alternate locales.
This helps Google understand which page is for which language.
10. Handling Forms, Errors, and Dates in Locales
Use Intl.DateTimeFormat
for date/time formatting:
tsCopyEditconst formattedDate = new Intl.DateTimeFormat(locale, {
dateStyle: 'long'
}).format(new Date());
Translate error messages, buttons, and form placeholders by using structured keys:
jsonCopyEdit{
"form": {
"username": "Username",
"password": "Password",
"submit": "Submit",
"errors": {
"required": "This field is required",
"invalid": "Invalid value"
}
}
}
11. Tips for Real-World Multilingual Projects
- Use consistent translation keys with nested objects.
- Maintain one master language file and extract others from it using tools like
i18next-parser
. - Add fallbacks for missing translations.
- Use static analysis or linters to detect untranslated keys.
- Centralize translation logic in a utility or wrapper if needed.
12. Conclusion
Internationalization with Next.js and the App Router is powerful, flexible, and scalable. With the help of next-intl
and dynamic routing, you can deliver localized experiences across the world — with full SEO benefits and optimal performance.
Multilingual support isn’t just a “nice-to-have” anymore. It’s a crucial step for user growth, global trust, and long-term success.