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

NextJS中如何在用户点击浏览器返回键前弹出确认提示?

NextJS中拦截查询参数导航的确认提示方案

问题背景

在NextJS应用中,通过查询参数isItemDetailsOpen控制模态框的显示/隐藏:设置参数时打开模态框,移除参数时关闭。当模态框内的表单存在未保存内容时,希望用户点击浏览器返回键或尝试导航离开时弹出确认提示,但当前实现中URL会先变更,再弹出确认,即使用户取消也无法恢复原URL状态。

解决方案

要实现先确认再变更URL的效果,需要分两种场景处理:浏览器的前进/后退操作(popstate事件)和NextJS内部的路由跳转。核心思路是在导航触发前拦截,确认后再执行路由变更,取消则保持当前状态。

修改后的代码示例

import {
  Box,
  Button,
  Modal,
  ModalBody,
  ModalContent,
  ModalOverlay,
  ModalProps,
} from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useEffect, useRef, useState } from 'react';

const ItemDetails = (props: Omit<ModalProps, 'children'>) => {
  return (
    <Modal {...props}>
      <ModalOverlay />
      <ModalBody>
        <ModalContent>
          <p>Hello I am an item</p>
          {/* 模拟表单内容 */}
          <input placeholder="输入内容" />
        </ModalContent>
      </ModalBody>
    </Modal>
  );
};

const Page = () => {
  const router = useRouter();
  const { isItemDetailsOpen } = router.query;
  const isOpen = isItemDetailsOpen === 'true';
  // 标记是否为主动关闭模态框,避免拦截合法操作
  const isClosingManually = useRef(false);
  // 模拟表单是否存在未保存内容
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);

  const onClose = () => {
    isClosingManually.current = true;
    const { isItemDetailsOpen, ...restOfQuery } = router.query;
    // 使用shallow:true避免页面重新加载
    router.push({ query: restOfQuery }, undefined, { shallow: true });
    // 重置标记
    setTimeout(() => {
      isClosingManually.current = false;
    }, 100);
  };

  useEffect(() => {
    // 拦截浏览器前进/后退的popstate事件
    const handlePopState = (e: PopStateEvent) => {
      if (isClosingManually.current) return;
      if (isOpen && hasUnsavedChanges) {
        e.preventDefault();
        const confirmLeave = window.confirm(
          '你正在编辑内容,确定要离开并丢失修改吗?'
        );
        if (confirmLeave) {
          // 用户确认后执行路由变更
          const { isItemDetailsOpen, ...restOfQuery } = router.query;
          router.push({ query: restOfQuery }, undefined, { shallow: true });
        } else {
          // 用户取消,恢复当前URL状态
          history.pushState(null, '', window.location.href);
        }
      }
    };

    // 拦截NextJS内部路由跳转
    const handleRouteChangeStart = (url: string) => {
      if (isClosingManually.current) return;
      if (isOpen && hasUnsavedChanges) {
        const confirmLeave = window.confirm(
          '你正在编辑内容,确定要离开并丢失修改吗?'
        );
        if (!confirmLeave) {
          router.events.emit('routeChangeError');
          throw 'Abort route change';
        }
      }
    };

    window.addEventListener('popstate', handlePopState);
    router.events.on('routeChangeStart', handleRouteChangeStart);

    return () => {
      window.removeEventListener('popstate', handlePopState);
      router.events.off('routeChangeStart', handleRouteChangeStart);
    };
  }, [router, isOpen, hasUnsavedChanges]);

  return (
    <Box>
      This is a Page
      <Button
        onClick={() =>
          router.push(
            { query: { ...router.query, isItemDetailsOpen: 'true' } },
            undefined,
            { shallow: true }
          )
        }
      >
        Open Item Details
      </Button>
      <ItemDetails isOpen={isOpen} onClose={onClose} />
      {/* 模拟表单修改状态 */}
      <Button onClick={() => setHasUnsavedChanges(true)}>标记为未保存</Button>
    </Box>
  );
};

export default Page;

关键实现要点

  • popstate事件拦截:浏览器返回/前进操作会触发该事件,这里先阻止默认行为,弹出确认框后再决定是否执行路由变更;用户取消时,用history.pushState恢复当前URL,避免历史记录异常。
  • 主动关闭标记:通过useRef维护isClosingManually状态,避免拦截用户主动点击模态框关闭按钮的合法操作。
  • 浅渲染优化:修改查询参数时添加shallow: true,避免页面重新加载,提升交互体验。
  • 未保存状态判断:通过hasUnsavedChanges状态控制是否触发确认提示,仅在表单有未修改内容时拦截导航。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 16:15:37