[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-cookie-store-api-async-cookies":44,"search-suggestions":247,"related-cookie-store-api-async-cookies":285,"code:js:true:2er0gt":413,"code:js:true:1lby6aq":414,"code:js:true:1uk29":415,"code:js:true:1cvi8ti":416,"code:js:true:1ev2vn0":417,"comments-01a05275-0841-74b8-9d17-108d35eb9cd5":418},[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,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":53,"likeCount":19,"commentCount":19,"author":54,"vertical":59,"topic":60,"tags":63,"_count":75,"playground":77,"body":79,"bodyMd":230,"seo":231,"translationGroupId":234,"series":48,"podcastUrl":48,"verticalId":5,"thread":235,"assessments":237,"translations":243,"quiz":245},"01a05275-0841-74b8-9d17-108d35eb9cd5","cookie-store-api-async-cookies","Stop Regex-Parsing document.cookie. Use CookieStore",null,"document.cookie hands you one flat string and no way to know when it changes. The Cookie Store API gives you async get\u002Fset\u002Fdelete and a change event — but its set() defaults to a stricter SameSite than the one you're used to.","\u002Fmedia\u002Fcovers\u002Fcookie-store-api-async-cookies.png",5,"2026-09-04T10:57:34.746Z",27,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},"javascript","JavaScript",[64,67,70,72],{"slug":65,"name":66,"color":48},"webdev","Webdev",{"slug":68,"name":69,"color":48},"frontend","Frontend",{"slug":61,"name":71,"color":48},"Javascript",{"slug":73,"name":74,"color":48},"browser","Browser",{"assessments":76},1,{"slug":46,"title":78},"document.cookie vs cookieStore — interactive playground",{"blocks":80,"version":76},[81,85,91,94,97,101,104,111,114,118,121,124,128,131,135,138,142,145,148,151,154,157,160,164,167,170,173,176,179,183,186,191,194,197,200,203,206,209,212,215,218,221,224],{"id":82,"html":83,"type":84},"b1","\u003Cp>Open any codebase old enough to have cookies in it and grep for \u003Ccode>document.cookie\u003C\u002Fcode>. You will find a function that looks like this, written by someone who is no longer at the company:\u003C\u002Fp>","paragraph",{"id":86,"code":87,"type":88,"language":89,"highlight":90},"b2","function getCookie(name) {\n  const match = document.cookie.match(\n    new RegExp(\"(^| )\" + name + \"=([^;]+)\")\n  );\n  return match ? decodeURIComponent(match[2]) : null;\n}","code","js",[],{"id":92,"html":93,"type":84},"b3","\u003Cp>It works. It has worked since 2011. It also has a small, well-known list of ways to get it wrong — cookie names that are prefixes of each other, values with unescaped \u003Ccode>=\u003C\u002Fcode> or \u003Ccode>;\u003C\u002Fcode>, whitespace after the semicolon depending on which browser wrote the header. Everyone&#39;s seen at least one of these bugs. Nobody rewrites the function, because it&#39;s not broken \u003Cem>today\u003C\u002Fem>.\u003C\u002Fp>",{"id":95,"html":96,"type":84},"b4","\u003Cp>Here&#39;s the harder problem that regex can&#39;t fix at all: \u003Cstrong>you have no way to know when a cookie changes.\u003C\u002Fstrong> Not from another tab. Not from a \u003Ccode>Set-Cookie\u003C\u002Fcode> header on a \u003Ccode>fetch()\u003C\u002Fcode> response. Not even from a second script on your own page calling \u003Ccode>document.cookie = ...\u003C\u002Fcode> a moment after yours did. \u003Ccode>document.cookie\u003C\u002Fcode> is a plain string property. Reading it tells you the current state. It has never told you \u003Cem>when\u003C\u002Fem> the state moved.\u003C\u002Fp>",{"id":98,"html":99,"text":99,"type":100,"level":31},"b5","What everyone reaches for instead","heading",{"id":102,"html":103,"type":84},"b6","\u003Cp>Once the &quot;I need to react to cookie changes&quot; requirement shows up — a login cookie set by an API call, a consent banner another tab just dismissed — the usual fixes are:\u003C\u002Fp>",{"id":105,"type":106,"items":107,"ordered":18},"b7","list",[108,109,110],"\u003Cstrong>Poll \u003Ccode>document.cookie\u003C\u002Fcode> on an interval.\u003C\u002Fstrong> It works, in the sense that a \u003Ccode>setInterval\u003C\u002Fcode> checking a string every 500ms will eventually notice a change. It also means every tab of every user is now diffing a string forever for an event that might happen once a session.","\u003Cstrong>Have the server tell the client via a WebSocket or SSE.\u003C\u002Fstrong> Real infrastructure for a problem that&#39;s purely local — the cookie already changed on this machine, in this browser, you just don&#39;t have a hook for it.","\u003Cstrong>Wrap every single place that writes a cookie in your own pub\u002Fsub.\u003C\u002Fstrong> This can work, right up until a third-party script, a \u003Ccode>Set-Cookie\u003C\u002Fcode> response header, or literally the browser&#39;s own cookie-jar expiry logic changes a cookie your pub\u002Fsub doesn&#39;t know about.",{"id":112,"html":113,"type":84},"b8","\u003Cp>All three treat &quot;the browser won&#39;t tell me&quot; as something to engineer around. It&#39;s worth asking why the browser won&#39;t tell you in the first place — and it turns out, more recently, it will.\u003C\u002Fp>",{"id":115,"html":116,"text":117,"type":100,"level":31},"b9","The API that actually does this: \u003Ccode>cookieStore\u003C\u002Fcode>","The API that actually does this: cookieStore",{"id":119,"html":120,"type":84},"b10","\u003Cp>Chrome and Edge ship \u003Ccode>window.cookieStore\u003C\u002Fcode> (and \u003Ccode>self.cookieStore\u003C\u002Fcode> inside a service worker) — a promise-based Cookie Store API that treats cookies as structured objects instead of one string you serialize by hand.\u003C\u002Fp>",{"id":122,"html":123,"type":84},"b11","\u003Cp>Reading is no longer a regex:\u003C\u002Fp>",{"id":125,"code":126,"type":88,"language":89,"highlight":127},"b12","const session = await cookieStore.get(\"session_id\");\n\u002F\u002F { name: \"session_id\", value: \"abc123\", domain: null, path: \"\u002F\", ... } or null\n\nconst all = await cookieStore.getAll();\n\u002F\u002F array of every cookie visible to this document, already parsed",[],{"id":129,"html":130,"type":84},"b13","\u003Cp>Writing takes an object instead of a hand-built \u003Ccode>key=value; path=...; expires=...\u003C\u002Fcode> string:\u003C\u002Fp>",{"id":132,"code":133,"type":88,"language":89,"highlight":134},"b14","await cookieStore.set({\n  name: \"theme\",\n  value: \"dark\",\n  expires: Date.now() + 1000 * 60 * 60 * 24 * 30, \u002F\u002F 30 days, in ms\n  path: \"\u002F\",\n});\n\nawait cookieStore.delete(\"theme\");",[],{"id":136,"html":137,"type":84},"b15","\u003Cp>And the part \u003Ccode>document.cookie\u003C\u002Fcode> could never do — a real event:\u003C\u002Fp>",{"id":139,"code":140,"type":88,"language":89,"highlight":141},"b16","cookieStore.addEventListener(\"change\", (event) => {\n  for (const cookie of event.changed) {\n    console.log(\"set:\", cookie.name, cookie.value);\n  }\n  for (const cookie of event.deleted) {\n    console.log(\"deleted:\", cookie.name);\n  }\n});",[],{"id":143,"html":144,"type":84},"b17","\u003Cp>That listener fires for cookies your page sets, cookies a \u003Ccode>fetch()\u003C\u002Fcode> response set via \u003Ccode>Set-Cookie\u003C\u002Fcode>, and cookies removed by expiry — no polling, no pub\u002Fsub you wrote yourself. A consent banner that gets dismissed in one tab can now update every other open tab of the same origin the moment it happens.\u003C\u002Fp>",{"id":146,"html":147,"type":84},"b18","\u003C!-- playground:start -->",{"id":149,"html":150,"text":150,"type":100,"level":31},"b19","🎮 Try it yourself",{"id":152,"html":153,"type":84},"b20","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcookie-store-api-async-cookies\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":155,"html":156,"type":84},"b21","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":158,"html":159,"type":84},"b22","\u003C!-- playground:end -->",{"id":161,"html":162,"text":163,"type":100,"level":31},"b23","The default that&#39;s stricter than you&#39;re used to","The default that's stricter than you're used to",{"id":165,"html":166,"type":84},"b24","\u003Cp>Here&#39;s the thing that actually catches people moving existing code over, and it&#39;s not a bug — it&#39;s a deliberate spec choice that reads like a footnote until it breaks something.\u003C\u002Fp>",{"id":168,"html":169,"type":84},"b25","\u003Cp>When you write a cookie the old way, through a \u003Ccode>Set-Cookie\u003C\u002Fcode> header or \u003Ccode>document.cookie\u003C\u002Fcode>, and you don&#39;t specify \u003Ccode>SameSite\u003C\u002Fcode>, browsers default it to \u003Ccode>Lax\u003C\u002Fcode>. That&#39;s been true for years — it&#39;s why a cookie set on your site still rides along when a user clicks a plain link \u003Cem>to\u003C\u002Fem> your site from somewhere else, but doesn&#39;t get sent on a cross-site \u003Ccode>POST\u003C\u002Fcode>.\u003C\u002Fp>",{"id":171,"html":172,"type":84},"b26","\u003Cp>\u003Ccode>cookieStore.set()\u003C\u002Fcode> doesn&#39;t inherit that default. Per the spec, if you don&#39;t pass \u003Ccode>sameSite\u003C\u002Fcode> explicitly, it defaults to \u003Cstrong>\u003Ccode>&quot;strict&quot;\u003C\u002Fcode>\u003C\u002Fstrong> — stricter than what \u003Ccode>document.cookie\u003C\u002Fcode> gives you for free. A \u003Ccode>Strict\u003C\u002Fcode> cookie is withheld on \u003Cem>any\u003C\u002Fem> cross-site navigation, top-level link clicks included.\u003C\u002Fp>",{"id":174,"html":175,"type":84},"b27","\u003Cp>So the failure mode looks like this: you migrate a cookie-setting line from \u003Ccode>document.cookie = &quot;...&quot;\u003C\u002Fcode> to \u003Ccode>cookieStore.set({...})\u003C\u002Fcode>, run your test suite, ship it. Everything that happens \u003Cem>inside\u003C\u002Fem> your own site keeps working, because same-site requests don&#39;t care about \u003Ccode>SameSite\u003C\u002Fcode> at all. Weeks later, someone clicks a link to your site from an email or a partner site, lands on a page that expects that cookie to already be there, and it isn&#39;t. No error. No console warning. The cookie you set is simply not attached to that request, because \u003Ccode>Strict\u003C\u002Fcode> said not to.\u003C\u002Fp>",{"id":177,"html":178,"type":84},"b28","\u003Cp>The fix is one keyword, once you know to look for it:\u003C\u002Fp>",{"id":180,"code":181,"type":88,"language":89,"highlight":182},"b29","await cookieStore.set({\n  name: \"session_id\",\n  value: token,\n  sameSite: \"lax\", \u002F\u002F match what document.cookie would have given you\n});",[],{"id":184,"html":185,"text":185,"type":100,"level":31},"b30","Two things worth knowing before you reach for it",{"id":187,"type":106,"items":188,"ordered":18},"b31",[189,190],"\u003Cstrong>It&#39;s Chromium-only right now.\u003C\u002Fstrong> Chrome and Edge support \u003Ccode>cookieStore\u003C\u002Fcode>; Firefox and Safari don&#39;t ship it as of this writing. Check the current numbers on caniuse before you rely on it for anything that isn&#39;t wrapped in a feature check — \u003Ccode>if (&quot;cookieStore&quot; in window)\u003C\u002Fcode> — with a \u003Ccode>document.cookie\u003C\u002Fcode> fallback.","\u003Cstrong>It requires a secure context.\u003C\u002Fstrong> Like most newer, more capable browser APIs, \u003Ccode>cookieStore\u003C\u002Fcode> simply isn&#39;t there on plain \u003Ccode>http:\u002F\u002F\u003C\u002Fcode> origins outside \u003Ccode>localhost\u003C\u002Fcode>. If it&#39;s \u003Ccode>undefined\u003C\u002Fcode> in production but present when you test locally, that&#39;s almost certainly why.",{"id":192,"html":193,"text":193,"type":100,"level":31},"b32","The takeaway",{"id":195,"html":196,"type":84},"b33","\u003Cp>\u003Ccode>document.cookie\u003C\u002Fcode> was never designed to be parsed — it&#39;s a string interface bolted onto a feature that predates \u003Ccode>JSON.parse\u003C\u002Fcode> existing. \u003Ccode>cookieStore\u003C\u002Fcode> treats cookies as the structured, awaitable, observable data they actually are, and the \u003Ccode>change\u003C\u002Fcode> event alone is worth the migration for anything that needs to react to a cookie set outside your own code. Just don&#39;t let \u003Ccode>sameSite\u003C\u002Fcode> default silently to something stricter than the behavior you were relying on.\u003C\u002Fp>",{"id":198,"html":199,"type":84},"b34","\u003Cp>Does your codebase still have a hand-rolled cookie parser in it? How old is it, and does anyone remember writing it?\u003C\u002Fp>",{"id":201,"html":202,"type":84},"b35","\u003C!-- quiz:start -->",{"id":204,"html":205,"text":205,"type":100,"level":31},"b36","🧠 Test yourself",{"id":207,"html":208,"type":84},"b37","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcookie-store-api-async-cookies\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":210,"html":211,"type":84},"b38","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":213,"html":214,"type":84},"b39","\u003C!-- quiz:end -->",{"id":216,"type":217},"b40","divider",{"id":219,"html":220,"type":84},"b41","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":222,"html":223,"type":84},"b42","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":225,"type":106,"items":226,"ordered":18},"b43",[227,228,229],"⭐ \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>","Open any codebase old enough to have cookies in it and grep for `document.cookie`. You will find a function that looks like this, written by someone who is no longer at the company:\n\n```js\nfunction getCookie(name) {\n  const match = document.cookie.match(\n    new RegExp(\"(^| )\" + name + \"=([^;]+)\")\n  );\n  return match ? decodeURIComponent(match[2]) : null;\n}\n```\n\nIt works. It has worked since 2011. It also has a small, well-known list of ways to get it wrong — cookie names that are prefixes of each other, values with unescaped `=` or `;`, whitespace after the semicolon depending on which browser wrote the header. Everyone's seen at least one of these bugs. Nobody rewrites the function, because it's not broken *today*.\n\nHere's the harder problem that regex can't fix at all: **you have no way to know when a cookie changes.** Not from another tab. Not from a `Set-Cookie` header on a `fetch()` response. Not even from a second script on your own page calling `document.cookie = ...` a moment after yours did. `document.cookie` is a plain string property. Reading it tells you the current state. It has never told you *when* the state moved.\n\n## What everyone reaches for instead\n\nOnce the \"I need to react to cookie changes\" requirement shows up — a login cookie set by an API call, a consent banner another tab just dismissed — the usual fixes are:\n\n- **Poll `document.cookie` on an interval.** It works, in the sense that a `setInterval` checking a string every 500ms will eventually notice a change. It also means every tab of every user is now diffing a string forever for an event that might happen once a session.\n- **Have the server tell the client via a WebSocket or SSE.** Real infrastructure for a problem that's purely local — the cookie already changed on this machine, in this browser, you just don't have a hook for it.\n- **Wrap every single place that writes a cookie in your own pub\u002Fsub.** This can work, right up until a third-party script, a `Set-Cookie` response header, or literally the browser's own cookie-jar expiry logic changes a cookie your pub\u002Fsub doesn't know about.\n\nAll three treat \"the browser won't tell me\" as something to engineer around. It's worth asking why the browser won't tell you in the first place — and it turns out, more recently, it will.\n\n## The API that actually does this: `cookieStore`\n\nChrome and Edge ship `window.cookieStore` (and `self.cookieStore` inside a service worker) — a promise-based Cookie Store API that treats cookies as structured objects instead of one string you serialize by hand.\n\nReading is no longer a regex:\n\n```js\nconst session = await cookieStore.get(\"session_id\");\n\u002F\u002F { name: \"session_id\", value: \"abc123\", domain: null, path: \"\u002F\", ... } or null\n\nconst all = await cookieStore.getAll();\n\u002F\u002F array of every cookie visible to this document, already parsed\n```\n\nWriting takes an object instead of a hand-built `key=value; path=...; expires=...` string:\n\n```js\nawait cookieStore.set({\n  name: \"theme\",\n  value: \"dark\",\n  expires: Date.now() + 1000 * 60 * 60 * 24 * 30, \u002F\u002F 30 days, in ms\n  path: \"\u002F\",\n});\n\nawait cookieStore.delete(\"theme\");\n```\n\nAnd the part `document.cookie` could never do — a real event:\n\n```js\ncookieStore.addEventListener(\"change\", (event) => {\n  for (const cookie of event.changed) {\n    console.log(\"set:\", cookie.name, cookie.value);\n  }\n  for (const cookie of event.deleted) {\n    console.log(\"deleted:\", cookie.name);\n  }\n});\n```\n\nThat listener fires for cookies your page sets, cookies a `fetch()` response set via `Set-Cookie`, and cookies removed by expiry — no polling, no pub\u002Fsub you wrote yourself. A consent banner that gets dismissed in one tab can now update every other open tab of the same origin the moment it happens.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcookie-store-api-async-cookies\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## The default that's stricter than you're used to\n\nHere's the thing that actually catches people moving existing code over, and it's not a bug — it's a deliberate spec choice that reads like a footnote until it breaks something.\n\nWhen you write a cookie the old way, through a `Set-Cookie` header or `document.cookie`, and you don't specify `SameSite`, browsers default it to `Lax`. That's been true for years — it's why a cookie set on your site still rides along when a user clicks a plain link *to* your site from somewhere else, but doesn't get sent on a cross-site `POST`.\n\n`cookieStore.set()` doesn't inherit that default. Per the spec, if you don't pass `sameSite` explicitly, it defaults to **`\"strict\"`** — stricter than what `document.cookie` gives you for free. A `Strict` cookie is withheld on *any* cross-site navigation, top-level link clicks included.\n\nSo the failure mode looks like this: you migrate a cookie-setting line from `document.cookie = \"...\"` to `cookieStore.set({...})`, run your test suite, ship it. Everything that happens *inside* your own site keeps working, because same-site requests don't care about `SameSite` at all. Weeks later, someone clicks a link to your site from an email or a partner site, lands on a page that expects that cookie to already be there, and it isn't. No error. No console warning. The cookie you set is simply not attached to that request, because `Strict` said not to.\n\nThe fix is one keyword, once you know to look for it:\n\n```js\nawait cookieStore.set({\n  name: \"session_id\",\n  value: token,\n  sameSite: \"lax\", \u002F\u002F match what document.cookie would have given you\n});\n```\n\n## Two things worth knowing before you reach for it\n\n- **It's Chromium-only right now.** Chrome and Edge support `cookieStore`; Firefox and Safari don't ship it as of this writing. Check the current numbers on caniuse before you rely on it for anything that isn't wrapped in a feature check — `if (\"cookieStore\" in window)` — with a `document.cookie` fallback.\n- **It requires a secure context.** Like most newer, more capable browser APIs, `cookieStore` simply isn't there on plain `http:\u002F\u002F` origins outside `localhost`. If it's `undefined` in production but present when you test locally, that's almost certainly why.\n\n## The takeaway\n\n`document.cookie` was never designed to be parsed — it's a string interface bolted onto a feature that predates `JSON.parse` existing. `cookieStore` treats cookies as the structured, awaitable, observable data they actually are, and the `change` event alone is worth the migration for anything that needs to react to a cookie set outside your own code. Just don't let `sameSite` default silently to something stricter than the behavior you were relying on.\n\nDoes your codebase still have a hand-rolled cookie parser in it? How old is it, and does anyone remember writing it?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcookie-store-api-async-cookies\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\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\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)",{"title":47,"canonical":232,"description":233},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcookie-store-api-async-cookies","document.cookie hands you one flat string and no way to know when it changes. The Cookie Store API gives you async get\u002Fset\u002Fdelete and a change event — but its set() defaults to a s","01a05275-0841-74b8-9d17-176e844abc7b",{"id":236,"locked":18},"01a05275-0863-757a-878d-60eb8f94e053",[238],{"id":239,"slug":46,"title":240,"_count":241},"01a05275-0880-75d5-a019-b2ea8bdb6cd0","document.cookie vs. the Cookie Store API",{"questions":242},7,[244],{"locale":13,"slug":46},{"id":239,"slug":46,"title":240,"_count":246,"questionCount":242},{"questions":242},[248,250,252,254,258,262,266,270,274,278,281,284],{"slug":65,"name":66,"articles":249},80,{"slug":68,"name":69,"articles":251},68,{"slug":61,"name":71,"articles":253},67,{"slug":255,"name":256,"articles":257},"css","Css",29,{"slug":259,"name":260,"articles":261},"tutorial","Tutorial",17,{"slug":263,"name":264,"articles":265},"typescript","Typescript",12,{"slug":267,"name":268,"articles":269},"performance","Performance",10,{"slug":271,"name":272,"articles":273},"react","React",8,{"slug":275,"name":276,"articles":277},"node","Node",6,{"slug":279,"name":280,"articles":277},"grammar","Grammar",{"slug":282,"name":283,"articles":51},"html","Html",{"slug":73,"name":74,"articles":51},{"items":286,"meta":412},[287,299,317,335,356,374,392],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":288,"likeCount":19,"commentCount":19,"author":289,"vertical":290,"topic":291,"tags":292,"_count":297,"playground":298,"hasQuiz":17,"hasPlayground":17},28,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[293,294,295,296],{"slug":65,"name":66,"color":48},{"slug":61,"name":71,"color":48},{"slug":73,"name":74,"color":48},{"slug":68,"name":69,"color":48},{"assessments":76},{"slug":46},{"id":300,"slug":301,"title":302,"subtitle":48,"excerpt":303,"coverUrl":304,"locale":13,"readingMinutes":51,"publishedAt":305,"viewCount":306,"likeCount":19,"commentCount":19,"author":307,"vertical":308,"topic":309,"tags":310,"_count":315,"playground":316,"hasQuiz":17,"hasPlayground":17},"01a04d81-723b-725d-9e99-7e11d03aa56a","clipboard-writetext-focus-bug","The await That Silently Breaks navigator.clipboard.writeText()","navigator.clipboard.writeText() only works while the document is focused and the user gesture is still fresh. Any await before the call can let both expire, so a copy button fails with an uncaught NotAllowedError and nobody ever sees why.","\u002Fmedia\u002Fcovers\u002Fclipboard-writetext-focus-bug.png","2026-09-03T17:23:32.218Z",108,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[311,312,313,314],{"slug":65,"name":66,"color":48},{"slug":61,"name":71,"color":48},{"slug":73,"name":74,"color":48},{"slug":68,"name":69,"color":48},{"assessments":76},{"slug":301},{"id":318,"slug":319,"title":320,"subtitle":48,"excerpt":321,"coverUrl":322,"locale":13,"readingMinutes":51,"publishedAt":323,"viewCount":324,"likeCount":19,"commentCount":19,"author":325,"vertical":326,"topic":327,"tags":328,"_count":333,"playground":334,"hasQuiz":17,"hasPlayground":17},"01a0499d-bb9f-709b-9e8a-9c426ae95530","declarative-shadow-dom-ssr-web-components","Shadow DOM Needed JavaScript to Exist. Now It Doesn't.","attachShadow() is a JavaScript-only API with no HTML serialization, so a server-rendered web component ships an empty host element until the client hydrates it. Declarative Shadow DOM attaches the real shadow root during HTML parsing — zero JS required.","\u002Fmedia\u002Fcovers\u002Fdeclarative-shadow-dom-ssr-web-components.png","2026-09-02T10:57:52.645Z",115,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[329,330,331,332],{"slug":65,"name":66,"color":48},{"slug":61,"name":71,"color":48},{"slug":282,"name":283,"color":48},{"slug":68,"name":69,"color":48},{"assessments":76},{"slug":319},{"id":336,"slug":337,"title":338,"subtitle":48,"excerpt":339,"coverUrl":340,"locale":13,"readingMinutes":341,"publishedAt":342,"viewCount":343,"likeCount":19,"commentCount":19,"author":344,"vertical":345,"topic":346,"tags":349,"_count":354,"playground":355,"hasQuiz":17,"hasPlayground":17},"01a0499d-b520-778f-8286-8221ec03b8b5","nextjs-weekly-cache-components-explained","Next.js Cache Components Explained (with Cheat Sheet)","How Next.js Cache Components decide what's static, what's cached, and what streams — the use cache directive, cacheLife, and Suspense explained.","\u002Fmedia\u002Fcovers\u002Fnextjs-weekly-cache-components-explained.png",14,"2026-09-01T11:24:15.087Z",110,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":347,"name":348},"nextjs","Nextjs",[350,351,352,353],{"slug":347,"name":348,"color":48},{"slug":271,"name":272,"color":48},{"slug":65,"name":66,"color":48},{"slug":259,"name":260,"color":48},{"assessments":76},{"slug":337},{"id":357,"slug":358,"title":359,"subtitle":48,"excerpt":360,"coverUrl":361,"locale":13,"readingMinutes":51,"publishedAt":362,"viewCount":363,"likeCount":19,"commentCount":19,"author":364,"vertical":365,"topic":366,"tags":367,"_count":372,"playground":373,"hasQuiz":17,"hasPlayground":17},"01a04446-4fc4-73e1-b763-eda8c9cf631f","navigation-api-intercept-navigation","Your Router Doesn't Intercept Navigation. It Reacts To It.","Double-click a link during a slow route change and most SPA routers render two pages and settle on the wrong one — because they're listening for navigation after it already happened. The Navigation API lets you stop it before it starts.","\u002Fmedia\u002Fcovers\u002Fnavigation-api-intercept-navigation.png","2026-09-01T11:23:43.875Z",109,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[368,369,370,371],{"slug":61,"name":71,"color":48},{"slug":65,"name":66,"color":48},{"slug":73,"name":74,"color":48},{"slug":68,"name":69,"color":48},{"assessments":76},{"slug":358},{"id":375,"slug":376,"title":377,"subtitle":48,"excerpt":378,"coverUrl":379,"locale":13,"readingMinutes":341,"publishedAt":380,"viewCount":381,"likeCount":19,"commentCount":19,"author":382,"vertical":383,"topic":384,"tags":385,"_count":390,"playground":391,"hasQuiz":17,"hasPlayground":17},"019ff73a-fb5b-73ce-b725-2912167a7dde","weekly-debounce-and-throttle","Debounce and Throttle in JavaScript: The Complete Guide","Learn debounce and throttle in JavaScript step by step, with worked examples, edge cases, and a copy-paste cheat sheet for search, scroll, and resize.","\u002Fmedia\u002Fcovers\u002Fweekly-debounce-and-throttle.png","2026-08-31T13:12:14.139Z",162,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[386,387,388,389],{"slug":61,"name":71,"color":48},{"slug":65,"name":66,"color":48},{"slug":259,"name":260,"color":48},{"slug":267,"name":268,"color":48},{"assessments":76},{"slug":376},{"id":393,"slug":394,"title":395,"subtitle":48,"excerpt":396,"coverUrl":397,"locale":13,"readingMinutes":277,"publishedAt":398,"viewCount":399,"likeCount":19,"commentCount":19,"author":400,"vertical":401,"topic":402,"tags":403,"_count":410,"playground":411,"hasQuiz":17,"hasPlayground":17},"01a03cdb-3743-73ef-ab9d-21f03158ca35","server-sent-events-eventsource-live-updates","You Don't Need a WebSocket for That Live Feed","A live notification badge, a progress bar, a dashboard counter — you reached for socket.io and started hand-rolling reconnect logic. The browser already ships a simpler API that does it for you.","\u002Fmedia\u002Fcovers\u002Fserver-sent-events-eventsource-live-updates.png","2026-08-31T13:11:42.385Z",104,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[404,405,406,407],{"slug":61,"name":71,"color":48},{"slug":65,"name":66,"color":48},{"slug":259,"name":260,"color":48},{"slug":408,"name":409,"color":48},"apis","Apis",{"assessments":76},{"slug":394},{"page":76,"perPage":242,"total":249,"totalPages":265},"\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">getCookie\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>name\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> match \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> document\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>cookie\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">match\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">RegExp\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"(^| )\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">+\u003C\u002Fspan> name \u003Cspan class=\"shj-oper\">+\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"=([^;]+)\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> match \u003Cspan class=\"shj-oper\">?\u003C\u002Fspan> \u003Cspan class=\"shj-func\">decodeURIComponent\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>match\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-num\">2\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> session \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> cookieStore\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">get\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"session_id\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F { name: \"session_id\", value: \"abc123\", domain: null, path: \"\u002F\", ... } or null\n\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> all \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> cookieStore\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">getAll\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F array of every cookie visible to this document, already parsed\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> cookieStore\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  name\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"theme\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  value\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"dark\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  expires\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-oper\">+\u003C\u002Fspan> \u003Cspan class=\"shj-num\">1000\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> \u003Cspan class=\"shj-num\">60\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> \u003Cspan class=\"shj-num\">60\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> \u003Cspan class=\"shj-num\">24\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> \u003Cspan class=\"shj-num\">30\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F 30 days, in ms\n\u003C\u002Fspan>  path\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"\u002F\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> cookieStore\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">delete\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"theme\"\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">cookieStore\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"change\"\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\">for\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> cookie \u003Cspan class=\"shj-kwd\">of\u003C\u002Fspan> event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>changed\u003Cspan class=\"shj-bracket\">)\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>\u003Cspan class=\"shj-str\">\"set:\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> cookie\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>name\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> cookie\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">for\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> cookie \u003Cspan class=\"shj-kwd\">of\u003C\u002Fspan> event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>deleted\u003Cspan class=\"shj-bracket\">)\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>\u003Cspan class=\"shj-str\">\"deleted:\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> cookie\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>name\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> cookieStore\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  name\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"session_id\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  value\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> token\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  sameSite\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"lax\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F match what document.cookie would have given you\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":419},[]]