跪拜 Guibai
← All articles
Frontend

How to Log Out on Tab Close but Not on Refresh Using the pageswap Event

By 用户6296059324705 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Logging out on tab close while preserving the session on refresh is a common requirement in enterprise web apps, and most existing solutions are unreliable guesses. The `pageswap` event provides the first browser-native signal to make this distinction correctly, but the event order is unintuitive and easy to get wrong.

Summary

For years, frontend developers have tried to tell whether a user is closing a tab or refreshing the page, mostly through fragile heuristics like mouse coordinates or timing gaps. Those guesses fail because the browser withholds the user's intent from the page. The `pageswap` event changes that: its `activation.navigationType` returns `"reload"` for refreshes, providing a direct signal. The catch is event ordering. `beforeunload` fires first to show the native confirmation dialog, but `pageswap` only fires after the user confirms. Any decision to log out must wait for `pagehide`, where the reload flag is finally available. A working implementation uses three listeners: `beforeunload` to trigger the dialog, `pageswap` to capture the navigation type, and `pagehide` to conditionally send a `keepalive` logout request only when the tab is actually closing.

Takeaways
Custom HTML dialogs cannot intercept tab close or refresh; only the browser's native `beforeunload` dialog is allowed.
`beforeunload` fires before `pageswap`, so the navigation type is not yet known when the dialog appears.
`pageswap` fires after the user confirms the `beforeunload` dialog, and its `activation.navigationType` property returns `"reload"` for refreshes.
The logout decision must be made in `pagehide`, not `beforeunload`, because only then is the reload flag available.
A logout request sent during page destruction should use `fetch` with `keepalive: true` to ensure delivery.
Browser crashes and forced process kills still bypass all frontend events; backend heartbeats or session timeouts remain necessary for high-assurance systems.
Conclusions

The core difficulty is not a missing API but a timing problem: `beforeunload` asks the question before the browser supplies the answer.

The fact that Chrome's native dialog text differs between refresh and close was the clue that the browser internally knows the operation type, even though older APIs never exposed it.

Many widely circulated solutions — time deltas, mouse position, PerformanceNavigationTiming — all read past state rather than current intent, making them fundamentally unreliable for this use case.

The `pageswap` event is part of the newer View Transitions API surface and is not yet universally supported, so a compatibility check is mandatory.

Concepts & terms
pageswap event
A browser event fired during a same-document or cross-document navigation, part of the View Transitions API. Its `activation.navigationType` property reveals whether the navigation is a push, replace, reload, or traverse.
keepalive
A flag on the Fetch API that allows a request to outlive the page that initiated it, useful for sending analytics or logout calls during page unload when the browser would otherwise cancel the request.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗