You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在带自定义中间件的NextJS项目中配置i18n:动态路由问题

解决方案

步骤1:调整项目结构

将主站的example.com文件夹移到[lang]目录下,同时新增无[lang]前缀的主站入口,用于默认英文路由的转发:

└── project-root
    ├── app
    │   ├── app <-- 子域名; app.localhost:3000
    │   │   ├── (auth)
    │   │   │   ├── *sign-in and sign-out routes here*
    │   │   │   └── layout.tsx
    │   │   └── (dashboard)
    │   │       ├── features
    │   │       │   └── page.tsx
    │   │       ├── page.tsx
    │   │       └── layout.tsx
    │   ├── [lang]
    │   │   └── example.com <-- 多语言主站核心代码
    │   │       ├── about
    │   │       │   └── page.tsx
    │   │       ├── features
    │   │       │   └── page.tsx
    │   │       ├── page.tsx
    │   │       └── layout.tsx  
    │   ├── example.com <-- 默认英文入口(转发到[lang]/example.com/en)
    │   │   ├── layout.tsx
    │   │   └── page.tsx
    │   ├── layout.tsx
    │   └── providers.tsx  

app/example.com/page.tsx内容可简化为:

'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';

export default function DefaultHome() {
  const router = useRouter();
  useEffect(() => {
    router.replace('/en');
  }, [router]);
  return null;
}

步骤2:修改middleware.ts,处理语言检测与路由重写

更新中间件逻辑,优先识别路径中的语言码,再处理主站与子域名的路由映射:

import { NextFetchEvent, NextRequest, NextResponse } from "next/server"
import { APP_HOSTNAMES, isHomeHostname } from "@/lib/constants"
import { AppMiddleware } from "@/lib/middleware"
import { parse } from "@/lib/middleware/utils"

const SUPPORTED_LANGUAGES = ["en", "es"];
const DEFAULT_LANG = "en";

export const config = {
  matcher: [
    "/((?!api/|_next/|_proxy/|_static|_vercel|[\w-]+\.\w+|).*)",
  ],
}

export default async function middleware(req: NextRequest, ev: NextFetchEvent) {
  const { domain, path } = parse(req)
  const url = req.nextUrl;

  // 优先处理App子域名,保留原有逻辑
  if (APP_HOSTNAMES.has(domain)) {
    return AppMiddleware(req, ev)
  }

  // 处理主站域名
  if (isHomeHostname(domain)) {
    // 提取路径中的语言码
    const langSegment = url.pathname.split('/')[1];
    const isLangValid = SUPPORTED_LANGUAGES.includes(langSegment);
    
    // 情况1:路径包含有效语言码(如/es/features)
    if (isLangValid) {
      const targetPath = url.pathname.slice(`/${langSegment}`.length) || '/';
      return NextResponse.rewrite(
        new URL(`/${langSegment}/example.com${targetPath}`, req.url)
      );
    }

    // 情况2:路径无语言码(如/features或/),重写为默认英文
    return NextResponse.rewrite(
      new URL(`/${DEFAULT_LANG}/example.com${path === "/" ? "" : path}`, req.url)
    );
  }

  return NextResponse.next();
}

步骤3:配置next.config.js(可选,优化路由映射)

添加rewrites规则,让无语言码的路径直接映射到默认语言,同时避免与子域名路由冲突:

const { withContentlayer } = require("next-contentlayer")

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  async rewrites() {
    return [
      {
        source: '/:path*',
        destination: '/en/example.com/:path*',
        has: [{ type: 'host', value: 'localhost:3000' }], // 生产环境替换为example.com
        missing: [
          { type: 'pathname', value: '/:lang(en|es)/:path*' },
          { type: 'pathname', value: '/app/:path*' },
        ],
      },
    ];
  },
}

module.exports = withContentlayer(nextConfig)

步骤4:实现语言切换组件

创建客户端组件处理语言切换逻辑,确保路由正确更新:

'use client';

import { useRouter } from 'next/navigation';

export default function LanguageSwitcher() {
  const router = useRouter();
  const currentPath = window.location.pathname;

  const switchLang = (targetLang: string) => {
    // 检查当前路径是否已有语言码
    const regex = /^\/([a-z]{2})(\/.*)?$/;
    const match = currentPath.match(regex);

    if (match) {
      // 替换现有语言码
      router.push(`/${targetLang}${match[2] || '/'}`);
    } else {
      // 为无语言码的路径添加前缀
      router.push(`/${targetLang}${currentPath === '/' ? '' : currentPath}`);
    }
  };

  return (
    <div className="flex gap-2">
      <button onClick={() => switchLang('en')}>English</button>
      <button onClick={() => switchLang('es')}>Español</button>
    </div>
  );
}

步骤5:动态设置根布局的lang属性

修改app/layout.tsx,根据路由参数动态设置html的语言属性:

import "@/styles/globals.css"
import { fontInter } from "@/lib/fonts"
import { Analytics } from "@vercel/analytics/react"
import Providers from "./providers"
import { useParams } from 'next/navigation';

const DEFAULT_LANG = "en";

interface RootLayoutProps {
  children: React.ReactNode
}

export default function RootLayout({ children }: RootLayoutProps) {
  const params = useParams();
  const currentLang = (params.lang as string) || DEFAULT_LANG;

  return (
    <html
      lang={currentLang}
      suppressHydrationWarning={true}
      className={fontInter.className}
    >
      <body className="bg-background antialiased">
        <Providers>{children}</Providers>
      </body>
      <Analytics />
    </html>
  )
}

内容的提问来源于stack exchange,提问作者cording12

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 05:17:06