Next.js 如何无闪烁地重定向旧版 -es URL 并初始化 i18next?
可以在页面渲染前处理旧 URL:在 Middleware 或 Proxy 中返回重定向响应,并通过 Cookie 保存 es。目标页面在服务端读取 Cookie,把语言传给 TranslationsProvider,在首次渲染前准备好翻译资源。
这样可以避免进入 not-found.tsx 后再跳转。要消除默认语言切换到西班牙语时的闪烁,还需要让 Provider 使用正确的初始语言,不能继续依赖目标组件挂载后的 changeLanguage()。
为什么当前实现会闪烁?
现有流程会先渲染 Not Found 组件,再运行 useEffect、写入 sessionStorage 并跳转。目标页面随后通过另一个 Effect 切换语言,因此有两个可能出现闪烁的阶段:404 空白页面短暂显示,以及目标页面先显示默认语言。
sessionStorage 只能在浏览器中访问,服务端无法用它决定首次渲染的语言。将它放入 Effect 是避免服务端访问浏览器 API 的常见做法,但事件处理函数也能访问浏览器 API。这里需要调整语言信息的传递位置。
1. 在渲染之前重定向,并设置语言 Cookie
Next.js 16 将 middleware.ts 文件约定更名为 proxy.ts。下面使用 Next.js 16 的写法;使用此前 Middleware 文件约定的项目,可以将文件名和导出函数名分别改为 middleware.ts、middleware。文件应与 app 目录同级;使用 src/app 时,放在 src 下。Proxy 在路由渲染前执行,这里不需要访问 i18next 上下文。Next.js Proxy 文档
// proxy.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const ES_AUTO_REDIRECTS = new Map<string, string>([
['/page1-es', '/page1'],
['/page2-es', '/page2'],
]);
export function proxy(request: NextRequest) {
const target = ES_AUTO_REDIRECTS.get(request.nextUrl.pathname);
if (!target) {
return NextResponse.next();
}
// 使用核心路径,不添加语言参数。
const response = NextResponse.redirect(
new URL(target, request.url),
307,
);
response.cookies.set('app-locale', 'es', {
path: '/',
httpOnly: true,
sameSite: 'lax',
secure: request.nextUrl.protocol === 'https:',
});
response.headers.set('Cache-Control', 'private, no-store');
return response;
}
export const config = {
matcher: ['/page1-es', '/page2-es'],
};
浏览器收到响应后保存 Cookie,再请求 /page1 或 /page2。语言通过 Cookie 传递,地址栏无需增加参数。NextResponse 支持构造重定向响应并设置响应 Cookie。NextResponse 文档
这个示例只匹配明确列出的旧地址,其他未知路径可以继续交给 not-found.tsx 处理。
2. 在目标页面读取语言,而不是挂载后再切换
下面是 /page1 的页面逻辑。TranslationsProvider、App 和 i18nNamespaces 沿用项目现有定义与导入,/page2 采用相同处理。
// app/page1/page.tsx
import { cookies } from 'next/headers';
// 保留项目中 TranslationsProvider、App、i18nNamespaces 的导入。
export default async function Page1() {
const cookieStore = await cookies();
const storedLocale = cookieStore.get('app-locale')?.value;
const locale = storedLocale === 'es' ? 'es' : 'en';
return (
<TranslationsProvider
namespaces={i18nNamespaces}
locale={locale}
>
<App page="page1" />
</TranslationsProvider>
);
}
这里以 en 作为默认语言,结果只有 en 和 es 两种。若项目已有用户语言偏好,需要按实际规则合并,避免直接覆盖。
Server Component 可以通过 cookies() 读取请求 Cookie;示例使用 await cookies()。按请求读取 Cookie 会影响页面的渲染和缓存策略,不能再假定所有用户都能共享同一份静态语言页面。Next.js cookies 文档
原代码中的 locale={params.locale} 也需要统一处理。如果路由参数仍决定语言,而 Cookie 只在组件挂载后覆盖它,首屏语言仍可能不一致。
3. 确保 Provider 首次渲染就使用西班牙语
仅把 es 传给 Provider,还不足以保证没有语言闪烁。题目没有提供 TranslationsProvider 的实现,需要检查它是否满足以下条件:
- 使用传入的
locale初始化 i18next。 - 在渲染翻译内容前,已加载该语言所需的 namespace 资源。
- 服务端输出与客户端 hydration 使用相同的语言和初始翻译资源。
初始化逻辑可以按下面的顺序组织。这里的 resources 表示项目已经加载好的翻译资源,加载方式沿用现有实现:
import { createInstance } from 'i18next';
const i18n = createInstance();
await i18n.init({
lng: locale,
fallbackLng: 'en',
ns: i18nNamespaces,
defaultNS: 'appTranslations',
resources,
});
// 初始化完成后,再使用该实例渲染翻译内容。
这段代码说明初始化顺序,并非完整的 TranslationsProvider 实现。若服务端参与翻译渲染,应按请求创建独立实例,避免并发请求互相修改语言。跨 Server Component 与 Client Component 边界时,应传递可序列化的语言和资源数据,不要直接传递 i18next 实例。
i18next 要求等待初始化完成后再使用翻译函数;react-i18next 的 SSR 文档也说明了向客户端传递初始语言和翻译资源的方式。i18next API 文档、react-i18next SSR 文档
完成初始化调整后,可以删除目标组件中读取 sessionStorage 并调用 changeLanguage() 的启动逻辑,避免再次覆盖语言。
使用时需要注意什么?
示例中的 Cookie 是语言偏好,会随后续请求继续发送,通常也会在同一浏览器的标签页之间共享。它与原来按标签页存储、读取后删除的 sessionStorage 标记并不完全等价。如果只希望影响一次跳转,需要另外设计消费与清除流程;不要在 Server Component 渲染期间直接删除 Cookie。由于示例设置了 httpOnly: true,后续语言切换若要更新该 Cookie,应通过 Route Handler 或 Server Action 完成。
示例使用 307 并禁止缓存这次重定向响应,以便每次访问旧地址时执行语言设置。若要改成永久重定向,需要考虑浏览器和 CDN 缓存对 Set-Cookie 流程的影响。
如果项目已有国际化 Middleware 或 Proxy,应把旧地址映射合并进去,并检查它是否会再次给 /page1 添加语言前缀。若站内仍通过 <Link> 指向这些有语言设置副作用的旧地址,也应考虑关闭这些链接的预取。
这套方案需要能够处理请求的 Next.js 服务端或部署平台支持。纯静态导出需要在托管层另行实现重定向与语言传递。它能消除当前由 404 Effect 和挂载后切换语言引起的闪烁,但网络等待、loading.tsx 或翻译资源尚未准备好造成的加载状态,仍需分别处理。
备注:内容仅供参考。
