如何解决浏览器自动翻译导致服务端/客户端内容不匹配问题
解决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;
验证步骤
- 访问根路径,会自动重定向到
/ka - 切换到
/en,页面内容应为英语,且控制台无hydration错误 - 刷新
/en页面,服务端和客户端的lang值均为en
内容的提问来源于stack exchange,提问作者Bryan Fink
相关产品推荐
相关产品推荐

