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

组合next-intl与next-auth中间件出现ERR_TOO_MANY_REDIRECTS问题求助

next-intl 结合 next-auth 引发无限循环问题

背景说明

我的实现基于next-intl官方的next-auth示例项目,针对业务需求做了以下调整:

  • 无任何未受保护的页面
  • 用户访问首页时自动重定向到授权提供商页面
  • 登出操作需通过授权提供商完成

项目文件夹结构

文件夹结构

相关代码

middleware.ts

import { NextRequestWithAuth, withAuth } from 'next-auth/middleware';
import createIntlMiddleware from 'next-intl/middleware';
import { NextRequest, NextResponse } from 'next/server';

const locales = ['bg', 'en'];

const intlMiddleware = createIntlMiddleware({
  locales,
  defaultLocale: 'bg',
});

const authMiddleware = withAuth(
  (req: NextRequestWithAuth) => {
    const now: any = new Date();
    const timestamp = Math.round(now / 1000);
    const { expiresAt }: any = req.nextauth.token;

    if (timestamp > expiresAt) {
      if (!req.nextUrl.pathname.startsWith('/auth')) {
        const url = '/auth/signout';
        return NextResponse.redirect(new URL(url, req.nextUrl));
      }
    }

    // 此处触发多次重定向
    return intlMiddleware(req);
  },
  {
    pages: {
      signIn: '/auth/signin',
      error: '/auth/error',
      signOut: '/auth/signout',
    },
  },
);

export default function middleware(req: NextRequest) {
  return (authMiddleware as any)(req);
}

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

app/layout.tsx

'use client';

import React from 'react';
import { SessionProvider } from 'next-auth/react';
import { BodyStyled } from '@/global/styles';

type RootLayoutTypes = {
  children: React.ReactNode,
  params: any
}

export default function RootLayout({ children, params: { locale } }: RootLayoutTypes) {
  return (
    <html lang={locale}>
      <BodyStyled>
        <SessionProvider>
          {children}
        </SessionProvider>
      </BodyStyled>
    </html>
  );
}

app/[locale]/layout.tsx

import React from 'react';
import { NextIntlClientProvider } from 'next-intl';
import bg from '@/public/locales/bg/common.json';
import en from '@/public/locales/en/common.json';

type RootLayoutTypes = {
  children: React.ReactNode,
  params: any
}

export function generateStaticParams() {
  return [{ locale: 'bg' }, { locale: 'en' }];
}

export default function RootLayout({ children, params: { locale } }: RootLayoutTypes) {
  return (
    <NextIntlClientProvider locale={locale} messages={locale === 'bg' ? bg : en}>
      {children}
    </NextIntlClientProvider>
  );
}

app/api/auth/[...nextauth]/route.ts

/* eslint-disable no-param-reassign */
import NextAuth, { NextAuthOptions } from 'next-auth';
import jwtDecode from 'jwt-decode';

const authOptions: NextAuthOptions = {
  providers: [
    {
      wellKnown: `${process.env.NEXT_PUBLIC_AUTH_AUTHORITY}/.well-known/openid-configuration`,
      authorization: {
        params: { scope: 'openid profile' },
      },
      type: 'oauth',
      id: 'customOauth',
      clientId: process.env.NEXT_PUBLIC_AUTH_CLIENT_ID,
      clientSecret: process.env.NEXT_PUBLIC_AUTH_CLIENT_SECRET,
      name: '',
      checks: ['pkce', 'state'],
      profile(profile) {
        return {
          id: profile.sub,
          permissions: profile.permissions,
        };
      },
    },
  ],
  secret: process.env.NEXTAUTH_SECRET,
  session: {
    strategy: 'jwt',
  },
  callbacks: {
    async redirect() {
      return '/dashboard';
    },
    async jwt({ token, account }) {
      // 登录后将OAuth访问令牌和用户ID持久化到token中
      if (account) {
        const { exp }: any = jwtDecode(account.id_token || '');
        token.accessToken = account.access_token;
        token.idToken = account.id_token;
        token.expiresAt = exp;
      }
      return token;
    },
  },
  pages: {
    signIn: '/auth/signin',
  },
  debug: true,
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST }

app/auth/signin/page.tsx

'use client';

import React, { useEffect } from 'react';
import { signIn, useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';

interface LoginProps {
}

const Login: React.FC<LoginProps> = () => {
  const router = useRouter();
  const { status } = useSession();

  useEffect(() => {
    if (status === 'unauthenticated') {
      const queryString = window.location.search;
      const urlParams = new URLSearchParams(queryString);
      if (urlParams.get('error')) {
        void router.push('/auth/error');
      } else {
        void signIn('customOauth', undefined, { prompt: 'select_account' });
      }
    } else if (status === 'authenticated') {
      void router.push('/dashboard');
    }
  }, [status, router]);

  return null;
};

export default Login;

我遗漏了什么?已经卡了三周,希望能解决这个问题...

内容的提问来源于stack exchange,提问作者Chris Panayotova

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 07:12:53