[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-sendbeacon-reliable-unload-data":32,"quiz-article-sendbeacon-reliable-unload-data":48},[4,20],{"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","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","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.","Structured paths, listening drills and spaced repetition that actually sticks.","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,"kind":35,"title":36,"description":37,"config":38,"verticalId":5,"vertical":43,"course":40,"_count":44,"access":45,"attempts":47,"questionCount":39},"019fe776-d835-779f-afdd-e7e854f36341","sendbeacon-reliable-unload-data","PRACTICE_QUIZ","navigator.sendBeacon() — reliable unload data","navigator.sendBeacon() is the correct API for sending analytics, session-end events, and error reports when a page unloads. These questions check why fetch() fails in beforeunload, what sendBeacon() guarantees, what data types it accepts, how its return value works, why visibilitychange is preferred over beforeunload, and when keepalive fetch is a better fit.",{"questionCount":39,"timeLimitSec":40,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":41,"maxAttempts":40,"revealAnswers":42,"allowFlagging":18,"allowBacktracking":17},8,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":39},{"allowed":17,"reason":46},"FREE",[],{"id":49,"slug":34,"title":50,"subtitle":40,"excerpt":51,"coverUrl":52,"locale":13,"readingMinutes":53,"publishedAt":54,"viewCount":55,"likeCount":19,"commentCount":19,"author":56,"vertical":61,"topic":62,"tags":65,"_count":77,"playground":79,"body":80,"bodyMd":220,"seo":221,"translationGroupId":225,"thread":226,"assessments":228,"translations":231,"quiz":233},"019fe661-0450-720a-ad6e-5eb995429cd7","Your `fetch()` in `beforeunload` is being silently dropped. Use `navigator.sendBeacon()`.","Browsers cancel in-flight network requests when a page unloads. The common fix — synchronous XHR — is deprecated. `navigator.sendBeacon()` is the correct, fire-and-forget API designed exactly for this case.","\u002Fmedia\u002Fcovers\u002Fsendbeacon-reliable-unload-data.png",4,"2026-08-13T07:40:48.196Z",32,{"id":57,"name":58,"username":59,"avatarUrl":40,"headline":60},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":63,"name":64},"javascript","JavaScript",[66,68,71,74],{"slug":63,"name":67,"color":40},"Javascript",{"slug":69,"name":70,"color":40},"webdev","Webdev",{"slug":72,"name":73,"color":40},"frontend","Frontend",{"slug":75,"name":76,"color":40},"performance","Performance",{"assessments":78},1,{"slug":34,"title":36},{"blocks":81,"version":78},[82,86,91,94,100,103,106,109,113,116,120,123,126,130,133,136,139,143,146,150,153,157,160,163,166,169,172,175,178,181,184,187,190,193,196,199,202,205,208,211],{"id":83,"html":84,"type":85},"b1","\u003Cp>When a user closes a tab, submits a form, or clicks an external link, you often need to send one last piece of data — a session duration, the last scroll position, an unhandled error report, an A\u002FB test completion event. The natural instinct is to \u003Ccode>fetch()\u003C\u002Fcode> inside a \u003Ccode>beforeunload\u003C\u002Fcode> handler. The problem: modern browsers cancel in-flight requests the moment the page begins to unload. Your data never arrives, and you never find out.\u003C\u002Fp>","paragraph",{"id":87,"html":88,"text":89,"type":90,"level":31},"b2","Why \u003Ccode>fetch\u003C\u002Fcode> in \u003Ccode>beforeunload\u003C\u002Fcode> fails","Why fetch in beforeunload fails","heading",{"id":92,"html":93,"type":85},"b3","\u003Cp>The browser&#39;s job during a page unload is to navigate away as fast as possible. Keeping a page alive to wait for a network response directly conflicts with that goal. Modern browsers — Chrome, Firefox, Safari — cancel async requests that are in-flight during unload. The \u003Ccode>beforeunload\u003C\u002Fcode> handler runs, \u003Ccode>fetch()\u003C\u002Fcode> is called, and the request is silently aborted before it reaches the server.\u003C\u002Fp>",{"id":95,"code":96,"type":97,"language":98,"highlight":99},"b4","\u002F\u002F This looks correct, but the request is often cancelled\nwindow.addEventListener('beforeunload', () => {\n  fetch('\u002Fapi\u002Fsession-end', {\n    method: 'POST',\n    body: JSON.stringify({ duration: getSessionDuration() }),\n  });\n  \u002F\u002F The browser navigates away. Fetch is cancelled. Data is lost.\n});","code","js",[],{"id":101,"html":102,"type":85},"b5","\u003Cp>The old workaround was a synchronous \u003Ccode>XMLHttpRequest\u003C\u002Fcode>, which blocks the page from closing until the request completes. That approach worked — and also made every tab close feel sluggish. Browsers deprecated synchronous XHR in \u003Ccode>unload\u003C\u002Fcode> contexts because it reliably degraded user experience. Chrome has been logging warnings about it since 2019.\u003C\u002Fp>",{"id":104,"html":105,"text":105,"type":90,"level":31},"b6","The API",{"id":107,"html":108,"type":85},"b7","\u003Cp>\u003Ccode>navigator.sendBeacon()\u003C\u002Fcode> is built for exactly this case:\u003C\u002Fp>",{"id":110,"code":111,"type":97,"language":98,"highlight":112},"b8","window.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'hidden') {\n    navigator.sendBeacon('\u002Fapi\u002Fsession-end', JSON.stringify({\n      duration: getSessionDuration(),\n      page: location.pathname,\n    }));\n  }\n});",[],{"id":114,"html":115,"type":85},"b9","\u003Cp>The browser queues the request and delivers it asynchronously, even after the page has been discarded. The tab can close, the browser can background-suspend the tab, the user can navigate away — the beacon is still delivered. You get no response object back; \u003Ccode>sendBeacon\u003C\u002Fcode> returns \u003Ccode>true\u003C\u002Fcode> if the data was successfully queued, \u003Ccode>false\u003C\u002Fcode> if the payload is too large or the browser rejected it. There is no callback, no \u003Ccode>.then()\u003C\u002Fcode>, no \u003Ccode>await\u003C\u002Fcode>. That&#39;s intentional: the call is fire-and-forget by design.\u003C\u002Fp>",{"id":117,"html":118,"text":119,"type":90,"level":31},"b10","Why \u003Ccode>visibilitychange\u003C\u002Fcode> instead of \u003Ccode>beforeunload\u003C\u002Fcode>","Why visibilitychange instead of beforeunload",{"id":121,"html":122,"type":85},"b11","\u003Cp>\u003Ccode>beforeunload\u003C\u002Fcode> has a reliability problem beyond network requests: on mobile, it often doesn&#39;t fire at all. When the OS suspends a browser tab or the user swipes the app away, there&#39;s no \u003Ccode>beforeunload\u003C\u002Fcode> event — the page just disappears.\u003C\u002Fp>",{"id":124,"html":125,"type":85},"b12","\u003Cp>\u003Ccode>document.visibilitychange\u003C\u002Fcode> with \u003Ccode>document.visibilityState === &#39;hidden&#39;\u003C\u002Fcode> is more reliable:\u003C\u002Fp>",{"id":127,"code":128,"type":97,"language":98,"highlight":129},"b13","window.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'hidden') {\n    \u002F\u002F Page is backgrounded, tab switched, device locked, or tab closing\n    navigator.sendBeacon('\u002Fapi\u002Fbeacon', payload);\n  }\n});",[],{"id":131,"html":132,"type":85},"b14","\u003Cp>The \u003Ccode>hidden\u003C\u002Fcode> event fires whenever the page leaves the foreground — including on mobile when the user switches apps. It&#39;s not a perfect signal for &quot;tab is closing specifically,&quot; but it&#39;s the closest reliable approximation, and it fires in situations where \u003Ccode>beforeunload\u003C\u002Fcode> is completely absent.\u003C\u002Fp>",{"id":134,"html":135,"text":135,"type":90,"level":31},"b15","What you can send",{"id":137,"html":138,"type":85},"b16","\u003Cp>\u003Ccode>sendBeacon\u003C\u002Fcode> accepts a \u003Ccode>BodyInit\u003C\u002Fcode> — the same types that \u003Ccode>fetch\u003C\u002Fcode> accepts as a body:\u003C\u002Fp>",{"id":140,"code":141,"type":97,"language":98,"highlight":142},"b17","\u002F\u002F String (Content-Type: text\u002Fplain)\nnavigator.sendBeacon('\u002Fapi\u002Flog', 'user-exited');\n\n\u002F\u002F JSON string (still text\u002Fplain — set Content-Type via Blob)\nconst blob = new Blob([JSON.stringify({ event: 'exit', ts: Date.now() })], {\n  type: 'application\u002Fjson',\n});\nnavigator.sendBeacon('\u002Fapi\u002Flog', blob);\n\n\u002F\u002F FormData (Content-Type: multipart\u002Fform-data)\nconst form = new FormData();\nform.append('event', 'exit');\nnavigator.sendBeacon('\u002Fapi\u002Flog', form);\n\n\u002F\u002F URLSearchParams (Content-Type: application\u002Fx-www-form-urlencoded)\nnavigator.sendBeacon('\u002Fapi\u002Flog', new URLSearchParams({ event: 'exit' }));",[],{"id":144,"html":145,"type":85},"b18","\u003Cp>The \u003Ccode>Blob\u003C\u002Fcode> approach with an explicit \u003Ccode>type\u003C\u002Fcode> is the most useful — it lets you send JSON while controlling the \u003Ccode>Content-Type\u003C\u002Fcode> header so your server receives it correctly. Without the Blob wrapper, a stringified JSON payload arrives as \u003Ccode>text\u002Fplain\u003C\u002Fcode>, and any middleware expecting \u003Ccode>application\u002Fjson\u003C\u002Fcode> will reject or misparse it.\u003C\u002Fp>",{"id":147,"html":148,"text":149,"type":90,"level":31},"b19","The \u003Ccode>keepalive\u003C\u002Fcode> fetch alternative","The keepalive fetch alternative",{"id":151,"html":152,"type":85},"b20","\u003Cp>If you need a response from the server, or you want to add custom headers, \u003Ccode>fetch\u003C\u002Fcode> with \u003Ccode>keepalive: true\u003C\u002Fcode> is the modern alternative:\u003C\u002Fp>",{"id":154,"code":155,"type":97,"language":98,"highlight":156},"b21","window.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'hidden') {\n    fetch('\u002Fapi\u002Fsession-end', {\n      method: 'POST',\n      keepalive: true,\n      headers: { 'Content-Type': 'application\u002Fjson', 'X-Auth': token },\n      body: JSON.stringify({ duration: getSessionDuration() }),\n    });\n  }\n});",[],{"id":158,"html":159,"type":85},"b22","\u003Cp>\u003Ccode>keepalive: true\u003C\u002Fcode> tells the browser to keep the request alive even if the page is discarded. Unlike \u003Ccode>sendBeacon\u003C\u002Fcode>, you can set headers and use any HTTP method. The trade-off: total \u003Ccode>keepalive\u003C\u002Fcode> payload per page is capped at 64 KB across all requests. \u003Ccode>sendBeacon\u003C\u002Fcode> has the same limit. For analytics payloads, 64 KB is effectively unlimited — but for large error dumps, be aware of it.\u003C\u002Fp>",{"id":161,"html":162,"type":85},"b23","\u003Cp>Use \u003Ccode>sendBeacon\u003C\u002Fcode> when you don&#39;t need custom headers and want the simplest possible fire-and-forget. Use \u003Ccode>keepalive\u003C\u002Fcode> fetch when you need headers, a specific HTTP method, or want to handle a response.\u003C\u002Fp>",{"id":164,"html":165,"text":165,"type":90,"level":31},"b24","Browser support",{"id":167,"html":168,"type":85},"b25","\u003Cp>\u003Ccode>navigator.sendBeacon()\u003C\u002Fcode> is \u003Cstrong>Baseline 2022\u003C\u002Fstrong>: Chrome 39 (2014), Firefox 31 (2014), Safari 11.1 (2018). It has been available in every supported browser for years and works in Web Workers. There is nothing to polyfill for any currently-maintained target.\u003C\u002Fp>",{"id":170,"html":171,"type":85},"b26","\u003C!-- playground:start -->",{"id":173,"html":174,"text":174,"type":90,"level":31},"b27","🎮 Try it yourself",{"id":176,"html":177,"type":85},"b28","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fsendbeacon-reliable-unload-data\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":179,"html":180,"type":85},"b29","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":182,"html":183,"type":85},"b30","\u003C!-- playground:end -->",{"id":185,"html":186,"type":85},"b31","\u003C!-- quiz:start -->",{"id":188,"html":189,"text":189,"type":90,"level":31},"b32","🧠 Test yourself",{"id":191,"html":192,"type":85},"b33","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fsendbeacon-reliable-unload-data\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":194,"html":195,"type":85},"b34","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":197,"html":198,"type":85},"b35","\u003C!-- quiz:end -->",{"id":200,"html":201,"text":201,"type":90,"level":31},"b36","The takeaway",{"id":203,"html":204,"type":85},"b37","\u003Cp>Search your codebase for \u003Ccode>fetch\u003C\u002Fcode> or \u003Ccode>XMLHttpRequest\u003C\u002Fcode> inside \u003Ccode>beforeunload\u003C\u002Fcode> or \u003Ccode>unload\u003C\u002Fcode> handlers. If you find them, the data they send is being silently dropped in a meaningful percentage of page exits. Replace them with \u003Ccode>navigator.sendBeacon()\u003C\u002Fcode> on \u003Ccode>visibilitychange\u003C\u002Fcode>, or a \u003Ccode>keepalive\u003C\u002Fcode> fetch if you need headers. The API is one line, the delivery is reliable, and the browser handles the timing without blocking navigation.\u003C\u002Fp>",{"id":206,"type":207},"b38","divider",{"id":209,"html":210,"type":85},"b39","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":212,"type":213,"items":214,"ordered":18},"b40","list",[215,216,217,218,219],"⭐ \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, submits a form, or clicks an external link, you often need to send one last piece of data — a session duration, the last scroll position, an unhandled error report, an A\u002FB test completion event. The natural instinct is to `fetch()` inside a `beforeunload` handler. The problem: modern browsers cancel in-flight requests the moment the page begins to unload. Your data never arrives, and you never find out.\n\n## Why `fetch` in `beforeunload` fails\n\nThe browser's job during a page unload is to navigate away as fast as possible. Keeping a page alive to wait for a network response directly conflicts with that goal. Modern browsers — Chrome, Firefox, Safari — cancel async requests that are in-flight during unload. The `beforeunload` handler runs, `fetch()` is called, and the request is silently aborted before it reaches the server.\n\n```js\n\u002F\u002F This looks correct, but the request is often cancelled\nwindow.addEventListener('beforeunload', () => {\n  fetch('\u002Fapi\u002Fsession-end', {\n    method: 'POST',\n    body: JSON.stringify({ duration: getSessionDuration() }),\n  });\n  \u002F\u002F The browser navigates away. Fetch is cancelled. Data is lost.\n});\n```\n\nThe old workaround was a synchronous `XMLHttpRequest`, which blocks the page from closing until the request completes. That approach worked — and also made every tab close feel sluggish. Browsers deprecated synchronous XHR in `unload` contexts because it reliably degraded user experience. Chrome has been logging warnings about it since 2019.\n\n## The API\n\n`navigator.sendBeacon()` is built for exactly this case:\n\n```js\nwindow.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'hidden') {\n    navigator.sendBeacon('\u002Fapi\u002Fsession-end', JSON.stringify({\n      duration: getSessionDuration(),\n      page: location.pathname,\n    }));\n  }\n});\n```\n\nThe browser queues the request and delivers it asynchronously, even after the page has been discarded. The tab can close, the browser can background-suspend the tab, the user can navigate away — the beacon is still delivered. You get no response object back; `sendBeacon` returns `true` if the data was successfully queued, `false` if the payload is too large or the browser rejected it. There is no callback, no `.then()`, no `await`. That's intentional: the call is fire-and-forget by design.\n\n## Why `visibilitychange` instead of `beforeunload`\n\n`beforeunload` has a reliability problem beyond network requests: on mobile, it often doesn't fire at all. When the OS suspends a browser tab or the user swipes the app away, there's no `beforeunload` event — the page just disappears.\n\n`document.visibilitychange` with `document.visibilityState === 'hidden'` is more reliable:\n\n```js\nwindow.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'hidden') {\n    \u002F\u002F Page is backgrounded, tab switched, device locked, or tab closing\n    navigator.sendBeacon('\u002Fapi\u002Fbeacon', payload);\n  }\n});\n```\n\nThe `hidden` event fires whenever the page leaves the foreground — including on mobile when the user switches apps. It's not a perfect signal for \"tab is closing specifically,\" but it's the closest reliable approximation, and it fires in situations where `beforeunload` is completely absent.\n\n## What you can send\n\n`sendBeacon` accepts a `BodyInit` — the same types that `fetch` accepts as a body:\n\n```js\n\u002F\u002F String (Content-Type: text\u002Fplain)\nnavigator.sendBeacon('\u002Fapi\u002Flog', 'user-exited');\n\n\u002F\u002F JSON string (still text\u002Fplain — set Content-Type via Blob)\nconst blob = new Blob([JSON.stringify({ event: 'exit', ts: Date.now() })], {\n  type: 'application\u002Fjson',\n});\nnavigator.sendBeacon('\u002Fapi\u002Flog', blob);\n\n\u002F\u002F FormData (Content-Type: multipart\u002Fform-data)\nconst form = new FormData();\nform.append('event', 'exit');\nnavigator.sendBeacon('\u002Fapi\u002Flog', form);\n\n\u002F\u002F URLSearchParams (Content-Type: application\u002Fx-www-form-urlencoded)\nnavigator.sendBeacon('\u002Fapi\u002Flog', new URLSearchParams({ event: 'exit' }));\n```\n\nThe `Blob` approach with an explicit `type` is the most useful — it lets you send JSON while controlling the `Content-Type` header so your server receives it correctly. Without the Blob wrapper, a stringified JSON payload arrives as `text\u002Fplain`, and any middleware expecting `application\u002Fjson` will reject or misparse it.\n\n## The `keepalive` fetch alternative\n\nIf you need a response from the server, or you want to add custom headers, `fetch` with `keepalive: true` is the modern alternative:\n\n```js\nwindow.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'hidden') {\n    fetch('\u002Fapi\u002Fsession-end', {\n      method: 'POST',\n      keepalive: true,\n      headers: { 'Content-Type': 'application\u002Fjson', 'X-Auth': token },\n      body: JSON.stringify({ duration: getSessionDuration() }),\n    });\n  }\n});\n```\n\n`keepalive: true` tells the browser to keep the request alive even if the page is discarded. Unlike `sendBeacon`, you can set headers and use any HTTP method. The trade-off: total `keepalive` payload per page is capped at 64 KB across all requests. `sendBeacon` has the same limit. For analytics payloads, 64 KB is effectively unlimited — but for large error dumps, be aware of it.\n\nUse `sendBeacon` when you don't need custom headers and want the simplest possible fire-and-forget. Use `keepalive` fetch when you need headers, a specific HTTP method, or want to handle a response.\n\n## Browser support\n\n`navigator.sendBeacon()` is **Baseline 2022**: Chrome 39 (2014), Firefox 31 (2014), Safari 11.1 (2018). It has been available in every supported browser for years and works in Web Workers. There is nothing to polyfill for any currently-maintained target.\n\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fsendbeacon-reliable-unload-data\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 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fsendbeacon-reliable-unload-data\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` inside `beforeunload` or `unload` handlers. If you find them, the data they send is being silently dropped in a meaningful percentage of page exits. Replace them with `navigator.sendBeacon()` on `visibilitychange`, or a `keepalive` fetch if you need headers. The API is one line, the delivery is reliable, and the browser handles the timing without blocking navigation.\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":222,"canonical":223,"description":224},"Your `fetch()` in `beforeunload` is being silently dropped. Use `navig","https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fsendbeacon-reliable-unload-data","Browsers cancel in-flight network requests when a page unloads. The common fix — synchronous XHR — is deprecated. `navigator.sendBeacon()` is the correct, fire-and-forget API desig","019fe661-0451-73ec-b144-8d6c5db8f99c",{"id":227,"locked":18},"019fe661-0b8f-721e-86ee-c42e3463d503",[229],{"id":33,"slug":34,"title":36,"_count":230},{"questions":39},[232],{"locale":13,"slug":34},{"id":33,"slug":34,"title":36,"_count":234,"questionCount":39},{"questions":39}]