Next.js App Router中NextAuth登录动态路由重定向异常求助
问题描述
使用Next.js App Router开发网站,集成NextAuth处理登录功能,用户创建和登录流程正常,但登录后重定向到动态路由/profile/[userName]时遇到以下问题:
- 点击登录按钮后要么默认跳转到
/profile页面 - 使用
router.push(/profile/[${userSlug}]);会跳转到/profile/%5B%5D(方括号被URL编码) - 尝试
router.push(/profile/${String(userSlug)});无效,但直接写死路由router.push("/profile/john")可以正常跳转
相关代码
登录页代码
"use client"; import { useState } from "react"; import { useFormik } from "formik"; import Image from "next/image"; import { signIn, useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; import { redirect } from "next/navigation"; export default function Login({ params }) { const session = useSession(); const { status } = useSession(); let slug; const isAuthenticated = status === "Authenticated"; const userName = session.data?.user?.name; const userSlug = userName && userName.includes(" ") ? userName.split(" ")[0].toLowerCase() : ""; if (isAuthenticated) { return redirect("/"); } const router = useRouter(); const [error, setError] = useState([]); console.log("SESSION: ", session); const handleSubmit = async () => { const result = await signIn("credentials", { email: formik.values.email, password: formik.values.password, redirect: false, }); if (result.error == null) { console.log("fetch", slug); router.push(`/profile/${String(userSlug)}`); } setError(result.error); }; const formik = useFormik({ initialValues: { email: "", password: "", }, onSubmit: handleSubmit, }); return ( <section className="mt-8"> <h1 className="text-center text-4xl mb-4">Login</h1> <form className="flex flex-col max-w-xs mx-auto" onSubmit={formik.handleSubmit} > {error && <div className="text-red-500">{error}</div>} <input type="email" name="email" placeholder="email" value={formik.values.email} onChange={formik.handleChange} /> <input type="password" name="password" placeholder="password" value={formik.values.password} onChange={formik.handleChange} /> <button type="submit">Login</button> <div className="my-4 text-center text-gray-500"> or login with provider </div> <button type="button" alt="google logo" onClick={() => signIn("google", { callbackUrl: "/" })} className="flex gap-4 justify-center" > <Image src="/google.png" alt="" width={24} height={24} /> Login with google </button> </form> </section> ); }
Auth路由代码
import GoogleProvider from "next-auth/providers/google"; import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import bcrypt from "bcrypt"; import { PrismaAdapter } from "@auth/prisma-adapter"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); const handler = NextAuth({ providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, }), CredentialsProvider({ name: "credentials", credentials: { name: { label: "Name", type: "text" }, email: { label: "Email", type: "email", placeholder: "test@example.com", }, }, async authorize(credentials, req) { if ( !credentials.email || !credentials.password || credentials.email === "" || credentials.password === "" ) { throw new Error("Email and password are required"); } const user = await prisma.user.findUnique({ where: { email: credentials.email, }, }); if (!user) { throw new Error("User not found"); } const passwordMatch = await bcrypt.compare( credentials.password, user.password ); if (!passwordMatch) { throw new Error("Incorrect password"); } return user; }, }), ], session: { strategy: "jwt", }, secret: process.env.NEXTAUTH_SECRET, adapter: PrismaAdapter(prisma), }); export { handler as GET, handler as POST };
原因分析
- Session未及时更新:调用
signIn成功后,useSession的缓存不会立即刷新,此时session.data?.user?.name仍为空,导致userSlug是空字符串,最终router.push变成/profile/,被默认重定向到/profile。 - 动态路由结构错误:你在
pages目录下创建[userName]文件夹,但App Router的路由文件必须放在app目录下,pages目录属于Pages Router的结构,会导致路由无法匹配。 - userSlug逻辑有漏洞:当用户名不含空格时,
userSlug会被设置为空字符串,导致路由不完整。
修复方案
1. 调整动态路由目录结构
将pages/profile/[userName]/page.jsx移动到app/profile/[userName]/page.jsx,确保符合App Router的路由规则。
2. 登录成功后获取最新用户信息跳转
方案A:直接从signIn返回结果取用户信息
修改handleSubmit函数,使用signIn返回的result.user获取用户名,避免依赖未更新的session:
const handleSubmit = async () => { const result = await signIn("credentials", { email: formik.values.email, password: formik.values.password, redirect: false, }); if (result.error == null) { const userName = result.user?.name; // 优化userSlug生成逻辑,处理无空格的情况 const userSlug = userName ? userName.includes(" ") ? userName.split(" ")[0].toLowerCase() : userName.toLowerCase() : "default"; // 兜底默认值 router.push(`/profile/${userSlug}`); } setError(result.error); };
方案B:强制刷新session后跳转
如果需要依赖session数据,调用refetch方法刷新session后再跳转:
// 修改useSession的调用方式,获取refetch方法 const { data: session, refetch } = useSession(); const handleSubmit = async () => { const result = await signIn("credentials", { email: formik.values.email, password: formik.values.password, redirect: false, }); if (result.error == null) { await refetch(); // 强制刷新session const userName = session?.user?.name; const userSlug = userName ? userName.includes(" ") ? userName.split(" ")[0].toLowerCase() : userName.toLowerCase() : "default"; router.push(`/profile/${userSlug}`); } setError(result.error); };
3. 优化CredentialsProvider的返回值
确保authorize函数返回的用户对象包含name字段,这样signIn的结果中才能正确获取用户名:
async authorize(credentials, req) { // ...现有验证逻辑 // 明确返回需要的字段 return { id: user.id, name: user.name, email: user.email, // 其他必要字段 }; }
4. 处理Google登录的重定向
对于Google登录,修改为动态跳转逻辑:
<button type="button" alt="google logo" onClick={async () => { const session = await signIn("google", { redirect: false }); const userName = session.user?.name; const userSlug = userName ? userName.includes(" ") ? userName.split(" ")[0].toLowerCase() : userName.toLowerCase() : "default"; router.push(`/profile/${userSlug}`); }} className="flex gap-4 justify-center" > <Image src="/google.png" alt="" width={24} height={24} /> Login with google </button>
内容的提问来源于stack exchange,提问作者Josoa
相关产品推荐
相关产品推荐

