在带自定义中间件的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
相关产品推荐
相关产品推荐

