Complete Guide to Building Multilingual Sites with Next.js 15
An in-depth exploration of building modern multilingual websites using Next.js 15 App Router, including routing configuration, SEO optimization, and performance best practices.
Complete Guide to Building Multilingual Sites with Next.js 15
In today's globalized world, building multilingual websites has become an essential skill for modern web development. Next.js 15's App Router provides powerful and flexible solutions for multilingual website development.
Project Initialization
First, let's create a new Next.js 15 project:
npx create-next-app@latest my-multilingual-site
cd my-multilingual-site
npm install next-intl
Internationalization Configuration
1. Create Language Configuration File
Create src/lib/i18n.ts
:
export const locales = ['zh', 'en', 'fr', 'de'] as const;
export type Locale = typeof locales[number];
export const defaultLocale: Locale = 'en';
export const localeNames: Record<Locale, string> = {
zh: '中文',
en: 'English',
fr: 'Français',
de: 'Deutsch'
};
2. Configure Middleware
Create middleware.ts
:
import { createMiddleware } from 'next-intl/middleware';
import { locales, defaultLocale } from './src/lib/i18n';
export default createMiddleware({
locales,
defaultLocale,
localePrefix: 'always'
});
export const config = {
matcher: [
'/((?!api|_next|_vercel|.*\..*).*)',
'/([\w-]+)?/users/(.+)'
]
};
Route Structure Design
Using App Router's dynamic routing features:
src/app/
├── [locale]/
│ ├── layout.tsx
│ ├── page.tsx
│ ├── about/
│ │ └── page.tsx
│ ├── blog/
│ │ ├── page.tsx
│ │ └── [slug]/
│ │ └── page.tsx
│ └── contact/
│ └── page.tsx
├── globals.css
└── layout.tsx
Translation File Management
Create Translation Files
messages/zh.json
:
{
"navigation": {
"home": "首页",
"about": "关于我们",
"blog": "博客",
"contact": "联系我们"
},
"home": {
"title": "欢迎来到我们的网站",
"description": "这是一个多语言网站示例"
}
}
messages/en.json
:
{
"navigation": {
"home": "Home",
"about": "About",
"blog": "Blog",
"contact": "Contact"
},
"home": {
"title": "Welcome to Our Website",
"description": "This is a multilingual website example"
}
}
Component Internationalization
Create Navigation Component
'use client';
import { useTranslations } from 'next-intl';
import { useParams } from 'next/navigation';
import Link from 'next/link';
export default function Navigation() {
const t = useTranslations('navigation');
const params = useParams();
const locale = params.locale as string;
const navItems = [
{ key: 'home', href: `/${locale}` },
{ key: 'about', href: `/${locale}/about` },
{ key: 'blog', href: `/${locale}/blog` },
{ key: 'contact', href: `/${locale}/contact` }
];
return (
<nav className="flex space-x-6">
{navItems.map(item => (
<Link
key={item.key}
href={item.href}
className="hover:text-blue-600 transition-colors"
>
{t(item.key)}
</Link>
))}
</nav>
);
}
SEO Optimization
Multilingual SEO Configuration
import { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
interface Props {
params: { locale: string };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const t = await getTranslations({ locale: params.locale, namespace: 'metadata' });
return {
title: t('title'),
description: t('description'),
alternates: {
canonical: `https://example.com/${params.locale}`,
languages: {
'zh': 'https://example.com/zh',
'en': 'https://example.com/en',
'fr': 'https://example.com/fr',
'de': 'https://example.com/de'
}
},
openGraph: {
title: t('title'),
description: t('description'),
locale: params.locale,
alternateLocale: ['zh', 'en', 'fr', 'de'].filter(l => l !== params.locale)
}
};
}
Performance Optimization
1. Dynamic Import of Translation Files
async function getMessages(locale: string) {
try {
return (await import(`../messages/${locale}.json`)).default;
} catch (error) {
return (await import('../messages/en.json')).default;
}
}
2. Static Generation Optimization
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
Language Switcher
'use client';
import { useParams, usePathname } from 'next/navigation';
import Link from 'next/link';
import { locales, localeNames } from '@/lib/i18n';
export default function LanguageSwitcher() {
const params = useParams();
const pathname = usePathname();
const currentLocale = params.locale as string;
const getLocalizedPath = (locale: string) => {
const segments = pathname.split('/');
segments[1] = locale;
return segments.join('/');
};
return (
<div className="relative">
<select
value={currentLocale}
onChange={(e) => {
const newPath = getLocalizedPath(e.target.value);
window.location.href = newPath;
}}
className="bg-white border border-gray-300 rounded px-3 py-1"
>
{locales.map(locale => (
<option key={locale} value={locale}>
{localeNames[locale]}
</option>
))}
</select>
</div>
);
}
Best Practices
1. Translation Key Naming Conventions
- Use nested structures to organize translation keys
- Keep key names concise and descriptive
- Use consistent naming conventions
2. Content Management
- Consider using CMS for multilingual content management
- Implement translation workflows
- Regularly review and update translations
3. Testing Strategy
// Test multilingual routing
describe('Multilingual Routing', () => {
test('should redirect to default locale', () => {
// Test logic
});
test('should preserve locale in navigation', () => {
// Test logic
});
});
Conclusion
Next.js 15's App Router provides powerful tools and flexible architecture for building multilingual websites. With proper configuration and optimization, we can create high-performance, SEO-friendly multilingual websites.
Key takeaways:
- Use middleware for language detection and redirection
- Properly organize translation files and route structure
- Focus on SEO optimization and performance improvement
- Establish comprehensive testing and maintenance processes
In the next article, we'll explore how to implement complex content management and dynamic translation features in multilingual websites.
Related Articles
Other articles you might be interested in