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

如何解决浏览器自动翻译导致服务端/客户端内容不匹配问题

解决Next14 + Storyblok + i18n的服务端/客户端lang不匹配问题

问题根源

你遇到的Prop 'lang' did not match. Server: "en" Client: "ka"错误,本质是服务端渲染时使用的locale(en)与客户端hydration阶段使用的locale(ka)不一致,导致Next.js的hydration校验失败。核心原因在于locale检测逻辑、语言切换的状态同步,以及i18n配置存在疏漏。


分步解决方案

1. 修复中间件的Locale检测逻辑

当前getLocale函数硬返回ka,无法正确识别用户手动切换的语言或浏览器偏好。修改为以下逻辑,优先读取用户已选择的语言(通过cookie),再匹配浏览器偏好,最后 fallback 到默认的格鲁吉亚语:

import { NextResponse } from "next/server";

const locales = ["ka", "en"];

function getLocale(request) {
  // 1. 优先从cookie获取用户之前选择的语言
  const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value;
  if (cookieLocale && locales.includes(cookieLocale)) {
    return cookieLocale;
  }

  // 2. 从请求头读取浏览器语言偏好
  const acceptLanguage = request.headers.get('accept-language');
  if (acceptLanguage) {
    const preferredLocales = acceptLanguage.split(',').map(lang => lang.split(';')[0].trim());
    for (const locale of preferredLocales) {
      // 匹配完整locale(如en)或短码(如en-US -> en)
      if (locales.includes(locale)) return locale;
      const shortLocale = locale.split('-')[0];
      if (locales.includes(shortLocale)) return shortLocale;
    }
  }

  // 3. 默认返回格鲁吉亚语
  return 'ka';
}

export function middleware(request) {
  const { pathname } = request.nextUrl;
  const pathnameHasLocale = locales.some(
    (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  );

  if (pathnameHasLocale) return;

  const locale = getLocale(request);
  request.nextUrl.pathname = `/${locale}${pathname}`;
  return NextResponse.redirect(request.nextUrl);
}

export const config = {
  matcher: ['/((?!_next).*)'],
};

2. 确保根布局的lang属性与服务端locale同步

在app/[lang]/layout.js中,直接使用路由参数的lang设置html标签的lang属性,确保服务端和客户端渲染的lang值一致:

export default async function RootLayout({ children, params: { lang } }) {
  return (
    <html lang={lang}>
      <body>{children}</body>
    </html>
  );
}

3. 语言切换组件同步Cookie与路由

客户端切换语言时,必须设置Cookie保存用户选择,让中间件在后续请求中能正确识别,避免客户端与服务端locale脱节:

'use client';

import { useRouter } from 'next/navigation';
import { cookies } from 'next/headers';

export default function LanguageSwitcher({ currentLang }) {
  const router = useRouter();

  const handleSwitch = (newLang) => {
    if (newLang === currentLang) return;
    
    // 写入Cookie,持久化用户语言选择
    cookies().set('NEXT_LOCALE', newLang, { 
      path: '/',
      maxAge: 30 * 24 * 60 * 60 // 30天有效期
    });

    // 更新路由路径
    const newPath = window.location.pathname.replace(`/${currentLang}`, `/${newLang}`);
    router.push(newPath);
  };

  return (
    <div>
      <button onClick={() => handleSwitch('ka')} disabled={currentLang === 'ka'}>
        ქართული
      </button>
      <button onClick={() => handleSwitch('en')} disabled={currentLang === 'en'}>
        English
      </button>
    </div>
  );
}

4. Storyblok内容请求绑定当前Locale

在页面组件中获取Storyblok内容时,必须传入当前路由的lang参数,确保服务端渲染的内容与locale匹配:

import { getStoryblokApi } from '@storyblok/react';

export default async function Home({ params: { lang } }) {
  const storyblokApi = getStoryblokApi();
  const { data } = await storyblokApi.get(`cdn/stories/home`, {
    version: 'published',
    language: lang, // 绑定当前locale
  });

  return (
    <div>
      {/* 渲染Storyblok内容 */}
    </div>
  );
}

5. 配置Next.js全局i18n

在next.config.js中明确设置默认语言和支持的locale列表:

/** @type {import('next').NextConfig} */
const nextConfig = {
  i18n: {
    locales: ['ka', 'en'],
    defaultLocale: 'ka', // 默认语言为格鲁吉亚语
  },
};

module.exports = nextConfig;

验证步骤

  1. 访问根路径,会自动重定向到/ka
  2. 切换到/en,页面内容应为英语,且控制台无hydration错误
  3. 刷新/en页面,服务端和客户端的lang值均为en

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 19:42:46