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

Next.js登录页preventDefault无法阻止刷新及水合报错问题

问题诊断与修复方案

一、表单提交无法阻止页面刷新的问题

根本原因

  • 事件绑定目标完全错误:你把id="searchForm"绑在了外层普通div上,原生submit事件只会在<form>元素上触发,给div绑定submit监听没有任何作用
  • 回车监听逻辑完全失效:你把回车判断写在了onChange回调里,onChange只会在输入值变化时触发,事件对象里根本没有key字段,e.key === "Enter"的判断永远不会成立。当输入框聚焦时按回车,浏览器会自动触发表单默认提交,你写的拦截逻辑根本没执行
  • 事件绑定方式违反React/Next.js渲染逻辑:你用原生addEventListener手动绑事件的写法属于反模式,组件每次重渲染都会重复绑定监听,还会因为闭包拿到过期的状态值,而且你监听的还是div元素的事件,从根源上就监听不到表单提交
  • 按钮类型配置错误:你给登录按钮设置的type="button"本身不会触发提交,但浏览器在输入框收到回车事件时,会自动查找表单内第一个可提交按钮触发默认行为,表单内没有对应submit按钮时,就会直接触发整页刷新。

修复步骤

  1. 移除外层div上的searchForm id,删除所有用原生addEventListener绑定事件的代码,包括那段用useEffect存document的冗余逻辑——客户端组件里只要在事件回调、useEffect里访问document就不会有SSR问题,不需要单独存进state
  2. 直接给<form>标签绑定React的onSubmit事件,把登录逻辑移到这个提交处理函数里,函数第一行加e.preventDefault()就能完全阻止默认刷新,不需要到处写e.stopPropagation()
  3. 删掉onChange回调里所有和键盘事件、按钮点击触发相关的代码,onChange只负责更新输入的email值即可
  4. 把登录按钮的type从button改成submit,让按钮成为表单默认的提交触发点,回车和点击按钮都会走统一的onSubmit逻辑

二、Suspense边界外的水合错误

根本原因

这个错误和第三方库无关,是你写的JSX不符合Next.js渲染规则导致的:

  • 你在页面组件里直接写了原生<head>标签,Next.js服务端渲染和客户端hydrate的时候,原生head标签会被浏览器自动移到文档头部,造成前后端DOM结构不匹配,直接触发水合错误,严重时就会导致整棵根树切换为客户端渲染
  • 额外的语法问题:JSX里label标签的for属性需要写成htmlFor,直接写for也会触发DOM属性不匹配的警告,加重水合问题。

修复步骤

  1. 从next/head引入Next.js内置的Head组件,替换掉你写的原生<head>标签,title内容放到这个内置组件里
  2. 把label标签上的for="username"改成htmlFor="username",符合JSX语法规范

修复后的核心代码参考

import { useState } from "react";
import IsEmail from "isemail";
import { useRouter } from "next/router";
import Head from "next/head";
import m from "../library/magic-client";

const Login = () => {
  const [userMsg, setUserMsg] = useState("");
  const [email, setEmail] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const router = useRouter();

  const handleOnChangeEmail = (e) => {
    setUserMsg("");
    setEmail(e.target.value);
  };

  const handleLoginSubmit = async (e) => {
    e.preventDefault();
    if (email) {
      if (IsEmail.validate(email)) {
        try {
          setIsLoading(true);
          const didToken = await m.auth.loginWithMagicLink({ email });
          if (didToken) {
            setIsLoading(false);
            router.push("/");
          }
        } catch {
          console.error("something went wrong logging in");
          setIsLoading(false);
        }
      } else {
        setUserMsg("Enter a valid email address");
      }
    } else {
      setUserMsg("Enter an email address");
    }
  };

  return (
    <div className="bg-[url('/static/bglg.jpg')] flex items-stretch flex-col h-screen w-full">
      <Head>
        <title>SiteName SignIn</title>
      </Head>
      <header className="text-4xl px-10 py-2 font-black">
        <span className="text-indigo-700">SiteName</span>
      </header>
      <div className="w-full max-w-xs m-auto bg-[#C9C9C9] rounded p-5 bg-opacity-50 border-gray-200">
        <header>
          <div className="text-indigo-700 font-black text-3xl py-2">
            <p>{isLoading ? "Loading" : "Sign In"}</p>
          </div>
        </header>
        <form className="py-5" onSubmit={handleLoginSubmit}>
          <div>
            <label className="block mb-2 text-indigo-500" htmlFor="username">
              Email
            </label>
            <input
              className="w-full p-2 mb-6 text-indigo-700 border-b-2 border-indigo-500 outline-none focus:bg-gray-300"
              type="text"
              name="username"
              id="username"
              placeholder="Email Address"
              onChange={handleOnChangeEmail}
              value={email}
            />
            <div className="block mb-2 text-red-700">
              <p>{userMsg}</p>
            </div>
          </div>
          <div>
            <input
              className="w-full bg-indigo-700 hover:bg-pink-700 text-white font-bold py-2 px-4 mb-6 rounded"
              type="submit"
              value={isLoading ? "Loading" : "Sign In"}
              disabled={isLoading}
            />
          </div>
        </form>
      </div>
    </div>
  );
};

export default Login;

额外优化:给输入框加了受控的value属性绑定,给提交按钮加了loading态的disabled属性,避免重复提交。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 00:36:19