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

Next.js中如何实现关闭标签页或路由跳转时的未保存更改提示?

适配Next.js的页面跳转/关闭拦截方案

针对你迁移Next.js时需要实现的两种拦截场景,以下分Pages Router和App Router两种路由系统给出对应实现方案:


1. Pages Router 方案(Next.js 12及以下,或使用Pages Router的13+项目)

基于next/router的路由事件监听,替代react-router的Prompt,同时保留浏览器关闭/刷新的拦截逻辑:

import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';

const useUnsavedChangesWarning = (
  message = "确定要丢弃未保存的更改吗?",
  // 可选:自定义路由拦截条件,比如仅拦截跳转到注册页的场景
  shouldBlockRoute = () => true
) => {
  const [isDirty, setDirty] = useState(false);
  const router = useRouter();

  // 拦截浏览器关闭/刷新
  useEffect(() => {
    const handleBeforeUnload = (e) => {
      if (isDirty) {
        e.preventDefault();
        e.returnValue = message;
        return message;
      }
    };
    window.addEventListener('beforeunload', handleBeforeUnload);
    return () => window.removeEventListener('beforeunload', handleBeforeUnload);
  }, [isDirty, message]);

  // 拦截路由跳转
  useEffect(() => {
    const handleRouteChangeStart = (url) => {
      if (isDirty && shouldBlockRoute(url)) {
        if (!confirm(message)) {
          router.events.emit('routeChangeError');
          throw '路由跳转已取消';
        }
      }
    };
    router.events.on('routeChangeStart', handleRouteChangeStart);
    return () => router.events.off('routeChangeStart', handleRouteChangeStart);
  }, [isDirty, router, message, shouldBlockRoute]);

  return [() => setDirty(true), () => setDirty(false)];
};

export default useUnsavedChangesWarning;

使用示例

  • 表单页面(拦截所有跳转):
import useUnsavedChangesWarning from '../hooks/useUnsavedChangesWarning';

const ApplicationForm = () => {
  const [setDirty, setClean] = useUnsavedChangesWarning();

  // 输入变化时标记为需拦截状态
  const handleInputChange = () => setDirty();

  // 提交后清除拦截状态
  const handleSubmit = async (e) => {
    e.preventDefault();
    // 执行表单提交逻辑
    setClean();
    router.push('/success');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" onChange={handleInputChange} />
      <button type="submit">提交</button>
    </form>
  );
};
  • 仪表盘页面(仅拦截跳转到注册页):
import useUnsavedChangesWarning from '../hooks/useUnsavedChangesWarning';
import { useRouter } from 'next/router';

const Dashboard = () => {
  const router = useRouter();
  const [setDirty] = useUnsavedChangesWarning(
    "你确定要返回注册页吗?确定则登出,取消则留在仪表盘",
    (targetUrl) => targetUrl.includes('/auth/signup')
  );

  // 页面加载时标记为需拦截状态
  useEffect(() => setDirty(), []);

  const handleLogout = () => {
    // 执行登出逻辑(清除登录状态等)
    router.push('/auth/signup');
  };

  return (
    <div>
      <h1>仪表盘</h1>
      <button onClick={handleLogout}>登出</button>
    </div>
  );
};

2. App Router 方案(Next.js 13+ App Router)

App Router需标记组件为客户端组件,通过监听popstate事件处理浏览器后退,结合封装的编程式跳转方法实现拦截:

'use client';
import { useEffect, useState } from 'react';
import { useRouter, usePathname } from 'next/navigation';

const useUnsavedChangesWarning = (
  message = "确定要丢弃未保存的更改吗?",
  shouldBlockRoute = () => true
) => {
  const [isDirty, setDirty] = useState(false);
  const router = useRouter();
  const pathname = usePathname();

  // 拦截浏览器关闭/刷新
  useEffect(() => {
    const handleBeforeUnload = (e) => {
      if (isDirty) {
        e.preventDefault();
        e.returnValue = message;
        return message;
      }
    };
    window.addEventListener('beforeunload', handleBeforeUnload);
    return () => window.removeEventListener('beforeunload', handleBeforeUnload);
  }, [isDirty, message]);

  // 拦截浏览器后退/前进
  useEffect(() => {
    const handlePopState = () => {
      if (isDirty && shouldBlockRoute()) {
        if (!confirm(message)) {
          // 回退到当前页面
          router.push(pathname);
        }
      }
    };
    window.addEventListener('popstate', handlePopState);
    return () => window.removeEventListener('popstate', handlePopState);
  }, [isDirty, router, pathname, message, shouldBlockRoute]);

  // 封装安全跳转方法,拦截编程式路由跳转
  const safePush = (href) => {
    if (isDirty && shouldBlockRoute(href)) {
      if (confirm(message)) router.push(href);
    } else {
      router.push(href);
    }
  };

  return [() => setDirty(true), () => setDirty(false), safePush];
};

export default useUnsavedChangesWarning;

使用示例(仪表盘页面):

'use client';
import useUnsavedChangesWarning from '../hooks/useUnsavedChangesWarning';

const Dashboard = () => {
  const [setDirty, setClean, safePush] = useUnsavedChangesWarning(
    "你确定要返回注册页吗?确定则登出,取消则留在仪表盘",
    (targetHref) => targetHref === '/auth/signup'
  );

  useEffect(() => setDirty(), []);

  const handleLogout = () => {
    // 执行登出逻辑
    safePush('/auth/signup');
  };

  return (
    <div>
      <h1>仪表盘</h1>
      <button onClick={handleLogout}>登出</button>
    </div>
  );
};

关键注意事项

  • App Router下所有使用该hook的组件必须添加'use client'指令。
  • 浏览器的beforeunload提示文本受安全限制,部分浏览器会强制替换为默认文本,无法完全自定义。
  • 仪表盘场景中,若用户确认跳转,需在confirm的回调内先执行登出逻辑,再进行路由跳转。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 18:31:01