Detecting when the browser's "back" and "forward" buttons are pressed.

Table of Contents
-
- 3.1. Back
- 3.2. Forward
- 3.3. New Page Transition
- 3.4. None of the above
Introduction
How can we detect when a page transition occurs using the browser's "back" and "forward" buttons?
While there is a method using the popstate
event 1, popstate
does not occur during normal page transitions (moving to a different page). It is only meaningful when managing history with pushState
within the same page. When moving to a different page, the previous page is destroyed, and thus all event handlers, including popstate
, are also destroyed. Furthermore, information to determine whether it was a "back" or "forward" action is not provided.
There is also a method using performance.navigation.type
2. This can be used for transitions to different pages, but if HTML caching is disabled, the type
value will always be 1: TYPE_RELOAD
, making it indistinguishable from a normal page transition. Even when caching is enabled, the type
value for "back" and "forward" is 2: TYPE_BACK_FORWARD
, so, like popstate
, no information is provided to determine whether it was a "back" or "forward" action. Furthermore, performance.navigation.type
has been deprecated, so browsers may stop supporting it in the future.
Here, we introduce a method that combines sessionStorage
and history.state
3.
The differences between each method are as follows.
Method | Back/Forward Detection | When Cache is Disabled | Notes |
---|---|---|---|
popstate |
✓ | ✗ 4 | For SPAs |
performance.navigation.type |
✓ (Old spec) | ✗ (Always reload) | Deprecated |
sessionStorage + history.state |
◎ | ◎ | For normal page transitions |
Code
- Normal page transition
- Transition by back button
- Transition by forward button
- None of the above
The code to determine these is as follows.
window.addEventListener('pageshow', function () {
// Get the timestamp from sessionStorage before the transition
const prevTimestamp = Number(sessionStorage.getItem('prevTimestamp') ?? -1);
// Get the timestamp saved in history.state
let currentTimestamp = history.state?.timestamp ?? null;
// Determine if it's a new page transition
if (!currentTimestamp) {
console.log('新しいページ遷移'); // New page transition
currentTimestamp = Date.now();
if (currentTimestamp === prevTimestamp) {
currentTimestamp += 1; // I don't think the timestamps will be the same, but just in case
}
// Save timestamp to history.state
history.replaceState({
timestamp: currentTimestamp
}, '');
} else {
console.log('履歴内の移動'); // Movement within history
if (prevTimestamp > currentTimestamp) {
console.log('戻るボタン'); // Back button
} else if (prevTimestamp < currentTimestamp) {
console.log('進むボタン'); // Forward button
} else {
console.log('どれでもない'); // None of the above
}
}
sessionStorage.setItem('prevTimestamp', currentTimestamp);
});
Explanation
For example, let's consider a case where page transitions occur as follows.
History Order | Timestamp | Page |
---|---|---|
1 | 15:00 | page1 |
2 | 15:10 | page2 |
3 | 15:20 | page3 |
4 * | 15:30 | page1 |
*
indicates the currently displayed page.
Since a timestamp is set in history.state
for each new page transition, it looks like this:
History Order | history.state.timestamp | Page |
---|---|---|
1 | 15:00 | page1 |
2 | 15:10 | page2 |
3 | 15:20 | page3 |
4 * | 15:30 | page1 |
The prevTimestamp
stored in Session Storage
is 15:30.
Back
If you navigate with the "back" button here, you move to the 3rd page, and history.state.timestamp
is 15:20.
prevTimestamp(15:30)
> history.state.timestamp(15:20)
So we know it's a "back" action.
History Order | history.state.timestamp | Page |
---|---|---|
1 | 15:00 | page1 |
2 | 15:10 | page2 |
3 * | 15:20 | page3 |
4 | 15:30 | page1 |
prevTimestamp
is updated to 15:20.
Forward
Next, if you navigate with the "forward" button, you move to the 4th page, and history.state.timestamp
is 15:30.
prevTimestamp(15:20)
< history.state.timestamp(15:30)
So we know it's a "forward" action.
History Order | history.state.timestamp | Page |
---|---|---|
1 | 15:00 | page1 |
2 | 15:10 | page2 |
3 | 15:20 | page3 |
4 * | 15:30 | page1 |
prevTimestamp
is updated to 15:30.
New Page Transition
Now, let's try a normal page transition instead of "back" or "forward". Suppose you click a link within the page or directly type a URL into the browser's address bar to display page3.
At this time, a new entry is added to the history.
Since it's a new entry, history.state
contains nothing.
Therefore, we know it's a "new page transition".
Prepare for the next determination by setting the current timestamp in history.state
.
History Order | history.state.timestamp | Page |
---|---|---|
1 | 15:00 | page1 |
2 | 15:10 | page2 |
3 | 15:20 | page3 |
4 | 15:30 | page1 |
5 * | 15:40 | page3 |
prevTimestamp
is updated to 15:40.
None of the above
Let's perform a normal page transition again. We'll navigate to page3 in the same way as before.
Alternatively, reloading will result in the same behavior.
At this time, no new entry is added to the history.
history.state.timestamp
is set to 15:40
.
prevTimestamp(15:40)
= history.state.timestamp(15:40)
This is the "none of the above" pattern. The history state does not change.
History Order | history.state.timestamp | Page |
---|---|---|
1 | 15:00 | page1 |
2 | 15:10 | page2 |
3 | 15:20 | page3 |
4 | 15:30 | page1 |
5 * | 15:40 | page3 |
prevTimestamp
remains 15:40.
It seems there is a browser history specification that states: if you display a page with the same URL as the currently displayed page, the history does not increase. This applies not only to the beginning of the history; if you re-display the same page while page1 or page2 is showing, the history will not increase.
While I used the phrase "none of the above," "transitioned to the same page" might be a more appropriate expression.
Notes
pageshow
event 5
When a page is restored from cache, theload
event does not occur, sopageshow
is used.
history.state
In the code example,state
is overwritten withhistory.replaceState
, but ifstate
is being used for other purposes, care must be taken to avoid mutual overwriting.sessionStorage
SincesessionStorage
is data shared only within the same origin and same tab, it cannot be used for detection across different origins.
Footnotes
-
If caching is disabled, navigating with the "back" or "forward" buttons will cause a reload, and thus the
popstate
handler will be destroyed.↩