[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-broadcast-channel-cross-tab-messaging":32,"related-broadcast-channel-cross-tab-messaging":225,"code:js:true:cmg9p0":361,"code:js:true:5c87f6":362,"code:js:true:jealj8":363,"code:js:true:137fe6o":364,"code:js:true:1r8izbs":365},[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":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"id":33,"slug":34,"title":35,"subtitle":36,"excerpt":37,"coverUrl":38,"locale":13,"readingMinutes":39,"publishedAt":40,"viewCount":41,"likeCount":19,"commentCount":19,"author":42,"vertical":47,"topic":48,"tags":51,"_count":63,"playground":65,"body":67,"bodyMd":207,"seo":208,"translationGroupId":212,"thread":213,"assessments":215,"translations":221,"quiz":223},"019fe660-fcbe-745e-a443-5ae33ac10aaf","broadcast-channel-cross-tab-messaging","Stop using the localStorage hack to sync browser tabs. BroadcastChannel does it natively.",null,"Syncing state across browser tabs with localStorage events is a widely-used trick that requires JSON.stringify, event filtering, and careful cleanup. The Broadcast Channel API delivers messages between tabs directly, with none of the side effects.","\u002Fmedia\u002Fcovers\u002Fbroadcast-channel-cross-tab-messaging.png",4,"2026-08-09T07:02:41.629Z",55,{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":49,"name":50},"javascript","JavaScript",[52,54,57,60],{"slug":49,"name":53,"color":36},"Javascript",{"slug":55,"name":56,"color":36},"webdev","Webdev",{"slug":58,"name":59,"color":36},"frontend","Frontend",{"slug":61,"name":62,"color":36},"apis","Apis",{"assessments":64},1,{"slug":34,"title":66},"BroadcastChannel — interactive playground",{"blocks":68,"version":64},[69,73,77,83,86,89,93,96,99,103,106,109,112,116,119,123,126,129,132,139,142,145,148,151,154,157,160,163,166,169,172,175,178,181,184,187,190,193,196,199],{"id":70,"html":71,"type":72},"b1","\u003Cp>When a user logs out in one tab, the other tabs should follow. When they update their cart, every open window should reflect it. The common solution is a localStorage trick: write a sentinel value, listen for the \u003Ccode>storage\u003C\u002Fcode> event, read it, parse it, check if it&#39;s &quot;for you,&quot; and clean it up. It works — but it&#39;s a side-channel communication pattern built on a persistence API that was never meant for messaging. The Broadcast Channel API is the direct path.\u003C\u002Fp>","paragraph",{"id":74,"html":75,"text":75,"type":76,"level":31},"b2","The API","heading",{"id":78,"code":79,"type":80,"language":81,"highlight":82},"b3","\u002F\u002F Sender (any tab, worker, or iframe on the same origin)\nconst channel = new BroadcastChannel('app-sync');\nchannel.postMessage({ type: 'LOGOUT' });\n\n\u002F\u002F Receiver (every other context subscribed to the same name)\nconst channel = new BroadcastChannel('app-sync');\nchannel.onmessage = (event) => {\n  console.log(event.data); \u002F\u002F { type: 'LOGOUT' }\n};","code","js",[],{"id":84,"html":85,"type":72},"b4","\u003Cp>Two steps: open a channel by name, then send or listen. Any tab, worker, or iframe on the same origin that opens a channel with the same name receives every message sent on it — including messages sent \u003Cem>after\u003C\u002Fem> they subscribed. The sender does not receive its own messages.\u003C\u002Fp>",{"id":87,"html":88,"type":72},"b5","\u003Cp>Close the channel when you&#39;re done to release the listener:\u003C\u002Fp>",{"id":90,"code":91,"type":80,"language":81,"highlight":92},"b6","channel.close();",[],{"id":94,"html":95,"text":95,"type":76,"level":31},"b7","What the localStorage approach actually looks like",{"id":97,"html":98,"type":72},"b8","\u003Cp>The typical cross-tab sync pattern using \u003Ccode>storage\u003C\u002Fcode> events:\u003C\u002Fp>",{"id":100,"code":101,"type":80,"language":81,"highlight":102},"b9","\u002F\u002F Sender\nlocalStorage.setItem('__broadcast', JSON.stringify({ type: 'LOGOUT', t: Date.now() }));\nlocalStorage.removeItem('__broadcast'); \u002F\u002F clean up immediately\n\n\u002F\u002F Receiver\nwindow.addEventListener('storage', (event) => {\n  if (event.key !== '__broadcast') return; \u002F\u002F filter noise\n  if (!event.newValue) return;             \u002F\u002F ignore the removeItem\n  const message = JSON.parse(event.newValue);\n  if (message.type === 'LOGOUT') { \u002F* handle *\u002F }\n});",[],{"id":104,"html":105,"type":72},"b10","\u003Cp>Every part of this is load-bearing workaround: the timestamp prevents deduplication if the same value is sent twice; the \u003Ccode>removeItem\u003C\u002Fcode> triggers a second storage event that must be filtered out; \u003Ccode>JSON.stringify\u003C\u002Fcode>\u002F\u003Ccode>JSON.parse\u003C\u002Fcode> is required because storage only holds strings. BroadcastChannel replaces the entire block with a \u003Ccode>postMessage\u003C\u002Fcode> call.\u003C\u002Fp>",{"id":107,"html":108,"text":108,"type":76,"level":31},"b11","Real-world use cases",{"id":110,"html":111,"type":72},"b12","\u003Cp>\u003Cstrong>Logout across all tabs.\u003C\u002Fstrong> When the user logs out, invalidate the session in every open window simultaneously:\u003C\u002Fp>",{"id":113,"code":114,"type":80,"language":81,"highlight":115},"b13","\u002F\u002F auth.js — runs in every tab\nconst syncChannel = new BroadcastChannel('auth');\n\nexport function logout() {\n  clearSession();\n  syncChannel.postMessage({ type: 'SESSION_ENDED' });\n  redirect('\u002Flogin');\n}\n\nsyncChannel.onmessage = (event) => {\n  if (event.data.type === 'SESSION_ENDED') {\n    clearSession();\n    redirect('\u002Flogin');\n  }\n};",[],{"id":117,"html":118,"type":72},"b14","\u003Cp>\u003Cstrong>Cart sync in an e-commerce app.\u003C\u002Fstrong> Add to cart in one tab, see the count update in the header of every other tab:\u003C\u002Fp>",{"id":120,"code":121,"type":80,"language":81,"highlight":122},"b15","const cartChannel = new BroadcastChannel('cart');\n\nfunction addToCart(item) {\n  const updated = updateLocalCart(item);\n  cartChannel.postMessage({ type: 'CART_UPDATED', cart: updated });\n  renderCart(updated);\n}\n\ncartChannel.onmessage = (event) => {\n  if (event.data.type === 'CART_UPDATED') {\n    renderCart(event.data.cart);\n  }\n};",[],{"id":124,"html":125,"type":72},"b16","\u003Cp>\u003Cstrong>Live config refresh.\u003C\u002Fstrong> When an admin changes a feature flag in a settings tab, broadcast the update so every other open tab picks it up without a page reload.\u003C\u002Fp>",{"id":127,"html":128,"text":128,"type":76,"level":31},"b17","What you can send",{"id":130,"html":131,"type":72},"b18","\u003Cp>BroadcastChannel uses the \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\u002FStructured_clone_algorithm\">structured clone algorithm\u003C\u002Fa> — the same one used by \u003Ccode>structuredClone()\u003C\u002Fcode> and \u003Ccode>postMessage()\u003C\u002Fcode> on workers. That means you can send:\u003C\u002Fp>",{"id":133,"type":134,"items":135,"ordered":18},"b19","list",[136,137,138],"Plain objects and arrays (including nested)","\u003Ccode>Date\u003C\u002Fcode>, \u003Ccode>Map\u003C\u002Fcode>, \u003Ccode>Set\u003C\u002Fcode>, \u003Ccode>ArrayBuffer\u003C\u002Fcode>, \u003Ccode>Blob\u003C\u002Fcode>","Primitive values — strings, numbers, booleans, \u003Ccode>null\u003C\u002Fcode>",{"id":140,"html":141,"type":72},"b20","\u003Cp>You \u003Cstrong>cannot\u003C\u002Fstrong> send functions, DOM nodes, or anything not serializable by structured clone. If you try, the call throws a \u003Ccode>DataCloneError\u003C\u002Fcode>. For the message payloads most apps actually use — event objects with typed fields — structured clone covers everything without the JSON roundtrip.\u003C\u002Fp>",{"id":143,"html":144,"text":144,"type":76,"level":31},"b21","Scope and limits",{"id":146,"html":147,"type":72},"b22","\u003Cp>BroadcastChannel is scoped to \u003Cstrong>same-origin contexts\u003C\u002Fstrong> — same protocol, hostname, and port. A channel named \u003Ccode>&#39;app-sync&#39;\u003C\u002Fcode> on \u003Ccode>https:\u002F\u002Fexample.com\u003C\u002Fcode> is completely isolated from a channel with the same name on \u003Ccode>https:\u002F\u002Fstaging.example.com\u003C\u002Fcode>. You cannot use it to communicate between different origins.\u003C\u002Fp>",{"id":149,"html":150,"type":72},"b23","\u003Cp>The channel name is your namespace. If multiple features in your app use BroadcastChannel, give each a distinct name (\u003Ccode>&#39;auth&#39;\u003C\u002Fcode>, \u003Ccode>&#39;cart&#39;\u003C\u002Fcode>, \u003Ccode>&#39;notifications&#39;\u003C\u002Fcode>) rather than sharing a single \u003Ccode>&#39;app&#39;\u003C\u002Fcode> channel and multiplexing message types through it — separate channels are cleaner and don&#39;t require filtering.\u003C\u002Fp>",{"id":152,"html":153,"text":153,"type":76,"level":31},"b24","Browser support",{"id":155,"html":156,"type":72},"b25","\u003Cp>BroadcastChannel is \u003Cstrong>Baseline 2022\u003C\u002Fstrong>: Chrome 54 (2016), Firefox 38 (2015), Safari 15.4 (March 2022). The API has been in Chromium and Firefox for nearly a decade; Safari joined in 2022. It&#39;s available in all currently-supported browser versions and in Web Workers and Service Workers, not just the main thread.\u003C\u002Fp>",{"id":158,"html":159,"type":72},"b26","\u003C!-- playground:start -->",{"id":161,"html":162,"text":162,"type":76,"level":31},"b27","🎮 Try it yourself",{"id":164,"html":165,"type":72},"b28","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-08-09-broadcast-channel-cross-tab-messaging\u002Fplayground\u002F\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":167,"html":168,"type":72},"b29","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":170,"html":171,"type":72},"b30","\u003C!-- playground:end -->",{"id":173,"html":174,"type":72},"b31","\u003C!-- quiz:start -->",{"id":176,"html":177,"text":177,"type":76,"level":31},"b32","🧠 Test yourself",{"id":179,"html":180,"type":72},"b33","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-08-09-broadcast-channel-cross-tab-messaging\">Take the 9-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":182,"html":183,"type":72},"b34","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":185,"html":186,"type":72},"b35","\u003C!-- quiz:end -->",{"id":188,"html":189,"text":189,"type":76,"level":31},"b36","The takeaway",{"id":191,"html":192,"type":72},"b37","\u003Cp>Search your codebase for \u003Ccode>storage\u003C\u002Fcode> event listeners paired with a \u003Ccode>localStorage.setItem\u003C\u002Fcode> that immediately gets removed. That pattern is cross-tab messaging through a storage side-channel — exactly what BroadcastChannel exists to replace. Swap it out: open a channel by name, call \u003Ccode>postMessage\u003C\u002Fcode>, listen with \u003Ccode>onmessage\u003C\u002Fcode>. You get structured data without serialization, no storage event noise to filter, and no cleanup sentinel to manage. The intent becomes clear in the code; the runtime handles the delivery.\u003C\u002Fp>",{"id":194,"type":195},"b38","divider",{"id":197,"html":198,"type":72},"b39","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":200,"type":134,"items":201,"ordered":18},"b40",[202,203,204,205,206],"⭐ \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 logs out in one tab, the other tabs should follow. When they update their cart, every open window should reflect it. The common solution is a localStorage trick: write a sentinel value, listen for the `storage` event, read it, parse it, check if it's \"for you,\" and clean it up. It works — but it's a side-channel communication pattern built on a persistence API that was never meant for messaging. The Broadcast Channel API is the direct path.\n\n## The API\n\n```js\n\u002F\u002F Sender (any tab, worker, or iframe on the same origin)\nconst channel = new BroadcastChannel('app-sync');\nchannel.postMessage({ type: 'LOGOUT' });\n\n\u002F\u002F Receiver (every other context subscribed to the same name)\nconst channel = new BroadcastChannel('app-sync');\nchannel.onmessage = (event) => {\n  console.log(event.data); \u002F\u002F { type: 'LOGOUT' }\n};\n```\n\nTwo steps: open a channel by name, then send or listen. Any tab, worker, or iframe on the same origin that opens a channel with the same name receives every message sent on it — including messages sent *after* they subscribed. The sender does not receive its own messages.\n\nClose the channel when you're done to release the listener:\n\n```js\nchannel.close();\n```\n\n## What the localStorage approach actually looks like\n\nThe typical cross-tab sync pattern using `storage` events:\n\n```js\n\u002F\u002F Sender\nlocalStorage.setItem('__broadcast', JSON.stringify({ type: 'LOGOUT', t: Date.now() }));\nlocalStorage.removeItem('__broadcast'); \u002F\u002F clean up immediately\n\n\u002F\u002F Receiver\nwindow.addEventListener('storage', (event) => {\n  if (event.key !== '__broadcast') return; \u002F\u002F filter noise\n  if (!event.newValue) return;             \u002F\u002F ignore the removeItem\n  const message = JSON.parse(event.newValue);\n  if (message.type === 'LOGOUT') { \u002F* handle *\u002F }\n});\n```\n\nEvery part of this is load-bearing workaround: the timestamp prevents deduplication if the same value is sent twice; the `removeItem` triggers a second storage event that must be filtered out; `JSON.stringify`\u002F`JSON.parse` is required because storage only holds strings. BroadcastChannel replaces the entire block with a `postMessage` call.\n\n## Real-world use cases\n\n**Logout across all tabs.** When the user logs out, invalidate the session in every open window simultaneously:\n\n```js\n\u002F\u002F auth.js — runs in every tab\nconst syncChannel = new BroadcastChannel('auth');\n\nexport function logout() {\n  clearSession();\n  syncChannel.postMessage({ type: 'SESSION_ENDED' });\n  redirect('\u002Flogin');\n}\n\nsyncChannel.onmessage = (event) => {\n  if (event.data.type === 'SESSION_ENDED') {\n    clearSession();\n    redirect('\u002Flogin');\n  }\n};\n```\n\n**Cart sync in an e-commerce app.** Add to cart in one tab, see the count update in the header of every other tab:\n\n```js\nconst cartChannel = new BroadcastChannel('cart');\n\nfunction addToCart(item) {\n  const updated = updateLocalCart(item);\n  cartChannel.postMessage({ type: 'CART_UPDATED', cart: updated });\n  renderCart(updated);\n}\n\ncartChannel.onmessage = (event) => {\n  if (event.data.type === 'CART_UPDATED') {\n    renderCart(event.data.cart);\n  }\n};\n```\n\n**Live config refresh.** When an admin changes a feature flag in a settings tab, broadcast the update so every other open tab picks it up without a page reload.\n\n## What you can send\n\nBroadcastChannel uses the [structured clone algorithm](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\u002FStructured_clone_algorithm) — the same one used by `structuredClone()` and `postMessage()` on workers. That means you can send:\n\n- Plain objects and arrays (including nested)\n- `Date`, `Map`, `Set`, `ArrayBuffer`, `Blob`\n- Primitive values — strings, numbers, booleans, `null`\n\nYou **cannot** send functions, DOM nodes, or anything not serializable by structured clone. If you try, the call throws a `DataCloneError`. For the message payloads most apps actually use — event objects with typed fields — structured clone covers everything without the JSON roundtrip.\n\n## Scope and limits\n\nBroadcastChannel is scoped to **same-origin contexts** — same protocol, hostname, and port. A channel named `'app-sync'` on `https:\u002F\u002Fexample.com` is completely isolated from a channel with the same name on `https:\u002F\u002Fstaging.example.com`. You cannot use it to communicate between different origins.\n\nThe channel name is your namespace. If multiple features in your app use BroadcastChannel, give each a distinct name (`'auth'`, `'cart'`, `'notifications'`) rather than sharing a single `'app'` channel and multiplexing message types through it — separate channels are cleaner and don't require filtering.\n\n## Browser support\n\nBroadcastChannel is **Baseline 2022**: Chrome 54 (2016), Firefox 38 (2015), Safari 15.4 (March 2022). The API has been in Chromium and Firefox for nearly a decade; Safari joined in 2022. It's available in all currently-supported browser versions and in Web Workers and Service Workers, not just the main thread.\n\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-08-09-broadcast-channel-cross-tab-messaging\u002Fplayground\u002F)**\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\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-08-09-broadcast-channel-cross-tab-messaging)**\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 `storage` event listeners paired with a `localStorage.setItem` that immediately gets removed. That pattern is cross-tab messaging through a storage side-channel — exactly what BroadcastChannel exists to replace. Swap it out: open a channel by name, call `postMessage`, listen with `onmessage`. You get structured data without serialization, no storage event noise to filter, and no cleanup sentinel to manage. The intent becomes clear in the code; the runtime handles the delivery.\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":209,"canonical":210,"description":211},"Stop using the localStorage hack to sync browser tabs. BroadcastChanne","https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fbroadcast-channel-cross-tab-messaging","Syncing state across browser tabs with localStorage events is a widely-used trick that requires JSON.stringify, event filtering, and careful cleanup. The Broadcast Channel API deli","019fe660-fcbe-745e-a443-5db97da0d9ba",{"id":214,"locked":18},"019fe661-03c5-77bc-9a10-0bcd6223f910",[216],{"id":217,"slug":34,"title":218,"_count":219},"019fe776-d1ef-7058-9ab7-5d1a725b756a","BroadcastChannel API — cross-tab messaging",{"questions":220},9,[222],{"locale":13,"slug":34},{"id":217,"slug":34,"title":218,"_count":224,"questionCount":220},{"questions":220},{"items":226,"meta":358},[227,247,266,278,300,318,338],{"id":228,"slug":229,"title":230,"subtitle":36,"excerpt":231,"coverUrl":232,"locale":13,"readingMinutes":39,"publishedAt":233,"viewCount":234,"likeCount":19,"commentCount":19,"author":235,"vertical":236,"topic":237,"tags":238,"_count":245,"playground":246,"hasQuiz":17,"hasPlayground":17},"019ff19b-f68d-77ea-9a2c-70e14c5c7a05","mutation-observer-dom-change-detection","You're polling setInterval to detect DOM changes. `MutationObserver` fires when they happen.","Watching for DOM mutations with setInterval or custom event dispatch is fragile and imprecise. The MutationObserver API delivers a callback exactly when attributes, text content, or child elements change — with full control over which types of mutations you watch.","\u002Fmedia\u002Fcovers\u002Fmutation-observer-dom-change-detection.png","2026-08-11T15:48:01.781Z",115,{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":49,"name":50},[239,240,241,242],{"slug":49,"name":53,"color":36},{"slug":55,"name":56,"color":36},{"slug":58,"name":59,"color":36},{"slug":243,"name":244,"color":36},"dom","Dom",{"assessments":64},{"slug":229},{"id":248,"slug":249,"title":250,"subtitle":36,"excerpt":251,"coverUrl":252,"locale":13,"readingMinutes":39,"publishedAt":253,"viewCount":41,"likeCount":19,"commentCount":19,"author":254,"vertical":255,"topic":256,"tags":257,"_count":264,"playground":265,"hasQuiz":17,"hasPlayground":17},"019ff19b-f5b3-7569-8cc8-22b1136aa9b2","compressionstream-native-gzip","You're importing pako to gzip data. `CompressionStream` does it natively.","pako is one of npm's most downloaded packages — pulled in by thousands of projects to compress data before storing or sending it. CompressionStream and DecompressionStream do the same job natively, in any modern browser and Node.js 18+, with no dependencies and native C-speed execution.","\u002Fmedia\u002Fcovers\u002Fcompressionstream-native-gzip.png","2026-08-10T07:49:27.000Z",{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":49,"name":50},[258,259,260,261],{"slug":49,"name":53,"color":36},{"slug":55,"name":56,"color":36},{"slug":58,"name":59,"color":36},{"slug":262,"name":263,"color":36},"performance","Performance",{"assessments":64},{"slug":249},{"id":33,"slug":34,"title":35,"subtitle":36,"excerpt":37,"coverUrl":38,"locale":13,"readingMinutes":39,"publishedAt":40,"viewCount":267,"likeCount":19,"commentCount":19,"author":268,"vertical":269,"topic":270,"tags":271,"_count":276,"playground":277,"hasQuiz":17,"hasPlayground":17},56,{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":49,"name":50},[272,273,274,275],{"slug":49,"name":53,"color":36},{"slug":55,"name":56,"color":36},{"slug":58,"name":59,"color":36},{"slug":61,"name":62,"color":36},{"assessments":64},{"slug":34},{"id":279,"slug":280,"title":281,"subtitle":36,"excerpt":282,"coverUrl":283,"locale":13,"readingMinutes":284,"publishedAt":285,"viewCount":286,"likeCount":19,"commentCount":19,"author":287,"vertical":288,"topic":289,"tags":292,"_count":298,"playground":299,"hasQuiz":17,"hasPlayground":17},"019ff19b-f388-73f8-aa5b-93d73337d6fd","dialog-element-native-modal","You built your modal with a `\u003Cdiv>` and a focus trap library. The native `\u003Cdialog>` does all of that.","The `\u003Cdialog>` element ships with focus trapping, Escape-to-close, backdrop rendering, and correct ARIA semantics — no library, no boilerplate. It's been Baseline since 2022.","\u002Fmedia\u002Fcovers\u002Fdialog-element-native-modal.png",5,"2026-08-08T07:00:10.810Z",52,{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":290,"name":291},"html","HTML",[293,295,296,297],{"slug":290,"name":294,"color":36},"Html",{"slug":55,"name":56,"color":36},{"slug":58,"name":59,"color":36},{"slug":49,"name":53,"color":36},{"assessments":64},{"slug":280},{"id":301,"slug":302,"title":303,"subtitle":36,"excerpt":304,"coverUrl":305,"locale":13,"readingMinutes":306,"publishedAt":307,"viewCount":286,"likeCount":19,"commentCount":19,"author":308,"vertical":309,"topic":310,"tags":311,"_count":316,"playground":317,"hasQuiz":17,"hasPlayground":17},"019fe660-f552-73c8-9f52-d036e672c053","crypto-randomuuid-native-uuid","Stop importing `uuid`. `crypto.randomUUID()` has been native since 2021.","The `uuid` package is downloaded hundreds of millions of times a week. Most installs exist to call uuid\u002Fv4 — exactly what `crypto.randomUUID()` does natively, with no import, no bundle cost, and guaranteed cryptographic randomness.","\u002Fmedia\u002Fcovers\u002Fcrypto-randomuuid-native-uuid.png",3,"2026-08-08T07:00:09.988Z",{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":49,"name":50},[312,313,314,315],{"slug":49,"name":53,"color":36},{"slug":55,"name":56,"color":36},{"slug":58,"name":59,"color":36},{"slug":262,"name":263,"color":36},{"assessments":64},{"slug":302},{"id":319,"slug":320,"title":321,"subtitle":36,"excerpt":322,"coverUrl":323,"locale":13,"readingMinutes":39,"publishedAt":324,"viewCount":325,"likeCount":19,"commentCount":19,"author":326,"vertical":327,"topic":328,"tags":329,"_count":336,"playground":337,"hasQuiz":17,"hasPlayground":17},"019fe660-edbb-714e-b0e8-8488ccbb6940","intl-relativetimeformat-relative-dates","You're installing date-fns for 'X hours ago'. `Intl.RelativeTimeFormat` does it natively.","Most apps display relative timestamps — '3 minutes ago', 'last week'. Developers still reach for date-fns or moment for this. The Intl.RelativeTimeFormat API handles it natively, with full locale support and zero dependencies.","\u002Fmedia\u002Fcovers\u002Fintl-relativetimeformat-relative-dates.png","2026-08-07T07:22:54.352Z",18,{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":49,"name":50},[330,331,332,333],{"slug":49,"name":53,"color":36},{"slug":55,"name":56,"color":36},{"slug":58,"name":59,"color":36},{"slug":334,"name":335,"color":36},"intl","Intl",{"assessments":64},{"slug":320},{"id":339,"slug":340,"title":341,"subtitle":36,"excerpt":342,"coverUrl":343,"locale":13,"readingMinutes":39,"publishedAt":344,"viewCount":345,"likeCount":19,"commentCount":19,"author":346,"vertical":347,"topic":348,"tags":349,"_count":356,"playground":357,"hasQuiz":17,"hasPlayground":17},"019ff19b-f047-748a-800d-00912a4d95c9","object-hasown-safe-property-check","You use `obj.hasOwnProperty(key)` to check property ownership. It can silently fail. `Object.hasOwn()` is the safe replacement.","Two real cases where `obj.hasOwnProperty(key)` throws or returns wrong results — and why `Object.hasOwn(obj, key)` is the ES2022 fix that ESLint's `no-prototype-builtins` rule has been nudging you toward for years.","\u002Fmedia\u002Fcovers\u002Fobject-hasown-safe-property-check.png","2026-08-06T08:44:20.722Z",14,{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":49,"name":50},[350,351,352,353],{"slug":49,"name":53,"color":36},{"slug":55,"name":56,"color":36},{"slug":58,"name":59,"color":36},{"slug":354,"name":355,"color":36},"typescript","Typescript",{"assessments":64},{"slug":340},{"page":64,"perPage":359,"total":360,"totalPages":359},7,45,"\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F Sender (any tab, worker, or iframe on the same origin)\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> channel \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">BroadcastChannel\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'app-sync'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nchannel\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">postMessage\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> type\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'LOGOUT'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Receiver (every other context subscribed to the same name)\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> channel \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">BroadcastChannel\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'app-sync'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nchannel\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">onmessage\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>data\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F { type: 'LOGOUT' }\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-oneline\" data-lang=\"js\">channel\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">close\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F Sender\n\u003C\u002Fspan>localStorage\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">setItem\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'__broadcast'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">JSON\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">stringify\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> type\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'LOGOUT'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> t\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Date\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">now\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nlocalStorage\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">removeItem\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'__broadcast'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F clean up immediately\n\u003C\u002Fspan>\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Receiver\n\u003C\u002Fspan>window\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'storage'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>key \u003Cspan class=\"shj-oper\">!==\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'__broadcast'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F filter noise\n\u003C\u002Fspan>  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">!\u003C\u002Fspan>event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>newValue\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan>;             \u003Cspan class=\"shj-cmnt\">\u002F\u002F ignore the removeItem\n\u003C\u002Fspan>  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> message \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">JSON\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">parse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>newValue\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>message\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>type \u003Cspan class=\"shj-oper\">===\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'LOGOUT'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F* handle *\u002F\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F auth.js — runs in every tab\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> syncChannel \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">BroadcastChannel\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'auth'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">logout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-func\">clearSession\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  syncChannel\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">postMessage\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> type\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'SESSION_ENDED'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">redirect\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'\u002Flogin'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\nsyncChannel\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">onmessage\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>data\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>type \u003Cspan class=\"shj-oper\">===\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'SESSION_ENDED'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-func\">clearSession\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-func\">redirect\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'\u002Flogin'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> cartChannel \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">BroadcastChannel\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'cart'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">addToCart\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>item\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> updated \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">updateLocalCart\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>item\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  cartChannel\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">postMessage\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> type\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'CART_UPDATED'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> cart\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> updated \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">renderCart\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>updated\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\ncartChannel\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">onmessage\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>data\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>type \u003Cspan class=\"shj-oper\">===\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'CART_UPDATED'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-func\">renderCart\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>data\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>cart\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>"]