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

Next.js <Link>组件不触发beforeunload事件,如何实现离开页面前用户提示?

问题根因

Next.js 的 <Link> 组件走的是客户端路由跳转,不会触发浏览器原生的页面卸载流程,所以仅在整页刷新/跳转时才会触发的 beforeunload 事件自然不会生效。普通 <a> 标签走的是浏览器原生跳转,属于整页加载卸载流程,所以可以正常触发该事件。

解决方案

方案1:监听路由变化事件手动拦截(最通用)

Pages 路由(Next.js 12及更早版本)写法

在需要拦截离开的页面组件内,通过useEffect监听路由变化事件,匹配拦截条件时中断跳转即可,同时保留beforeunload监听处理刷新、关闭标签页的原生场景:

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

export default function EventDetail() {
  const router = useRouter()
  // 自定义拦截条件,例如表单未提交时设为true
  const [shouldBlockLeave, setShouldBlockLeave] = useState(true)

  useEffect(() => {
    // 拦截客户端路由跳转
    const handleRouteChange = (url) => {
      if (!shouldBlockLeave) return
      // 中断路由跳转流程
      router.events.emit('routeChangeError')
      // 抛出异常终止Next.js内部路由逻辑执行
      throw 'routeChange aborted'
    }
    router.events.on('routeChangeStart', handleRouteChange)

    // 拦截原生页面卸载场景
    const handleBeforeUnload = (e) => {
      if (shouldBlockLeave) {
        e.preventDefault()
        e.returnValue = ''
        return ''
      }
    }
    window.addEventListener('beforeunload', handleBeforeUnload)

    // 组件卸载时清理监听,避免影响其他页面
    return () => {
      router.events.off('routeChangeStart', handleRouteChange)
      window.removeEventListener('beforeunload', handleBeforeUnload)
    }
  }, [router, shouldBlockLeave])

  return <div>活动详情页内容</div>
}

App 路由(Next.js 13+)写法

App路由没有内置路由事件,可通过监听popstate事件处理返回按钮跳转,配合官方useBeforeUnload钩子处理原生卸载场景:

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

export default function EventDetail() {
  const [shouldBlockLeave, setShouldBlockLeave] = useState(true)
  const router = useRouter()

  // 处理刷新、关闭标签页等原生卸载场景
  useBeforeUnload((e) => {
    if (shouldBlockLeave) {
      e.preventDefault()
      e.returnValue = ''
    }
  })

  // 拦截客户端路由跳转,包括返回按钮点击
  useEffect(() => {
    const handlePopState = () => {
      if (shouldBlockLeave) {
        // 恢复历史栈,避免地址栏地址提前变化
        window.history.pushState(null, document.title, window.location.href)
        if (confirm('确定要离开吗?未保存的内容将会丢失')) {
          router.back()
        }
      }
    }
    window.addEventListener('popstate', handlePopState)
    return () => window.removeEventListener('popstate', handlePopState)
  }, [shouldBlockLeave, router])

  return <div>活动详情页内容</div>
}

方案2:封装自定义Link组件(针对主动点击链接跳转场景)

如果只需拦截当前页面内点击Link的跳转行为,可直接封装带拦截逻辑的自定义Link组件:

import Link from 'next/link'
import { useRouter } from 'next/router'

export default function BlockableLink({ href, children, shouldBlock }) {
  const router = useRouter()
  const handleClick = (e) => {
    if (shouldBlock) {
      e.preventDefault()
      if (confirm('确定要离开当前页面吗?')) {
        router.push(href)
      }
    }
  }
  return <Link href={href} onClick={handleClick}>{children}</Link>
}

使用时替换原有<Link>组件,传入拦截状态参数即可。

注意事项

  • 现代浏览器大多不支持自定义beforeunload事件的提示文案,只会显示浏览器默认的提示内容
  • 所有事件监听必须在组件卸载时清理,避免造成内存泄漏、其他页面路由被异常拦截

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 17:06:07