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

Next.js 14.1.0+next-intl实现动态国家前缀路由及首页双视图求助

国家选择功能开发问题

我正在开发国家选择功能,需求如下:

  • 新用户访问首页时,需展示国家选择视图
  • 已选择过国家的用户,直接跳转至带/us或对应国家代码前缀路径的首页

技术栈:Next.js 14.1.0 + next-intl,已设置localePrefix: 'never'避免显示本地化前缀。尝试过以下方案但效果不佳:

  1. 用并行路由实现视图切换
  2. 修改middleware逻辑
  3. 修改next.config.js的rewrites配置,但无法读取cookie中的已选国家值

求更优实现方案。

文件夹结构

Folder structure

Locale Layout 代码

import { Viewport } from 'next';
import '@/styles/globals.css';
import IntlProvider from '@/contexts/intl.provider';
import { cookies } from 'next/headers';
import { COUNTRY_COOKIE } from '@/i18n/routing';

export const viewport: Viewport = {
  themeColor: "#fff"
}

export default async function RootLayout({
  params,
  home,
  country,
}: {
  params: { locale: string };
  home: React.ReactNode;
  country: React.ReactNode;
}) {
  const cookieStore = cookies()

  return (
    <html lang={params.locale}>
      <body>
        <IntlProvider>
          {cookieStore.get(COUNTRY_COOKIE) ? (
            home
          ) : (
            country
          )}
        </IntlProvider>
      </body>
    </html>
  );
}

middleware.ts 代码

export default async function middleware(req: NextRequest) {
  const publicPathnameRegex = RegExp(
    `^(/(${locales.join('|')}))?(${publicPages
      .flatMap((p) => (p === '/' ? ['', '/'] : p))
      .join(
        '|'
      )}|\/(product|category|supplier|brand|collection|auth\/forget-password)?\/[^\/]+)?$`,
    'i'
  );
  const isPublicPage = publicPathnameRegex.test(
    req.nextUrl.pathname.split(countryPrefix[0])[1] ??
      req.nextUrl.pathname.split(countryPrefix[0])[0]
  );
  const selectedCountry = req.cookies.get(COUNTRY_COOKIE);
  if (selectedCountry) {
    if (isPublicPage) {
      return NextResponse.rewrite(new URL(req.nextUrl.pathname, req.url));
    } else {
      return (authMiddleware as any)(req);
    }
  } else {
    return intlMiddleware(req);
  }
}

export const config = {
  // Match only internationalized pathnames
  matcher: ['/((?!api|_next|.*\\..*).*)', '/', '/:path*'],
};

intl-config 代码

export const routing = defineRouting({
  // A list of all locales that are supported
  locales: ['en', 'fr'],

  // Used when no locale matches
  defaultLocale: 'en',

  localePrefix: 'never',
});

next.config.js 代码

const nextConfig = {
  async rewrites() {
    return [
      {
        source: '/:path*',
        destination: '/should-read-from-cookie/:path*'
      }
    ]
  },
}

优化方案

1. 调整 Middleware 逻辑,实现路径重定向与视图控制

放弃并行路由的条件渲染,改用 Middleware 根据 cookie 直接处理路由跳转,更符合 Next.js 14 的 App Router 逻辑:

import { NextRequest, NextResponse } from 'next/server';
import { COUNTRY_COOKIE } from '@/i18n/routing';

export default async function middleware(req: NextRequest) {
  const url = req.nextUrl;
  const selectedCountry = req.cookies.get(COUNTRY_COOKIE);
  const isCountrySelectPage = url.pathname === '/country-select';

  // 已选国家,且当前路径无国家前缀时,重定向到带国家前缀的首页
  if (selectedCountry && !url.pathname.startsWith(`/${selectedCountry.value}`) && !isCountrySelectPage) {
    return NextResponse.redirect(new URL(`/${selectedCountry.value}`, req.url));
  }

  // 未选国家,且当前不是国家选择页时,重定向到国家选择页
  if (!selectedCountry && !isCountrySelectPage) {
    return NextResponse.redirect(new URL('/country-select', req.url));
  }

  // 正常放行
  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next|.*\\..*|country-select).*)'],
};

2. 重构页面结构,分离国家选择页与带前缀的首页

  • 创建app/country-select/page.tsx:专门用于新用户的国家选择界面,选择后设置COUNTRY_COOKIE并跳转到对应国家前缀的首页。
  • 创建app/[country]/page.tsx:作为已选国家用户的首页,通过params.country获取当前国家代码,配合 next-intl 做本地化处理。

3. 调整 next-intl 配置,适配国家前缀路由

修改intl-config.ts,保留localePrefix: 'never',新增国家与locale的映射:

export const routing = defineRouting({
  locales: ['en', 'fr'],
  defaultLocale: 'en',
  localePrefix: 'never',
});

// 国家与locale的映射关系
export const countryToLocale: Record<string, string> = {
  us: 'en',
  fr: 'fr',
  // 可扩展其他国家映射
};

在app/[country]/layout.tsx中动态设置locale:

import { getMessages } from 'next-intl/server';
import { countryToLocale } from '@/i18n/routing';
import IntlProvider from '@/contexts/intl.provider';

export default async function CountryLayout({
  children,
  params: { country },
}: {
  children: React.ReactNode;
  params: { country: string };
}) {
  const locale = countryToLocale[country] || 'en';
  const messages = await getMessages({ locale });

  return (
    <html lang={locale}>
      <body>
        <IntlProvider locale={locale} messages={messages}>
          {children}
        </IntlProvider>
      </body>
    </html>
  );
}

4. 移除 next.config.js 中的无效 rewrites

rewrites 在构建阶段执行,无法读取请求时的 cookie,直接删除该配置,改用 Middleware 处理动态路由逻辑。

5. 国家选择页核心逻辑示例

'use client';

import { useRouter } from 'next/navigation';
import { COUNTRY_COOKIE } from '@/i18n/routing';

export default function CountrySelectPage() {
  const router = useRouter();

  const handleSelectCountry = (countryCode: string) => {
    // 设置cookie,有效期按需调整
    document.cookie = `${COUNTRY_COOKIE}=${countryCode}; path=/; max-age=31536000`;
    router.push(`/${countryCode}`);
  };

  return (
    <div>
      <h1>选择您的国家</h1>
      <button onClick={() => handleSelectCountry('us')}>美国</button>
      <button onClick={() => handleSelectCountry('fr')}>法国</button>
    </div>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 10:39:51