navigator.sendBeaconと離脱時の確実な送信
離脱時にビーコンが消える事故を防げる。sendBeaconがブラウザにキューを預けてページ破棄後も送り切る仕組み、fetch keepaliveとの違い、計測設計の勘所を内部動作から正確に解きます。
- 1.sendBeacon はリクエストをブラウザのバックグラウンドキューに預け、呼び出し元のページが破棄された後もブラウザ自身が送信を完遂する。同期 XHR や離脱時 fetch のように「ページ消滅で送信ごと巻き込まれる」事故を避けられるのが本質。
- 2.戻り値はキュー投入の可否(true/false)だけで、送信の成否やレスポンスは観測できない。HTTP メソッドは常に POST、本文の型で Content-Type が自動決定され、ヘッダや credentials の細かい指定はできない一方、CORS プリフライトを避ける軽量送信に向く。
- 3.より柔軟な制御が要るなら fetch の keepalive:true が代替で、GET/ヘッダ/レスポンス取得が可能。離脱検知は unload ではなく visibilitychange の hidden と pagehide を使い、bfcache を壊さない設計にするのが計測の定石。
ページを閉じる・別ページへ遷移する直前に、最後のアクセスログや計測データを送りたい場面は多くあります。素朴に unload で fetch() や非同期 XHR を投げると、送信が始まる前にページが破棄され、リクエストが握りつぶされることが珍しくありません。navigator.sendBeacon() は、この「離脱時の取りこぼし」を構造的に解決するために設計された API です。ここではキューイングの内部動作、fetch の keepalive との違い、そして計測用途で壊れない設計を原理から見ていきます。
なぜ離脱時の送信は失敗するのか
ブラウザはページを破棄するとき、そのページに紐づく実行コンテキスト・タイマー・進行中のネットワーク取得をまとめて停止します。unload ハンドラ内で fetch() を呼んでも、返る Promise の解決やネットワーク送出はイベントループ上の非同期処理で進むため(イベントループとマイクロタスク の上で動く)、ハンドラを抜けてページが消えると送信が中断されます。
歴史的な回避策は同期 XHR(xhr.open(..., false))でした。同期送信ならハンドラを抜ける前に送り切れますが、代償としてメインスレッドを完全にブロックし、サーバー応答が遅いと離脱そのものがカクつきます。ユーザー体験を犠牲にして確実性を買う、筋の悪いトレードオフです。
unload はモバイルでは発火しないことがあり、さらに登録するだけで Back/Forward Cache を阻害してページ復元を遅くします。離脱検知は後述の visibilitychange(hidden)と pagehide を使い、unload は避けるのが現在の定石です。
sendBeacon のキューイング機構
navigator.sendBeacon(url, data) の核心は、送信をページから切り離してブラウザに委譲する点にあります。呼び出すとブラウザはリクエストを内部のバックグラウンドキューに登録し、その場では送信完了を待たずに即座に制御を返します。実際の送出は、呼び出し元のページが破棄された後でもブラウザプロセス自身が責任を持って完遂します。
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
const data = JSON.stringify({ event: 'leave', t: Date.now() });
const blob = new Blob([data], { type: 'application/json' });
navigator.sendBeacon('/collect', blob); // 投入だけして即リターン
}
});
戻り値は boolean で、キューへの投入に成功したかだけを表します。true は「ブラウザが送信を引き受けた」という意味であって、サーバーに届いたこと・2xx が返ったことは一切保証しません。false になるのは、データ量がブラウザのキュー上限を超えた場合などです(Beacon 標準は実装に上限を許し、多くの実装で 64KB 程度)。レスポンスやステータスを観測する手段はなく、これは「投げっぱなしで良い計測」という用途に最適化した割り切りです。
sendBeacon が true を返しても、ネットワーク障害やサーバーエラーで欠損する可能性はあります。返り値が示すのは「ブラウザのキューに入った」ことだけ。確実な配信が要るデータ(課金・注文確定など)を離脱時ビーコンだけに頼ってはいけません。
メソッド・Content-Type・CORS の制約
sendBeacon のリクエストは常に POST で、メソッドを選べません。Content-Type は第 2 引数 data の型から自動決定されます。設計上、ヘッダを自由に付けられない代わりに、型の選び方で「プリフライトを避けられるか」が決まる点が実務上重要です。
| data の型 | 送られる Content-Type | CORS プリフライト |
|---|---|---|
| 文字列 | text/plain;charset=UTF-8 | 発生しない(単純リクエスト) |
| URLSearchParams | application/x-www-form-urlencoded | 発生しない(単純リクエスト) |
| FormData | multipart/form-data | 発生しない(単純リクエスト) |
| Blob(type 指定) | Blob の type をそのまま | type 次第(application/json 等はプリフライト対象) |
ここに落とし穴があります。JSON を送りたくて Blob の type に application/json を指定すると、これは CORS の単純リクエストの範囲外となり、クロスオリジンではプリフライト(OPTIONS)が必要になります。ところが離脱の瞬間にプリフライトと本送信の 2 往復を完了できるとは限らず、欠損率が上がります。同一オリジンへの送信、あるいはサーバーが text/plain ボディの JSON 文字列を受け付ける設計にしておくと、単純リクエストのまま確実に送れます。
credentials は明示指定できませんが、Beacon は内部的に**include 相当**で動き、対象オリジンの Cookie が同梱されます(クロスオリジンではサーバー側の CORS 許可が前提)。Cookie を使ったセッション計測ではこの挙動が効いてきます。
Authorization やカスタムヘッダ(X-...)を付けたい場合、sendBeacon では実現できません。認証トークンをクエリ文字列や本文に載せるか、後述の fetch の keepalive を使います。ただしトークンの URL 露出はログ流出のリスクがある点に注意します。
fetch keepalive との違い
fetch(url, { keepalive: true }) は、sendBeacon と**同じ下層機構(離脱後も生き残る取得)**を使いつつ、より柔軟な制御を提供します。違いは「何を犠牲にして何を得るか」です。
| 観点 | sendBeacon | fetch keepalive:true |
|---|---|---|
| メソッド | POST 固定 | GET/POST など自由 |
| ヘッダ指定 | 不可(型で自動) | 可能(Authorization 等) |
| レスポンス取得 | 不可(boolean のみ) | 可能(離脱しなければ) |
| 離脱後の完遂 | ブラウザが保証 | 同様に保証 |
| 本文サイズ上限 | 実装依存(~64KB) | keepalive 合計で 64KB |
keepalive 付き fetch は通常の Fetch API と同じくレスポンスを読め、ヘッダも付けられます。一方で keepalive リクエストの本文合計サイズは 64KB に制限され(同時に飛ばす複数の keepalive 全体の上限)、超えると fetch が reject します。「ヘッダ・メソッド・レスポンスが要るなら keepalive、最小限の投げ捨て計測なら sendBeacon」が選び分けの基準です。なお両者とも、ページが本当に破棄されるとレスポンスは観測できない点は共通です(送信自体は完遂される)。
頻出の誤解は「sendBeacon だけが離脱後も送れる特別な仕組み」というもの。実体は逆で、sendBeacon は keepalive 取得の簡易ラッパに近い位置づけです。低レベルの送信保証は共通で、API としての制約(POST 固定・ヘッダ不可・boolean 返り)が sendBeacon 側に乗っているだけ、と理解すると整理できます。
計測用途の壊れない設計
離脱検知の置き場所が、ビーコンの取りこぼし率を左右します。最も信頼できるのは visibilitychange で visibilityState === 'hidden' になった瞬間です。タブの非アクティブ化・アプリ切り替え・タブを閉じる操作のいずれでも発火し、モバイルでも比較的確実に呼ばれます。補完として pagehide も使い、両者で送ります(多重送信は計測 ID で冪等化する)。
function flush() {
if (!buffered.length) return;
const body = JSON.stringify(buffered);
navigator.sendBeacon('/collect', body); // text/plain → 単純リクエスト
buffered = [];
}
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') flush();
});
window.addEventListener('pagehide', flush); // bfcache 退避時も拾う
pagehide は bfcache への退避時にも発火するため、ここで送っておけば「戻る/進む」で凍結される直前のデータも拾えます。逆に、計測のために unload を登録すると bfcache を壊し、ページ復元を遅くするので避けます。パフォーマンス計測 API で集めた PerformanceObserver のエントリも、この hidden のタイミングで sendBeacon にまとめて流すのが王道です。
hidden は離脱時だけでなくタブ切り替えでも発火します。そこで「ページの最後に一度だけ送る」のではなく、hidden のたびにバッファを送り切る設計にすると、ユーザーが戻ってこなくてもデータが残ります。再表示(visible)後は新しいバッファを貯め直します。これにより長時間ページでも欠損を最小化できます。
まとめ
navigator.sendBeacon は送信をブラウザのバックグラウンドキューに預け、ページ破棄後も完遂させることで、離脱時の取りこぼしを構造的に防ぎます。戻り値はキュー投入の可否(boolean)だけで、到達やレスポンスは観測できず、メソッドは POST 固定、Content-Type は data の型で自動決定されます。Blob に application/json を指定すると CORS プリフライトを誘発するため、クロスオリジンでは text/plain 文字列など単純リクエストで送るのが安全です。より柔軟な制御(GET・ヘッダ・レスポンス)が要るなら fetch の keepalive:true を使い、両者は同じ離脱後完遂の機構を共有します。離脱検知は unload を避け、visibilitychange の hidden と pagehide で逐次フラッシュすれば、bfcache を壊さず欠損も最小化できます。計測 API のエントリ送出と組み合わせると、実利用者のデータを取りこぼさない計測基盤が組めます。
Web/フロントエンド Article
navigator.sendBeaconと離脱時の確実な送信を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
sendBeacon
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5
導入後に効く点
戻り値はキュー投入の可否(true/false)だけで、送信の成否やレスポンスは観測できない。HTTP メソッドは常に POST、本文の型で Content-Type が自動決定され、ヘッダや credentials の細かい指定はできない一方、CORS プリフライトを避ける軽量送信に向く。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 5
判断チェックリスト
- 自社の用途が「sendBeacon / 計測」に近いか確認する。
- 強みである「sendBeacon はリクエストをブラウザのバックグラウンドキューに預け、呼び出し元のページが破棄された後もブラウザ自身が送信を完遂する。同期 XHR や離脱時 fetch のように「ページ消滅で送信ごと巻き込まれる」事故を避けられるのが本質。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。