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
相关产品推荐
相关产品推荐

