NextJS13应用登录页数据库凭证校验故障求助
问题分析与修复方案
先明确代码里的核心问题:
1. 客户端登录逻辑错误
你当前通过response.ok判断是否登录成功,但服务端无论校验成功与否都会返回200状态码(NextResponse.json默认状态码200),所以不管密码对错,都会执行跳转。必须解析服务端返回的authorized字段来判断结果。
2. 错误提示逻辑混淆
react-hook-form的errors是用于前端表单校验(比如必填、长度不符合要求),但你把“邮箱未注册”“密码错误”这类后端返回的业务错误绑定到了表单校验错误上,导致提示逻辑混乱。
3. 服务端错误响应不规范
NextResponse.error()的用法不正确,应该返回带有错误信息和对应状态码的JSON响应,方便客户端处理。
4. 密码校验规则不一致
密码输入框的placeholder提示“至少8字符”,但react-hook-form的校验规则是minLength:4,两者不匹配。
修复后的服务端代码(/api/checkuser/route.js)
import { NextResponse } from 'next/server'; import { PrismaClient } from "@prisma/client"; import bcrypt from "bcrypt"; const prisma = new PrismaClient(); async function checkUserCredentials(email, password) { try { const user = await prisma.user.findUnique({ where: { email }, }); if (!user) { return { valid: false, reason: "email_not_found" }; } const passwordMatch = await bcrypt.compare(password, user.password); return passwordMatch ? { valid: true } : { valid: false, reason: "wrong_password" }; } catch (error) { console.error("Error checking user credentials", error); throw error; } } export async function POST(request) { try { const { email, password } = await request.json(); const result = await checkUserCredentials(email, password); if (result.valid) { return NextResponse.json({ authorized: true }); } else { // 返回401状态码表示未授权 return NextResponse.json({ authorized: false, reason: result.reason }, { status: 401 }); } } catch (error) { return NextResponse.json( { error: "服务器内部错误" }, { status: 500 } ); } }
修复后的客户端登录表单代码
"use client" import { useForm } from "react-hook-form"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { useState } from "react"; export default function LogIn() { const { register, reset, formState: { errors }, handleSubmit } = useForm(); const router = useRouter(); // 新增状态存储后端返回的业务错误 const [loginError, setLoginError] = useState(""); const onSubmit = async (data) => { setLoginError(""); // 清空之前的错误 try { const response = await fetch("/api/checkuser", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data), }); const result = await response.json(); if (response.ok && result.authorized) { router.push("/"); reset(); } else { // 根据后端返回的错误原因设置提示 switch(result.reason) { case "email_not_found": setLoginError("该邮箱未注册"); break; case "wrong_password": setLoginError("密码错误,请重新输入"); break; default: setLoginError("登录失败,请稍后重试"); } } } catch (error) { console.error("登录请求出错:", error); setLoginError("网络异常,请检查连接"); } }; return ( <div> <h1 className="signUp">登录</h1> {/* 显示后端返回的错误 */} {loginError && <p className="errorMessage" role="alert">{loginError}</p>} <form onSubmit={handleSubmit(onSubmit)} className="signUpForm"> <input {...register("email", { required: "请输入邮箱", pattern: { value: /^\S+@\S+$/i, message: "请输入有效的邮箱格式" } })} type="email" id="email" placeholder="邮箱地址" aria-invalid={errors.email ? "true" : "false"} /> {/* 显示前端表单校验的错误 */} {errors.email && <p className="errorMessage" role="alert">{errors.email.message}</p>} <input {...register("password", { required: "请输入密码", minLength: { value: 8, message: "密码长度不能少于8位" } })} type="password" // 修复:添加密码输入类型,避免明文显示 id="password" placeholder="密码长度不能少于8位" aria-invalid={errors.password ? "true" : "false"} /> {/* 显示前端表单校验的错误 */} {errors.password && <p className="errorMessage" role="alert">{errors.password.message}</p>} <button type="submit" className="signSubmit">提交</button> <p className="alreadyText">还没有账号?<Link href="/sign-up"><strong>立即注册</strong></Link></p> </form> </div> ); }
额外优化点
- 密码输入框添加
type="password",避免明文显示密码; - 前端表单校验补充邮箱格式校验,统一密码长度为8位,和提示保持一致;
- 新增
loginError状态专门处理后端返回的业务错误,与前端表单校验错误区分,提示更准确; - 服务端返回符合HTTP规范的状态码(401表示未授权,500表示服务器错误),便于客户端识别错误类型。
内容的提问来源于stack exchange,提问作者user19102549
相关产品推荐
相关产品推荐

