Next.js 14+Auth.js v5中间件重定向生效但浏览器URL未更新
问题:Next.js 14 + Auth.js v5 登录后URL未同步更新
环境与配置
使用Next.js v14和Auth.js v5实现认证,配置文件如下:
auth.config.ts
import type { NextAuthConfig } from 'next-auth'; import { NextResponse } from 'next/server'; export const authConfig = { pages: { signIn: '/login', }, callbacks: { authorized({ auth, request: { nextUrl } }) { const isLoggedIn = !!auth?.user; const isProtectedRoute = nextUrl.pathname.startsWith('/boards') || nextUrl.pathname.startsWith('/people'); const callbackUrl = nextUrl.searchParams.get("callbackUrl"); if (isProtectedRoute) { return isLoggedIn; } else if (isLoggedIn) { if (callbackUrl !== null) { return NextResponse.redirect(callbackUrl); } else { return NextResponse.redirect(new URL('/boards', nextUrl)); } } return true; }, }, providers: [], } satisfies NextAuthConfig;
middleware.ts
import NextAuth from 'next-auth'; import { authConfig } from './auth.config'; export default NextAuth(authConfig).auth; export const config = { matcher: ['/boards/:path*', '/people/:path*', '/login'] };
问题现象
未登录时访问受保护路由(如http://localhost:4000/boards),会被重定向到登录页,URL显示为http://localhost:4000/login?callbackUrl=http%3A%2F%2Flocalhost%3A4000%2Fboards;登录后页面渲染/boards的内容,但浏览器URL仍停留在登录页地址,未同步更新。
问题原因
Auth.js的authorized回调设计仅用于授权判断(返回布尔值),若直接返回NextResponse.redirect,只会在服务端完成内容的内部重定向渲染,但不会触发浏览器地址栏的URL更新,导致内容与URL不一致。
解决方案
1. 修正authorized回调逻辑
让authorized仅处理授权判断,跳转逻辑交给Auth.js自动处理:
import type { NextAuthConfig } from 'next-auth'; export const authConfig = { pages: { signIn: '/login', }, callbacks: { authorized({ auth, request: { nextUrl } }) { const isLoggedIn = !!auth?.user; const isProtectedRoute = nextUrl.pathname.startsWith('/boards') || nextUrl.pathname.startsWith('/people'); // 受保护路由:未登录则返回false,Auth.js会自动跳转到登录页并携带callbackUrl if (isProtectedRoute) { return isLoggedIn; } // 已登录访问登录页:返回false,触发Auth.js自动重定向 if (isLoggedIn && nextUrl.pathname === '/login') { return false; } return true; }, }, providers: [], } satisfies NextAuthConfig;
2. 在登录页处理登录后的跳转
根据登录页的组件类型(客户端/服务端),手动处理callbackUrl的跳转:
客户端组件示例
'use client'; import { signIn, useSession } from 'next-auth/react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useEffect } from 'react'; export default function LoginPage() { const { data: session } = useSession(); const router = useRouter(); const searchParams = useSearchParams(); const callbackUrl = searchParams.get('callbackUrl') || '/boards'; // 登录成功后跳转至目标页 useEffect(() => { if (session) { router.push(callbackUrl); } }, [session, router, callbackUrl]); const handleLogin = async () => { await signIn('your-provider', { callbackUrl }); }; return ( <div> <h1>登录</h1> <button onClick={handleLogin}>登录</button> </div> ); }
服务端组件示例
import { getSession, signIn, redirect } from 'next-auth/react'; import { cookies } from 'next/headers'; export default async function LoginPage({ searchParams }: { searchParams: { callbackUrl?: string } }) { const session = await getSession({ cookies }); const callbackUrl = searchParams.callbackUrl || '/boards'; // 已登录直接跳转 if (session) { redirect(callbackUrl); } return ( <div> <h1>登录</h1> <form action={async () => { 'use server'; await signIn('your-provider', { callbackUrl }); }}> <button type="submit">登录</button> </form> </div> ); }
3. 优化Middleware匹配规则
扩大匹配范围,覆盖所有非静态资源路由:
import NextAuth from 'next-auth'; import { authConfig } from './auth.config'; export default NextAuth(authConfig).auth; export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], };
原理说明
Auth.js的authorized回调核心是授权判断:
- 返回
true:允许访问当前路由 - 返回
false:自动跳转到pages.signIn配置页面,并携带当前路由作为callbackUrl - 直接返回
NextResponse.redirect属于服务端内部渲染重定向,不会触发浏览器URL更新,因此需要将跳转逻辑交给客户端或Auth.js的自动处理流程,确保URL与页面内容同步。
内容的提问来源于stack exchange,提问作者mattstuehler
相关产品推荐
相关产品推荐

