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

NextJS+react-i18n国际化下Canonical URL生产环境异常求助

解决NextJS 14.2.3 + Vercel部署下默认语言Canonical URL带前缀的问题

核心问题

Vercel生产环境下,默认语言(da)的路由被自动加上了/da前缀,导致canonical URL不符合预期,而本地测试正常。这通常是因为路由处理逻辑在生产环境的解析差异,或是metadata生成时未区分默认语言和非默认语言的路径规则。

解决方案步骤

1. 确认NextJS国际化配置

先检查next.config.js的i18n配置,确保默认语言设置正确:

/** @type {import('next').NextConfig} */
const nextConfig = {
  i18n: {
    locales: ['da', 'en'],
    defaultLocale: 'da',
    localeDetection: false, // 关闭自动语言检测,避免不必要的跳转
  },
}

module.exports = nextConfig

2. 用Middleware统一路由规则

添加middleware.js,将默认语言的带前缀路径重定向到无前缀版本,同时确保非默认语言保留前缀:

import { NextResponse } from 'next/server'
import Negotiator from 'negotiator'
import { match } from '@formatjs/intl-localematcher'

const locales = ['da', 'en']
const defaultLocale = 'da'

function getLocale(request) {
  const headers = {}
  request.headers.forEach((val, key) => (headers[key] = val))
  const languages = new Negotiator({ headers }).languages()
  return match(languages, locales, defaultLocale)
}

export function middleware(request) {
  const pathname = request.nextUrl.pathname

  // 已包含locale前缀的路径直接放行
  if (locales.some(locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`)) {
    // 如果是默认语言的前缀路径,重定向到无前缀版本
    if (pathname.startsWith(`/${defaultLocale}`)) {
      const newPath = pathname.replace(`/${defaultLocale}`, '') || '/'
      return NextResponse.redirect(new URL(newPath, request.url))
    }
    return
  }

  // 无前缀路径:根据用户语言偏好跳转,默认语言直接保留无前缀
  const locale = getLocale(request)
  if (locale === defaultLocale) {
    return NextResponse.next()
  } else {
    return NextResponse.redirect(new URL(`/${locale}${pathname}`, request.url))
  }
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}

3. 精准生成Canonical URL

在App Router的layout.js或page.js中,通过generateMetadata函数,根据当前locale是否为默认语言,构建正确的绝对路径Canonical:

import { headers } from 'next/headers'

export async function generateMetadata({ params, locale }) {
  const defaultLocale = 'da'
  const host = headers().get('host')
  const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http'

  // 构建路径:默认语言去掉前缀,非默认语言保留前缀
  let canonicalPath = ''
  if (locale !== defaultLocale) {
    canonicalPath = `/${locale}`
  }
  // 拼接子页面路径
  if (params?.slug) {
    canonicalPath += `/${params.slug}`
  }

  const canonicalUrl = `${protocol}://${host}${canonicalPath}`

  return {
    alternates: {
      canonical: canonicalUrl,
      languages: {
        'da': `${protocol}://${host}${params?.slug ? `/${params.slug}` : ''}`,
        'en': `${protocol}://${host}/en${params?.slug ? `/${params.slug}` : ''}`,
      },
    },
    // 其他元数据配置...
  }
}

额外注意事项

  • 避免使用相对路径(./或/)生成canonical,绝对路径能确保本地和生产环境解析一致。
  • 部署到Vercel后,清除缓存重新部署一次,确保路由规则生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 20:51:18