Next.js 14集成next-intl后页面与请求重复执行问题排查
Next.js 14 + next-intl 页面切换时渲染/请求重复执行两次
问题描述
使用Next.js 14集成next-intl实现国际化,在browse和my-items页面间切换时,页面渲染及fetch请求会执行两次。移除顶层[locale]目录后,开发环境恢复正常,但生产环境仍存在重复请求问题。
项目目录结构
app ├── [locale] │ ├── items │ │ ├── browse │ │ │ └──page.tsx │ │ ├── my-items │ │ │ └──page.tsx │ │ └──page.tsx ...
相关代码
middleware.ts
import { match } from '@formatjs/intl-localematcher'; import Negotiator, { Headers } from 'negotiator'; import { NextRequest } from 'next/server'; import createIntlMiddleware from 'next-intl/middleware'; const NEXT_LOCALE_NAME = 'NEXT_LOCALE'; const locales = ["en" , "de"]; const defaultLocale = 'en'; type Locales = typeof locales; function getLocale(request: NextRequest): Locales[number] { try { const headers: Headers = Object.fromEntries(request.headers.entries()); const languages = new Negotiator({ headers, }).languages(); return match(languages, locales, defaultLocale) as Locales[number]; } catch (error) { return defaultLocale; } } export default async function middleware(request: NextRequest) { const dl = getLocale(request); const handleI18nRouting = createIntlMiddleware({ locales, defaultLocale: dl, }); const response = handleI18nRouting(request); return response; } export const config = { matcher: ['/((?!_next|api).*)'] };
browse/page.tsx
const getData = async () => { return fetch('https://browse-url', { 'cache': 'no-cache' }); } export default async function Page() { const data = await getData(); const dataJson = await data.json() return ( <main> <h1>Browse Page</h1> </main> ); }
my-items/page.tsx
const getData = async () => { return fetch('https://my-items', { 'cache': 'no-cache' }); } export default async function Page() { const data = await getData(); const dataJson = await data.json() return ( <main> <h1>My-items Page</h1> </main> ); }
原因分析
中间件重定向冲突
自定义的getLocale逻辑与next-intl内置的语言协商重复,导致中间件对无locale的请求发起重定向,触发两次请求:原始请求+重定向后的请求,最终导致页面渲染和fetch执行两次。fetch缓存策略不当
使用cache: 'no-cache'会触发条件请求,在生产环境的缓存机制下容易引发重复请求;同时生产环境App Router的预渲染/hydration逻辑如果和无缓存请求叠加,会加剧重复执行的问题。路由切换的潜在问题
如果使用原生a标签而非Next.js的Link组件切换页面,会触发全页面刷新,也可能导致重复请求。
修复方案
1. 简化中间件逻辑
移除自定义的语言协商,直接使用next-intl内置的中间件处理,避免重定向冲突:
import createIntlMiddleware from 'next-intl/middleware'; const locales = ["en" , "de"]; const defaultLocale = 'en'; export default createIntlMiddleware({ locales, defaultLocale }); export const config = { matcher: ['/((?!_next|api).*)'] };
说明:next-intl的
createIntlMiddleware已经内置了基于请求头的语言协商和重定向逻辑,无需手动实现。
2. 优化fetch缓存配置
- 若无需实时数据,使用默认的
cache: 'force-cache'或设置revalidate实现增量静态再生:const getData = async () => { return fetch('https://browse-url', { next: { revalidate: 60 } }); }; - 若需实时数据,改用
cache: 'no-store'替代no-cache,避免条件请求引发的重复:const getData = async () => { return fetch('https://browse-url', { cache: 'no-store' }); };
3. 使用Link组件进行路由切换
确保页面切换使用Next.js的Link组件,实现客户端路由跳转,避免全页面刷新:
import Link from 'next/link'; // 在页面中添加切换链接 <Link href="/items/browse">Browse</Link> <Link href="/items/my-items">My Items</Link>
4. 验证生产环境配置
- 确保
app/[locale]/layout.tsx中正确配置NextIntlClientProvider:import { NextIntlClientProvider } from 'next-intl'; import { getMessages } from 'next-intl/server'; export default async function LocaleLayout({ children, params: { locale } }: { children: React.ReactNode; params: { locale: string }; }) { const messages = await getMessages({ locale }); return ( <html lang={locale}> <body> <NextIntlClientProvider locale={locale} messages={messages}> {children} </NextIntlClientProvider> </body> </html> ); } - 检查
next.config.js,确保没有开启不必要的静态导出或预渲染选项,比如output: 'export'(若不是静态站点则关闭)。
内容的提问来源于stack exchange,提问作者Peter
相关产品推荐
相关产品推荐

