Frontend Development 11 min read

Intercepting Browser Navigation Events (Back/Forward, Refresh/Close, and Route Changes) in Vue Applications

This article explains how to prevent loss of unsaved video data by intercepting browser back/forward actions, page refresh or close events, and Vue router navigation using the History API, beforeunload listener, and navigation guards, with complete Vue code examples.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Intercepting Browser Navigation Events (Back/Forward, Refresh/Close, and Route Changes) in Vue Applications

The author shares a practical problem: a page that records video stores the data in memory until the user saves it, so navigating away (back/forward, refresh, close, or route change) would discard the video, requiring interception of these actions.

Requirements : intercept (1) browser back/forward, (2) tab refresh and close, (3) Vue router navigation.

1. Intercept Browser Back/Forward : Since browsers block direct JS interception, a "trick" using history.pushState creates a dummy history entry so that a popstate event fires on back navigation. The handler shows a modal, and if the user confirms, history.go(-2) skips the dummy entries.

import { onUnmounted } from 'vue'

interface IBrowserInterceptEvents {
  popstate?: (next: () => void) => void // 监听浏览器前进后退
}

// 作用:添加一个历史记录,以便后续模拟拦截后退
function addStopHistory() {
  const state = { id: 'stopBack' }
  if (history.state.id === 'stopBack') return
  history.pushState(state, '', window.location.href)
}

const useBrowserInterceptor = (events: IBrowserInterceptEvents) => {
  const { popstate } = events
  let popstateCallback: EventListener | undefined

  let isHistoryBack = false
  // 拦截浏览器后退
  if (popstate) {
    addStopHistory()
    popstateCallback = () => {
      addStopHistory()
      popstate(() => {
        isHistoryBack = true
        history.go(-2)
      })
    }
    window.addEventListener('popstate', popstateCallback)
  }

  // 销毁事件
  onUnmounted(() => {
    // 不是历史后退触发的,仅仅是组件卸载,才需要清除模拟拦截后退时添加的历史记录
    if (popstate && !isHistoryBack) {
      history.go(-1)
    }
    popstateCallback && window.removeEventListener('popstate', popstateCallback)
  })
}

export default useBrowserInterceptor

Usage :

// 使用拦截
useBrowserInterceptor({
  popstate: showWarnModal,
})

// 弹窗提示
const showWarnModal = (next: any) => {
  const { pending, uploading, failed } = taskStatusMap.value
  if (pending + uploading + failed > 0) {
    Modal.confirm({
      title: h('h3', '当前页面有未完成的任务!'),
      width: 500,
      content: h('div', null, [
        taskStatusMap.value.pending
          ? h(Tag, { color: 'default' }, `待上传:${taskStatusMap.value.pending}`)
          : null,
        taskStatusMap.value.uploading
          ? h(Tag, { color: 'processing' }, `上传中:${taskStatusMap.value.uploading}`)
          : null,
        taskStatusMap.value.failed
          ? h(Tag, { color: 'error' }, `上传失败:${taskStatusMap.value.failed}`)
          : null,
        h(
          'div',
          { style: { marginTop: '10px' } },
          '此操作会导致未完成上传的视频数据丢失,确定要继续吗?'
        )
      ]),
      onOk() {
        next()
      }
    })
  } else {
    next()
  }
}

2. Intercept Tab Refresh and Close : Listen to the beforeunload event and prevent the default action. Browsers only allow the default confirmation dialog, not custom text.

import { onUnmounted } from 'vue'

interface IBrowserInterceptEvents {
  popstate?: (next: () => void) => void // 监听浏览器前进后退
  beforeunload?: EventListener // 监听标签页刷新和关闭
}

// addStopHistory ...

const useBrowserInterceptor = (events: IBrowserInterceptEvents) => {
  const { popstate, beforeunload } = events
  let popstateCallback: EventListener | undefined
  let beforeunloadCallback: EventListener | undefined

  let isHistoryBack = false
  // 拦截浏览器后退 ...

  // 拦截标签页关闭和刷新
  if (beforeunload) {
    beforeunloadCallback = (event) => {
      if (!isHistoryBack) beforeunload(event)
    }
    window.addEventListener('beforeunload', beforeunloadCallback)
  }

  // 销毁事件
  onUnmounted(() => {
    // 不是后退且不是导航守卫触发的,仅仅是组件卸载,才需要清除模拟拦截后退时添加的历史记录
    if (popstate && !isHistoryBack) {
      history.go(-1)
    }
    popstateCallback && window.removeEventListener('popstate', popstateCallback)
    beforeunloadCallback && window.removeEventListener('beforeunload', beforeunloadCallback)
  })
}

export default useBrowserInterceptor

Usage example adds both popstate and beforeunload handlers to show a warning when there are pending uploads.

useBrowserInterceptor({
  popstate: showWarnModal,
  beforeunload: (e) => {
    const { pending, uploading, failed } = taskStatusMap.value
    if (pending + uploading + failed > 0) {
      e.preventDefault()
      e.returnValue = false
    }
  }
})

3. Intercept Route Navigation (Full Version) : Use Vue Router's onBeforeRouteLeave guard together with the previous mechanisms. An isRouter flag distinguishes route navigation from history back.

import { onUnmounted } from 'vue'
import { type NavigationGuardNext, onBeforeRouteLeave } from 'vue-router'

interface IBrowserInterceptEvents {
  popstate?: (next: () => void) => void // 监听浏览器前进后退
  beforeunload?: EventListener // 监听标签页刷新和关闭
  beforeRouteLeave?: (next: NavigationGuardNext) => void // 导航守卫
}

// 作用:添加一个历史记录,以便后续模拟拦截后退
function addStopHistory() {
  const state = { id: 'stopBack' }
  if (history.state.id === 'stopBack') return
  history.pushState(state, '', window.location.href)
}

const useBrowserInterceptor = (events: IBrowserInterceptEvents) => {
  const { popstate, beforeunload, beforeRouteLeave } = events
  let popstateCallback: EventListener | undefined
  let beforeunloadCallback: EventListener | undefined

  let isHistoryBack = false
  let isRouter = false
  // 拦截浏览器后退
  if (popstate) {
    addStopHistory()
    popstateCallback = () => {
      addStopHistory()
      popstate(() => {
        isHistoryBack = true
        history.go(-2)
      })
    }
    window.addEventListener('popstate', popstateCallback)
  }

  // 拦截标签页关闭和刷新
  if (beforeunload) {
    beforeunloadCallback = (event) => {
      if (!isHistoryBack) beforeunload(event)
    }
    window.addEventListener('beforeunload', beforeunloadCallback)
  }

  // 导航守卫
  beforeRouteLeave &&
    onBeforeRouteLeave((_to, _from, next) => {
      if (isHistoryBack) {
        next()
        return
      }
      beforeRouteLeave(() => {
        isRouter = true
        next()
      })
    })

  // 销毁事件
  onUnmounted(() => {
    // 不是后退且不是导航守卫触发的,仅仅是组件卸载,才需要清除模拟拦截后退时添加的历史记录
    if (popstate && !isHistoryBack && !isRouter) {
      history.go(-1)
    }
    popstateCallback && window.removeEventListener('popstate', popstateCallback)
    beforeunloadCallback && window.removeEventListener('beforeunload', beforeunloadCallback)
  })
}

export default useBrowserInterceptor

Usage combines all three handlers to protect unsaved video uploads across navigation types.

// 使用拦截
useBrowserInterceptor({
  beforeRouteLeave: showWarnModal,
  popstate: showWarnModal,
  beforeunload: (e) => {
    const { pending, uploading, failed } = taskStatusMap.value
    if (pending + uploading + failed > 0) {
      e.preventDefault()
      e.returnValue = false
    }
  }
})

// 弹窗提示同前文

Conclusion : By intercepting user actions such as refresh, tab close, browser back/forward, and route changes, developers can present friendly warnings and prevent accidental loss of in‑progress video uploads, thereby improving user experience.

References : Articles on History API, beforeunload handling, and Vue router navigation guards.

frontendVueHistory APIBrowser InterceptionNavigation Guard
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.