Next.js 13.2.4中next-auth 4.22.0的SignIn函数JSON解析错误求助
我在项目中使用Next.js 13.2.4,采用next-auth 4.22.0实现身份验证。提交登录表单时,调用signIn函数出现如下错误:
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
相关代码
/api/auth/[...nextauth]/route.js
import { PrismaClient } from "@prisma/client"; import { compare } from "bcryptjs"; import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; const prisma = new PrismaClient(); export const authOptions = { session: { strategy: "jwt", }, providers: [ CredentialsProvider({ type: "credentials", id: "login_id", name: "Sign In", async authorize(credentials) { const user = { id: "1", password: "hicham", email: "test@gmail.com", name: "hicham zak", }; if (!credentials?.email || !credentials.password) { return null; } // test if (!user || credentials.password !== user.password) { return null; } return { id: user.id, email: user.email, name: user.name, randomKey: "Hey cool", }; }, }), ], jwt: ({ token, user }) => { console.log("JWT Callback", { token, user }); if (user) { const u = user; return { ...token, id: u.id, randomKey: u.randomKey, }; } return token; }, }, pages: { signIn: "/login", }, }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
LoginForm.jsx
"use client"; import Label from "../ui/Label"; import Input from "../ui/Input"; import Link from "next/link"; import Btn from "../ui/Btn"; import { useState } from "react"; import ShowHidePass from "../ShowHidePass"; import { useFormik } from "formik"; import { loginValidate } from "../../lib/validate"; import { signIn } from "next-auth/react"; import { useSearchParams, useRouter } from "next/navigation"; import Alert from "../ui/Alert"; export const contentType = "application/json"; const Form = ({ btn }) => { // this for formik const formik = useFormik({ initialValues: { email: "", password: "", }, validate: loginValidate, onSubmit, }); const router = useRouter(); const searchParams = useSearchParams(); const callbackUrl = searchParams.get("callbackUrl") || "/home"; // states const [error, setError] = useState(""); // this for hide and show the password const [showPass, setShowPass] = useState(false); function handelShowPass(e) { e.preventDefault(); setShowPass(!showPass); } // this function excute in submission async function onSubmit(values) { try { const result = await signIn("login_id", { email: "test@gmail.com", password: "test123455", redirect: false, callbackUrl, }); console.log(result); if (!result?.error) router.push(callbackUrl); else setError("Invalid email or password"); } catch (err) { console.log(err); } } return ( <form method="post" className="space-y-8 sm1:w-[400px]" onSubmit={formik.handleSubmit} > <div className="grid w-full items-center gap-1.5"> <Label htmlFor="email" text="Email" /> <Input name="email" type="email" placeholder="Enter your email" formik={formik.getFieldProps("email")} style={` ${formik.errors.email && formik.touched.email ? "border-red-600 " : ""} hover:border-b-primary hover:border-b-2 hover:rounded-none hover:rounded-b-md `} /> </div> <div className="grid w-full items-center gap-1.5"> <Label htmlFor="pass" text="Password" /> <Input name="password" type={showPass ? "text" : "password"} placeholder="Enter your Password" children={ <ShowHidePass showPass={showPass} handelShowPass={handelShowPass} /> } formik={formik.getFieldProps("password")} style={` ${formik.errors.password && formik.touched.password ? "border-red-600 " : ""} hover:border-b-primary hover:border-b-2 hover:rounded-none hover:rounded-b-md `} /> </div> {error && <Alert children={error} />} <div className="w-full text-center pb-2 font-semibold text-sm"> <Link href="/forgot-password" className="text-indigo-500"> Forgot password? </Link> </div> <div className="w-full"> <Btn btnText={btn} type="submit" /> </div> <div className="w-full text-center pb-2 font-semibold text-sm"> <span>Create an account? </span> <Link href="/signUp" className="text-indigo-500 hover:underline"> Sign Up </Link> </div> </form> ); }; export default Form;
登录页面
import Form from "../../../../components/form/LoginForm"; import Logo from "../../../../components/Logo"; const Login = () => { return ( <div className="h-screen w-screen sm1:flex sm1:justify-center sm1:items-center "> <div className="sm1:shadow-xl bg-slate-200 sm1:bg-white sm1:rounded-lg px-8 pt-12 pb-8 space-y-12"> <Logo /> <h1 className="text-2xl my-8 font-semibold">Login to your account</h1> <Form btn="Login" /> </div> </div> ); }; export default Login;
问题原因及解决方案
这个错误是因为NextAuth的API路由返回了HTML页面(通常是错误页面)而非预期的JSON响应,以下是具体问题和修复步骤:
1. 修复NextAuth路由的语法错误
查看/api/auth/[...nextauth]/route.js代码,发现jwt配置的括号存在语法错误:jwt属性前多了一个闭合大括号,导致authOptions对象结构异常,服务器返回500错误页面(HTML)。
修改后的authOptions结构:
export const authOptions = { session: { strategy: "jwt", }, providers: [ // 保持原有provider代码不变 ], // 修正:jwt直接作为authOptions的属性,移除多余的闭合括号 jwt: ({ token, user }) => { console.log("JWT Callback", { token, user }); if (user) { const u = user; return { ...token, id: u.id, randomKey: u.randomKey, }; } return token; }, pages: { signIn: "/login", }, };
2. 修正登录凭证不匹配问题
authorize函数中硬编码的用户密码是"hicham",但signIn调用中传入的密码是"test123455",这会导致authorize返回null,触发NextAuth重定向到登录页面(HTML)。
修改onSubmit中的密码为正确值:
async function onSubmit(values) { try { const result = await signIn("login_id", { email: "test@gmail.com", password: "hicham", // 改为和authorize中一致的密码 redirect: false, callbackUrl, }); // 保持后续代码不变 } catch (err) { console.log(err); } }
3. 移除表单的method="post"属性
客户端组件中使用signIn函数时,不需要给form标签设置method="post",否则会触发默认表单提交行为,导致请求错误。
修改LoginForm中的form标签:
<form className="space-y-8 sm1:w-[400px]" onSubmit={formik.handleSubmit} >
4. 验证NextAuth路由路径是否正确
确保NextAuth的API路由文件放在app/api/auth/[...nextauth]/route.js(App Router结构),如果是Pages Router则路径应为pages/api/auth/[...nextauth].js,路径错误会返回404页面(HTML)。
完成以上修改后,重启开发服务器,登录请求会返回正确的JSON响应,解决解析错误。
内容的提问来源于stack exchange,提问作者Hicham Zakroum

