[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-navigator-sendbeacon-reliable-analytics":44,"search-suggestions":60,"quiz-article-navigator-sendbeacon-reliable-analytics":105},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"kind":47,"title":48,"description":49,"config":50,"verticalId":5,"vertical":55,"course":52,"_count":56,"access":57,"attempts":59,"questionCount":51},"019ff19b-fc34-77eb-b95e-b15a6c5d2ba5","navigator-sendbeacon-reliable-analytics","PRACTICE_QUIZ","navigator.sendBeacon — reliable analytics on page unload","navigator.sendBeacon sends a POST the browser guarantees to deliver even after the page unloads. These questions cover the API shape, why fetch fails in beforeunload, payload types, the visibilitychange trick, and browser support.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},9,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,77,81,84,88,92,96,99,102],{"slug":62,"name":63,"articles":64},"webdev","Webdev",61,{"slug":66,"name":67,"articles":68},"frontend","Frontend",55,{"slug":70,"name":71,"articles":72},"javascript","Javascript",52,{"slug":74,"name":75,"articles":76},"css","Css",23,{"slug":78,"name":79,"articles":80},"typescript","Typescript",10,{"slug":82,"name":83,"articles":51},"performance","Performance",{"slug":85,"name":86,"articles":87},"grammar","Grammar",6,{"slug":89,"name":90,"articles":91},"react","React",5,{"slug":93,"name":94,"articles":95},"html","Html",4,{"slug":97,"name":98,"articles":95},"tutorial","Tutorial",{"slug":100,"name":101,"articles":95},"node","Node",{"slug":103,"name":104,"articles":43},"ai","Ai",{"id":106,"slug":46,"title":107,"subtitle":52,"excerpt":108,"coverUrl":109,"locale":13,"readingMinutes":95,"publishedAt":110,"viewCount":111,"likeCount":19,"commentCount":19,"author":112,"vertical":117,"topic":118,"tags":120,"_count":125,"playground":127,"body":129,"bodyMd":270,"seo":271,"translationGroupId":275,"thread":276,"assessments":278,"translations":281,"quiz":283},"019ff19b-fbc7-7069-b3ba-28ffd8fbf4d3","Your fetch in beforeunload is silently killed. navigator.sendBeacon guarantees delivery.","When the page unloads, the browser may cancel any in-flight fetch or XHR before it completes. navigator.sendBeacon sends a POST the browser commits to delivering, even after the document is gone.","\u002Fmedia\u002Fcovers\u002Fnavigator-sendbeacon-reliable-analytics.png","2026-08-22T06:47:21.180Z",45,{"id":113,"name":114,"username":115,"avatarUrl":52,"headline":116},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":70,"name":119},"JavaScript",[121,122,123,124],{"slug":70,"name":71,"color":52},{"slug":62,"name":63,"color":52},{"slug":66,"name":67,"color":52},{"slug":82,"name":83,"color":52},{"assessments":126},1,{"slug":46,"title":128},"navigator.sendBeacon — interactive playground",{"blocks":130,"version":126},[131,135,139,145,148,151,154,157,161,164,167,170,178,181,185,188,191,194,197,201,204,207,210,213,217,220,223,226,229,232,235,238,241,244,247,250,253,256,259,262],{"id":132,"html":133,"type":134},"b1","\u003Cp>When a user closes a tab, your analytics flush typically looks like a \u003Ccode>fetch\u003C\u002Fcode> call inside a \u003Ccode>beforeunload\u003C\u002Fcode> handler. It works in testing and silently fails in production. The browser&#39;s unload policy cancels in-flight requests before they complete, and a fetch fired in \u003Ccode>beforeunload\u003C\u002Fcode> races directly against that. \u003Ccode>navigator.sendBeacon\u003C\u002Fcode> was designed for exactly this moment.\u003C\u002Fp>","paragraph",{"id":136,"html":137,"text":137,"type":138,"level":31},"b2","The API","heading",{"id":140,"code":141,"type":142,"language":143,"highlight":144},"b3","navigator.sendBeacon('\u002Fanalytics', JSON.stringify({ event: 'page_exit', duration: 4200 }));","code","js",[],{"id":146,"html":147,"type":134},"b4","\u003Cp>One call, one guarantee: the browser queues the request and delivers it even if the page unloads before the network round-trip completes. The return value is a boolean — \u003Ccode>true\u003C\u002Fcode> if the request was successfully queued, \u003Ccode>false\u003C\u002Fcode> if the browser declined it (typically because the payload exceeds the size limit). You don&#39;t receive a response body; \u003Ccode>sendBeacon\u003C\u002Fcode> is fire-and-forget.\u003C\u002Fp>",{"id":149,"html":150,"type":134},"b5","\u003Cp>It always sends a \u003Ccode>POST\u003C\u002Fcode>. No custom headers — the request goes out with minimal headers by default.\u003C\u002Fp>",{"id":152,"html":153,"text":153,"type":138,"level":31},"b6","Why fetch fails on page unload",{"id":155,"html":156,"type":134},"b7","\u003Cp>The \u003Ccode>beforeunload\u003C\u002Fcode> event fires synchronously when the page is about to be discarded. Async operations started inside it have no delivery guarantee:\u003C\u002Fp>",{"id":158,"code":159,"type":142,"language":143,"highlight":160},"b8","\u002F\u002F Looks correct. In practice, this often never arrives.\nwindow.addEventListener('beforeunload', () => {\n  fetch('\u002Fanalytics', {\n    method: 'POST',\n    body: JSON.stringify({ event: 'page_exit' }),\n  });\n});",[],{"id":162,"html":163,"type":134},"b9","\u003Cp>The fetch starts, but the browser tears down the page context while it is in-flight. Some browsers cancel the request immediately; others may complete it if the connection is already established, but you cannot rely on that. The \u003Ccode>keepalive: true\u003C\u002Fcode> fetch option tells the browser to outlive the page context, but it caps the total keepalive payload at 64 KB across all active requests and has had reliability issues in older Safari. \u003Ccode>sendBeacon\u003C\u002Fcode> has none of these caveats — it was purpose-built for this pattern.\u003C\u002Fp>",{"id":165,"html":166,"text":166,"type":138,"level":31},"b10","What you can send",{"id":168,"html":169,"type":134},"b11","\u003Cp>\u003Ccode>sendBeacon\u003C\u002Fcode> accepts a \u003Ccode>BodyInit\u003C\u002Fcode> payload, the same types \u003Ccode>fetch\u003C\u002Fcode> accepts for \u003Ccode>body\u003C\u002Fcode>:\u003C\u002Fp>",{"id":171,"type":172,"items":173,"ordered":18},"b12","list",[174,175,176,177],"A \u003Ccode>string\u003C\u002Fcode>","A \u003Ccode>Blob\u003C\u002Fcode>","\u003Ccode>FormData\u003C\u002Fcode>","\u003Ccode>URLSearchParams\u003C\u002Fcode>",{"id":179,"html":180,"type":134},"b13","\u003Cp>For JSON payloads, wrap the string in a \u003Ccode>Blob\u003C\u002Fcode> with the correct content type so the request&#39;s \u003Ccode>Content-Type\u003C\u002Fcode> header is set properly:\u003C\u002Fp>",{"id":182,"code":183,"type":142,"language":143,"highlight":184},"b14","function sendAnalytics(payload) {\n  const body = new Blob(\n    [JSON.stringify(payload)],\n    { type: 'application\u002Fjson' }\n  );\n  navigator.sendBeacon('\u002Fanalytics', body);\n}",[],{"id":186,"html":187,"type":134},"b15","\u003Cp>Without the \u003Ccode>Blob\u003C\u002Fcode> wrapper, \u003Ccode>sendBeacon\u003C\u002Fcode> sends the JSON string with a \u003Ccode>text\u002Fplain\u003C\u002Fcode> content type. Your server may parse it regardless — but the explicit content type is more correct and avoids surprises with strict backends.\u003C\u002Fp>",{"id":189,"html":190,"text":190,"type":138,"level":31},"b16","Use visibilitychange, not beforeunload",{"id":192,"html":193,"type":134},"b17","\u003Cp>\u003Ccode>beforeunload\u003C\u002Fcode> has a second problem beyond delivery: on mobile browsers — particularly iOS Safari — it doesn&#39;t fire reliably when the user switches apps, locks their phone, or is backgrounded by the OS. Events collected during a mobile session can disappear without a trace.\u003C\u002Fp>",{"id":195,"html":196,"type":134},"b18","\u003Cp>The correct trigger for flush-on-exit logic is \u003Ccode>visibilitychange\u003C\u002Fcode>:\u003C\u002Fp>",{"id":198,"code":199,"type":142,"language":143,"highlight":200},"b19","const eventQueue = [];\n\nfunction track(event) {\n  eventQueue.push({ ...event, ts: Date.now() });\n}\n\nfunction flush() {\n  if (eventQueue.length === 0) return;\n  const body = new Blob(\n    [JSON.stringify(eventQueue)],\n    { type: 'application\u002Fjson' }\n  );\n  const queued = navigator.sendBeacon('\u002Fanalytics\u002Fbatch', body);\n  if (queued) eventQueue.length = 0;\n}\n\ndocument.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'hidden') flush();\n});",[],{"id":202,"html":203,"type":134},"b20","\u003Cp>\u003Ccode>visibilitychange\u003C\u002Fcode> to \u003Ccode>&#39;hidden&#39;\u003C\u002Fcode> fires when the tab loses focus, when the user navigates away, when they switch apps on mobile, and when the browser is backgrounded — which covers every case \u003Ccode>beforeunload\u003C\u002Fcode> misses. Events collected in memory are batched into one \u003Ccode>sendBeacon\u003C\u002Fcode> call instead of one request per event, which is both cheaper and less likely to exceed the size limit.\u003C\u002Fp>",{"id":205,"html":206,"type":134},"b21","\u003Cp>One note: \u003Ccode>visibilitychange\u003C\u002Fcode> to \u003Ccode>&#39;hidden&#39;\u003C\u002Fcode> does not mean the page is gone. The user may tab back in. If your flush logic clears the queue on a successful \u003Ccode>sendBeacon\u003C\u002Fcode>, you will start a new batch cleanly on the next interaction. That is the right behavior.\u003C\u002Fp>",{"id":208,"html":209,"text":209,"type":138,"level":31},"b22","Browser support",{"id":211,"html":212,"type":134},"b23","\u003Cp>\u003Ccode>sendBeacon\u003C\u002Fcode> is \u003Cstrong>Baseline 2022\u003C\u002Fstrong>: Chrome 39 (2014), Firefox 31 (2014), Safari 11.1 (2018). It has been universally available for years. The feature detection is simple if you need it:\u003C\u002Fp>",{"id":214,"code":215,"type":142,"language":143,"highlight":216},"b24","if (navigator.sendBeacon) {\n  navigator.sendBeacon(url, body);\n} else {\n  fetch(url, { method: 'POST', body, keepalive: true });\n}",[],{"id":218,"html":219,"type":134},"b25","\u003Cp>In practice, if you are targeting any currently-supported browser, the \u003Ccode>else\u003C\u002Fcode> branch never runs.\u003C\u002Fp>",{"id":221,"html":222,"type":134},"b26","\u003C!-- playground:start -->",{"id":224,"html":225,"text":225,"type":138,"level":31},"b27","🎮 Try it yourself",{"id":227,"html":228,"type":134},"b28","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnavigator-sendbeacon-reliable-analytics\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":230,"html":231,"type":134},"b29","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":233,"html":234,"type":134},"b30","\u003C!-- playground:end -->",{"id":236,"html":237,"type":134},"b31","\u003C!-- quiz:start -->",{"id":239,"html":240,"text":240,"type":138,"level":31},"b32","🧠 Test yourself",{"id":242,"html":243,"type":134},"b33","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnavigator-sendbeacon-reliable-analytics\u002Fquiz\">Take the 9-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":245,"html":246,"type":134},"b34","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":248,"html":249,"type":134},"b35","\u003C!-- quiz:end -->",{"id":251,"html":252,"text":252,"type":138,"level":31},"b36","The takeaway",{"id":254,"html":255,"type":134},"b37","\u003Cp>Search your codebase for \u003Ccode>fetch\u003C\u002Fcode> or \u003Ccode>XMLHttpRequest\u003C\u002Fcode> calls inside \u003Ccode>beforeunload\u003C\u002Fcode> or \u003Ccode>unload\u003C\u002Fcode> handlers. They may complete reliably in a Chrome dev session and silently drop data in production — especially on mobile. Replace them with \u003Ccode>navigator.sendBeacon\u003C\u002Fcode>, send JSON as a \u003Ccode>Blob\u003C\u002Fcode> with \u003Ccode>application\u002Fjson\u003C\u002Fcode> to preserve the content type, and trigger the flush from \u003Ccode>visibilitychange\u003C\u002Fcode> rather than \u003Ccode>beforeunload\u003C\u002Fcode> to cover mobile users. You will stop losing events you have been quietly counting on.\u003C\u002Fp>",{"id":257,"type":258},"b38","divider",{"id":260,"html":261,"type":134},"b39","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":263,"type":172,"items":264,"ordered":18},"b40",[265,266,267,268,269],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","💼 \u003Cstrong>LinkedIn\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F\">linkedin.com\u002Fin\u002Fparsa-jiravand\u003C\u002Fa>","✉️ \u003Cstrong>Email\u003C\u002Fstrong> (work &amp; contract inquiries): \u003Ca href=\"mailto:bestpractice2026@gmail.com\">bestpractice2026@gmail.com\u003C\u002Fa>","When a user closes a tab, your analytics flush typically looks like a `fetch` call inside a `beforeunload` handler. It works in testing and silently fails in production. The browser's unload policy cancels in-flight requests before they complete, and a fetch fired in `beforeunload` races directly against that. `navigator.sendBeacon` was designed for exactly this moment.\n\n## The API\n\n```js\nnavigator.sendBeacon('\u002Fanalytics', JSON.stringify({ event: 'page_exit', duration: 4200 }));\n```\n\nOne call, one guarantee: the browser queues the request and delivers it even if the page unloads before the network round-trip completes. The return value is a boolean — `true` if the request was successfully queued, `false` if the browser declined it (typically because the payload exceeds the size limit). You don't receive a response body; `sendBeacon` is fire-and-forget.\n\nIt always sends a `POST`. No custom headers — the request goes out with minimal headers by default.\n\n## Why fetch fails on page unload\n\nThe `beforeunload` event fires synchronously when the page is about to be discarded. Async operations started inside it have no delivery guarantee:\n\n```js\n\u002F\u002F Looks correct. In practice, this often never arrives.\nwindow.addEventListener('beforeunload', () => {\n  fetch('\u002Fanalytics', {\n    method: 'POST',\n    body: JSON.stringify({ event: 'page_exit' }),\n  });\n});\n```\n\nThe fetch starts, but the browser tears down the page context while it is in-flight. Some browsers cancel the request immediately; others may complete it if the connection is already established, but you cannot rely on that. The `keepalive: true` fetch option tells the browser to outlive the page context, but it caps the total keepalive payload at 64 KB across all active requests and has had reliability issues in older Safari. `sendBeacon` has none of these caveats — it was purpose-built for this pattern.\n\n## What you can send\n\n`sendBeacon` accepts a `BodyInit` payload, the same types `fetch` accepts for `body`:\n\n- A `string`\n- A `Blob`\n- `FormData`\n- `URLSearchParams`\n\nFor JSON payloads, wrap the string in a `Blob` with the correct content type so the request's `Content-Type` header is set properly:\n\n```js\nfunction sendAnalytics(payload) {\n  const body = new Blob(\n    [JSON.stringify(payload)],\n    { type: 'application\u002Fjson' }\n  );\n  navigator.sendBeacon('\u002Fanalytics', body);\n}\n```\n\nWithout the `Blob` wrapper, `sendBeacon` sends the JSON string with a `text\u002Fplain` content type. Your server may parse it regardless — but the explicit content type is more correct and avoids surprises with strict backends.\n\n## Use visibilitychange, not beforeunload\n\n`beforeunload` has a second problem beyond delivery: on mobile browsers — particularly iOS Safari — it doesn't fire reliably when the user switches apps, locks their phone, or is backgrounded by the OS. Events collected during a mobile session can disappear without a trace.\n\nThe correct trigger for flush-on-exit logic is `visibilitychange`:\n\n```js\nconst eventQueue = [];\n\nfunction track(event) {\n  eventQueue.push({ ...event, ts: Date.now() });\n}\n\nfunction flush() {\n  if (eventQueue.length === 0) return;\n  const body = new Blob(\n    [JSON.stringify(eventQueue)],\n    { type: 'application\u002Fjson' }\n  );\n  const queued = navigator.sendBeacon('\u002Fanalytics\u002Fbatch', body);\n  if (queued) eventQueue.length = 0;\n}\n\ndocument.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'hidden') flush();\n});\n```\n\n`visibilitychange` to `'hidden'` fires when the tab loses focus, when the user navigates away, when they switch apps on mobile, and when the browser is backgrounded — which covers every case `beforeunload` misses. Events collected in memory are batched into one `sendBeacon` call instead of one request per event, which is both cheaper and less likely to exceed the size limit.\n\nOne note: `visibilitychange` to `'hidden'` does not mean the page is gone. The user may tab back in. If your flush logic clears the queue on a successful `sendBeacon`, you will start a new batch cleanly on the next interaction. That is the right behavior.\n\n## Browser support\n\n`sendBeacon` is **Baseline 2022**: Chrome 39 (2014), Firefox 31 (2014), Safari 11.1 (2018). It has been universally available for years. The feature detection is simple if you need it:\n\n```js\nif (navigator.sendBeacon) {\n  navigator.sendBeacon(url, body);\n} else {\n  fetch(url, { method: 'POST', body, keepalive: true });\n}\n```\n\nIn practice, if you are targeting any currently-supported browser, the `else` branch never runs.\n\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnavigator-sendbeacon-reliable-analytics\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 9-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnavigator-sendbeacon-reliable-analytics\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n## The takeaway\n\nSearch your codebase for `fetch` or `XMLHttpRequest` calls inside `beforeunload` or `unload` handlers. They may complete reliably in a Chrome dev session and silently drop data in production — especially on mobile. Replace them with `navigator.sendBeacon`, send JSON as a `Blob` with `application\u002Fjson` to preserve the content type, and trigger the flush from `visibilitychange` rather than `beforeunload` to cover mobile users. You will stop losing events you have been quietly counting on.\n\n---\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)\n- 💼 **LinkedIn** — [linkedin.com\u002Fin\u002Fparsa-jiravand](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F)\n- ✉️ **Email** (work & contract inquiries): [bestpractice2026@gmail.com](mailto:bestpractice2026@gmail.com)",{"title":272,"canonical":273,"description":274},"Your fetch in beforeunload is silently killed. navigator.sendBeacon gu","https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnavigator-sendbeacon-reliable-analytics","When the page unloads, the browser may cancel any in-flight fetch or XHR before it completes. navigator.sendBeacon sends a POST the browser commits to delivering, even after the do","019ff19b-fbc7-7069-b3ba-2fb512ce3d10",{"id":277,"locked":18},"019ff19b-fbfd-769a-bc73-b3a776d0855c",[279],{"id":45,"slug":46,"title":48,"_count":280},{"questions":51},[282],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":284,"questionCount":51},{"questions":51}]