frontend

用 Next.js 15 构建多语言网站完整指南

深入探讨如何使用 Next.js 15 的 App Router 构建支持多语言的现代网站,包括路由配置、SEO 优化、性能提升等最佳实践。

张伟
2024/12/15
8 分钟阅读
Next.jsi18nSEOApp Router

用 Next.js 15 构建多语言网站完整指南

在全球化的今天,构建支持多语言的网站已经成为现代 Web 开发的必备技能。Next.js 15 的 App Router 为多语言网站开发提供了强大而灵活的解决方案。

项目初始化

首先,让我们创建一个新的 Next.js 15 项目:

npx create-next-app@latest my-multilingual-site
cd my-multilingual-site
npm install next-intl

配置国际化

1. 创建语言配置文件

创建 src/lib/i18n.ts

export const locales = ['zh', 'en', 'fr', 'de'] as const;
export type Locale = typeof locales[number];

export const defaultLocale: Locale = 'zh';

export const localeNames: Record<Locale, string> = {
  zh: '中文',
  en: 'English',
  fr: 'Français',
  de: 'Deutsch'
};

2. 配置中间件

创建 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/(.+)'
  ]
};

路由结构设计

使用 App Router 的动态路由功能:

src/app/
├── [locale]/
│   ├── layout.tsx
│   ├── page.tsx
│   ├── about/
│   │   └── page.tsx
│   ├── blog/
│   │   ├── page.tsx
│   │   └── [slug]/
│   │       └── page.tsx
│   └── contact/
│       └── page.tsx
├── globals.css
└── layout.tsx

翻译文件管理

创建翻译文件

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"
  }
}

组件国际化

创建导航组件

'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 优化

多语言 SEO 配置

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)
    }
  };
}

性能优化

1. 动态导入翻译文件

async function getMessages(locale: string) {
  try {
    return (await import(`../messages/${locale}.json`)).default;
  } catch (error) {
    return (await import('../messages/zh.json')).default;
  }
}

2. 静态生成优化

export function generateStaticParams() {
  return locales.map((locale) => ({ locale }));
}

语言切换器

'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>
  );
}

最佳实践

1. 翻译键命名规范

  • 使用嵌套结构组织翻译键
  • 保持键名简洁且具有描述性
  • 使用一致的命名约定

2. 内容管理

  • 考虑使用 CMS 管理多语言内容
  • 实现翻译工作流程
  • 定期审查和更新翻译

3. 测试策略

// 测试多语言路由
describe('Multilingual Routing', () => {
  test('should redirect to default locale', () => {
    // 测试逻辑
  });

  test('should preserve locale in navigation', () => {
    // 测试逻辑
  });
});

总结

Next.js 15 的 App Router 为构建多语言网站提供了强大的工具和灵活的架构。通过合理的配置和优化,我们可以创建出性能优异、SEO 友好的多语言网站。

关键要点:

  • 使用中间件处理语言检测和重定向
  • 合理组织翻译文件和路由结构
  • 重视 SEO 优化和性能提升
  • 建立完善的测试和维护流程

在下一篇文章中,我们将探讨如何在多语言网站中实现复杂的内容管理和动态翻译功能。

喜欢这篇文章?

关注我们的博客,获取更多技术文章和行业洞察