「戻るボタンを押してもpopstate
イベントが発火しないんだけど…?」JavaScriptで戻るボタンの挙動を制御しようとしたとき、こんな疑問にぶつかったことはありませんか?
この記事では、JavaScriptのpopstate
イベントが発火しない原因と対策について、図やサンプルコードを用いてわかりやすく解説します。
「window.addEventListener('popstate', ...)
を書いたのに反応しない!」、「pushState
を使わないとダメなの?」こんな疑問を持った方は、ぜひ読んでみてください!
popstateとは
popstate
は、「ブラウザの戻る・進む操作」や「history.back()
/history.forward()
」などによって履歴が移動したときに発火するイベントです。たとえば、以下のように記述すると、履歴が移動したタイミングでイベントが発火します。
window.addEventListener('popstate', (event) => {
console.log('popstate 発火!', event.state);
});
popstate
は、以下のような「履歴の移動(戻る/進む)」のときにのみ発火する点に注意してください。
history.pushState()
によって追加された履歴が「戻された時」history.pushState()
によって追加された履歴に「進んだ時」
そのため、普通のページ遷移やリンククリックではpopstate
は発火しません。この理由についてこれから詳しく説明します。
popstateが発火しない理由
理由1:pushStateをしていないから
popstate
は、history.pushState()
によって履歴を追加した後、追加された履歴が「戻された時」や追加された履歴に「進んだ時」にしか発火しません。たとえば、以下のようにhistory.pushState()
で履歴を1つ追加した後、ユーザーが戻るボタンを押すとpopstate
が発火します。
history.pushState(null, null, '/page1');
window.addEventListener('popstate', (event) => {
console.log('popstate 発火!', event.state);
});
逆に、history.pushState()
をしていない状態では、戻る操作があってもpopstate
は発火しません。history.pushState()
をしていない状態としている状態で動作の違いを図を用いてもう少し詳しく説明します。
history.pushState()をしていない状態
例えば、http://a.com
からhttp://b.com
にページ遷移したとします。この状態において、ブラウザの「戻る」ボタンを押してブラウザバッグをしても、popstate
は発火しません。なぜなら、ブラウザにとっては「history.pushState()
を使って追加された履歴」がない状態なので、戻るボタンを押しても、popstate
が発火しないのです。

history.pushState()をしている状態
今度は、http://a.com
からhttp://b.com
にページ遷移し、http://b.com
のJavaScriptでhistory.pushState()
を実行して履歴を追加した場合を考えます。このとき、履歴やアドレスバーの値は「history.pushState()
で指定したURL」に切り替わっていますが、historyAPIを使って履歴を変更した場合、画面ロードが走らないため、実際の画面表示は変わっていません。画面上はhistory.pushState()
実行前の「B画面」が表示されています。
history.pushStateの挙動
- 変化するもの: 履歴、アドレスバーの値
- 変化しないもの: 表示されている画面
この状態において、ブラウザの「戻る」ボタンを押してブラウザバッグすると、「history.pushState()
を使って追加された履歴」が戻されているのでpopstate
が発火します。

理由2:replaceStateだけ使っているから
history.replaceState()
は、現在の履歴エントリを「上書き」するだけなので、戻る履歴が増えません。そのため、以下のコードでは戻るボタンをクリックしてもpopstate
は発火しません。
history.replaceState({ page: 'page1' }, '', '/page1');
window.addEventListener('popstate', (event) => {
console.log('popstate 発火!', event.state);
});
だだし、すでにhistory.pushState()
を使って履歴が増えていて、そのあとにhistory.replaceState()
を使った場合は、前の履歴に戻るときにpopstate
が発火します。例えば、以下のコードでは、history.pushState()
したあとにhistory.replaceState()
を使っています。
// 初期URL: /home
history.pushState({ page: 'page1' }, '', '/page1'); // /page1 を追加
history.replaceState({ page: 'page1b' }, '', '/page1b'); // /page1 を上書き
// 履歴は:[/home] → [/page1b]
window.addEventListener('popstate', (event) => {
console.log('popstate 発火!', event.state);
});
この状態で戻るボタンを押すと、history.pushState()
によって追加された履歴が戻されているのでpopstate
が発火します。なお、event.state
には/home
に対応するstate(null)
が入ります。
理由3: ユーザー操作(クリックなど)がないとpopstateが発火しない場合があるから
現在のChromeやEdgeでは、セキュリティやユーザー保護の観点から、ユーザーが一度画面をクリックするなどの操作(インタラクション)を行わない限り、popstate
イベントが発火しない仕様になっています。
そのため、ページを開いてすぐに「Webブラウザの戻るボタン」や「バッグスペースキー」を押しても、JavaScriptでの制御が効かず、ブラウザバックを無効化できません。
Firefoxではページを開いた直後でもpopstate
イベントが発火するため、ブラウザバックの無効化が機能します(2023年12月時点の情報)。ただし今後、FirefoxもChromeやEdgeと同じ仕様に合わせてくる可能性がありますので注意してください。
本記事のまとめ
この記事では『popstateイベントが発火しない原因と対策』について、以下の内容を説明しました。
popstate
とはpopstate
が発火しない理由- 理由1:
pushState
をしていないから - 理由2:
replaceState
だけ使っているから - 理由3: ユーザー操作(クリックなど)がないと
popstate
が発火しない場合があるから
- 理由1:
お読み頂きありがとうございました。