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

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>
  );
}

原因分析

  1. 中间件重定向冲突
    自定义的getLocale逻辑与next-intl内置的语言协商重复,导致中间件对无locale的请求发起重定向,触发两次请求:原始请求+重定向后的请求,最终导致页面渲染和fetch执行两次。

  2. fetch缓存策略不当
    使用cache: 'no-cache'会触发条件请求,在生产环境的缓存机制下容易引发重复请求;同时生产环境App Router的预渲染/hydration逻辑如果和无缓存请求叠加,会加剧重复执行的问题。

  3. 路由切换的潜在问题
    如果使用原生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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 18:45:07