[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"search-suggestions":44,"article-uint8array-tobase64":92,"path-membership-uint8array-tobase64":314,"related-uint8array-tobase64":315,"code:plain:true:b77egm":440,"code:js:true:ajqdr3":441,"code:js:true:1l4pjo":442,"code:js:true:1x5srrl":443,"code:js:true:19ratsc":444,"code:js:true:11d9nfh":445,"code:js:true:d176o":446,"code:js:true:1sv37a7":447,"code:js:true:7ruc1b":448,"comments-01a18b4e-fbba-7593-896a-8808e32d35cc":449},[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,[45,49,53,57,61,65,69,73,77,81,84,88],{"slug":46,"name":47,"articles":48},"webdev","Webdev",168,{"slug":50,"name":51,"articles":52},"javascript","Javascript",135,{"slug":54,"name":55,"articles":56},"frontend","Frontend",94,{"slug":58,"name":59,"articles":60},"tutorial","Tutorial",68,{"slug":62,"name":63,"articles":64},"css","Css",50,{"slug":66,"name":67,"articles":68},"performance","Performance",28,{"slug":70,"name":71,"articles":72},"typescript","Typescript",24,{"slug":74,"name":75,"articles":76},"react","React",20,{"slug":78,"name":79,"articles":80},"browser","Browser",17,{"slug":82,"name":83,"articles":80},"programming","Programming",{"slug":85,"name":86,"articles":87},"node","Node",14,{"slug":89,"name":90,"articles":91},"html","Html",11,{"id":93,"slug":94,"title":95,"subtitle":96,"excerpt":97,"coverUrl":98,"locale":13,"readingMinutes":99,"publishedAt":100,"viewCount":101,"likeCount":19,"commentCount":19,"author":102,"vertical":107,"topic":108,"tags":110,"_count":115,"playground":117,"body":119,"bodyMd":297,"seo":298,"translationGroupId":301,"series":96,"podcastUrl":96,"verticalId":5,"thread":302,"assessments":304,"translations":310,"quiz":312},"01a18b4e-fbba-7593-896a-8808e32d35cc","uint8array-tobase64","Stop Hacking Base64 With btoa(). toBase64() Fixes It Natively.",null,"btoa() throws on emoji, and the classic String.fromCharCode(...bytes) workaround crashes on large files. toBase64() and fromBase64() on Uint8Array fix both — no string round trip, no library.","\u002Fmedia\u002Fcovers\u002Fuint8array-tobase64.png",5,"2026-11-05T06:10:25.995Z",26,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":50,"name":109},"JavaScript",[111,112,113,114],{"slug":50,"name":51,"color":96},{"slug":46,"name":47,"color":96},{"slug":78,"name":79,"color":96},{"slug":58,"name":59,"color":96},{"assessments":116},1,{"slug":94,"title":118},"btoa() vs toBase64() — interactive playground",{"blocks":120,"version":116},[121,125,128,134,137,142,147,150,153,156,159,162,166,169,172,176,179,182,185,188,191,194,197,200,203,206,209,213,216,220,223,227,230,234,237,241,244,247,250,254,257,260,263,266,269,272,275,278,281,284,287,290],{"id":122,"html":123,"type":124},"b1","\u003Cp>The feature was simple: let someone export a report and share it as a single link, PDF baked right in as a data URL. Worked first try. Worked in the demo. Worked for two weeks in production.\u003C\u002Fp>","paragraph",{"id":126,"html":127,"type":124},"b2","\u003Cp>Then someone exported a 40-page report, and the share button threw:\u003C\u002Fp>",{"id":129,"code":130,"type":131,"language":132,"highlight":133},"b3","RangeError: Maximum call stack size exceeded","code","plain",[],{"id":135,"html":136,"type":124},"b4","\u003Cp>Not a network error. Not a permissions error. The encoding step itself — the part of the code nobody had touched since the first version — blew the call stack on a PDF that was, by file standards, perfectly ordinary.\u003C\u002Fp>",{"id":138,"html":139,"text":140,"type":141,"level":31},"b5","The code that worked right up until it didn&#39;t","The code that worked right up until it didn't","heading",{"id":143,"code":144,"type":131,"language":145,"highlight":146},"b6","async function shareReport(url) {\n  const res = await fetch(url);\n  const bytes = new Uint8Array(await res.arrayBuffer());\n  const base64 = btoa(String.fromCharCode(...bytes));\n  return `data:application\u002Fpdf;base64,${base64}`;\n}","js",[],{"id":148,"html":149,"type":124},"b7","\u003Cp>This is the pattern you&#39;ll find in half the Stack Overflow answers for &quot;convert ArrayBuffer to base64 JavaScript.&quot; It reads fine. It passed code review. It&#39;s also a time bomb, and the fuse length depends on file size.\u003C\u002Fp>",{"id":151,"html":152,"type":124},"b8","\u003Cp>\u003Cstrong>Before you keep reading: what do you think \u003Ccode>...bytes\u003C\u002Fcode> is actually doing to \u003Ccode>String.fromCharCode\u003C\u002Fcode>?\u003C\u002Fstrong>\u003C\u002Fp>",{"id":154,"html":155,"type":124},"b9","\u003Cp>It&#39;s not passing an array. \u003Ccode>String.fromCharCode\u003C\u002Fcode> doesn&#39;t take one — it takes a list of arguments, one per character code. The spread operator expands every single byte into its own argument in that function call. A 2KB test file is 2,000-ish arguments. Fine. A 40-page PDF is tens of thousands of bytes, which means tens of thousands of arguments in one call — and every JS engine caps how many arguments a function call can take. Cross that line and you don&#39;t get a graceful error, you get a stack blow-up with a message that tells you nothing about where it came from.\u003C\u002Fp>",{"id":157,"html":158,"text":158,"type":141,"level":31},"b10","It gets worse with text, not just files",{"id":160,"html":161,"type":124},"b11","\u003Cp>Swap the binary file for something simpler — base64-encoding a string for a shareable URL — and \u003Ccode>btoa()\u003C\u002Fcode> has a \u003Cem>second\u003C\u002Fem> failure mode:\u003C\u002Fp>",{"id":163,"code":164,"type":131,"language":145,"highlight":165},"b12","btoa(\"Hello 👋 World\");\n\u002F\u002F DOMException: Failed to execute 'btoa' on 'Window':\n\u002F\u002F The string to be encoded contains characters outside of\n\u002F\u002F the Latin1 range.",[],{"id":167,"html":168,"type":124},"b13","\u003Cp>\u003Ccode>btoa()\u003C\u002Fcode> was never built to handle arbitrary text. Its actual contract is a &quot;byte string&quot; — every character has to map to a single byte, U+0000 through U+00FF. That range covers plenty of accented Latin letters (\u003Ccode>café\u003C\u002Fcode> and \u003Ccode>José\u003C\u002Fcode> actually encode fine, just not as UTF-8), but step outside it — an emoji, an em dash, a name with a character like \u003Ccode>Ł\u003C\u002Fcode> or a Cyrillic letter in it — and it&#39;s an instant throw.\u003C\u002Fp>",{"id":170,"html":171,"type":124},"b14","\u003Cp>The classic fix you&#39;ll find for this one is uglier than the bug:\u003C\u002Fp>",{"id":173,"code":174,"type":131,"language":145,"highlight":175},"b15","btoa(unescape(encodeURIComponent(text)));",[],{"id":177,"html":178,"type":124},"b16","\u003Cp>It works, in the sense that it doesn&#39;t throw. It also routes your string through \u003Ccode>unescape()\u003C\u002Fcode> — a function the spec deprecated decades ago and keeps around purely so this exact hack doesn&#39;t break the web. If you&#39;ve ever wondered why base64-encoding a string in JavaScript feels like three functions wearing a trenchcoat, this is why: \u003Ccode>btoa()\u003C\u002Fcode> does one narrow job, and everything else is a workaround bolted on from outside.\u003C\u002Fp>",{"id":180,"html":181,"text":181,"type":141,"level":31},"b17","Why both bugs have the same root cause",{"id":183,"html":184,"type":124},"b18","\u003Cp>\u003Ccode>btoa()\u003C\u002Fcode> and \u003Ccode>atob()\u003C\u002Fcode> operate on strings, not bytes. Any time your actual data is binary — a fetched file, a crypto digest, Unicode text — you have to convert it into a byte-string-shaped JavaScript string first. That conversion step is where both bugs live: \u003Ccode>String.fromCharCode(...bytes)\u003C\u002Fcode> is how you build that string for binary data, and it&#39;s the one that blows the call stack. \u003Ccode>encodeURIComponent\u003C\u002Fcode> plus \u003Ccode>unescape\u003C\u002Fcode> is how you build it for Unicode text, and it&#39;s the one that&#39;s a deprecated-function sandwich.\u003C\u002Fp>",{"id":186,"html":187,"type":124},"b19","\u003Cp>The actual problem was never base64. It was the round trip through a string that base64 never needed in the first place.\u003C\u002Fp>",{"id":189,"html":190,"type":124},"b20","\u003C!-- playground:start -->",{"id":192,"html":193,"text":193,"type":141,"level":31},"b21","🎮 Try it yourself",{"id":195,"html":196,"type":124},"b22","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fuint8array-tobase64\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":198,"html":199,"type":124},"b23","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":201,"html":202,"type":124},"b24","\u003C!-- playground:end -->",{"id":204,"html":205,"text":205,"type":141,"level":31},"b25","The fix: encode the bytes directly",{"id":207,"html":208,"type":124},"b26","\u003Cp>As of late 2025, \u003Ccode>Uint8Array\u003C\u002Fcode> has two methods that skip the string entirely:\u003C\u002Fp>",{"id":210,"code":211,"type":131,"language":145,"highlight":212},"b27","async function shareReport(url) {\n  const res = await fetch(url);\n  const bytes = new Uint8Array(await res.arrayBuffer());\n  const base64 = bytes.toBase64(); \u002F\u002F no spread, no stack limit\n  return `data:application\u002Fpdf;base64,${base64}`;\n}",[],{"id":214,"html":215,"type":124},"b28","\u003Cp>One call. No \u003Ccode>String.fromCharCode\u003C\u002Fcode>, no spread, no argument-count ceiling, because nothing is being passed as individual arguments — \u003Ccode>toBase64()\u003C\u002Fcode> walks the bytes internally and hands back a string. Decoding mirrors it exactly:\u003C\u002Fp>",{"id":217,"code":218,"type":131,"language":145,"highlight":219},"b29","const bytes = Uint8Array.fromBase64(base64);",[],{"id":221,"html":222,"type":124},"b30","\u003Cp>For the text case, pair it with \u003Ccode>TextEncoder\u003C\u002Fcode>\u002F\u003Ccode>TextDecoder\u003C\u002Fcode> instead of the \u003Ccode>unescape\u003C\u002Fcode> hack:\u003C\u002Fp>",{"id":224,"code":225,"type":131,"language":145,"highlight":226},"b31","const base64 = new TextEncoder().encode(\"Hello 👋 World\").toBase64();\nconst text = new TextDecoder().decode(Uint8Array.fromBase64(base64));\n\u002F\u002F text === \"Hello 👋 World\"",[],{"id":228,"html":229,"type":124},"b32","\u003Cp>\u003Ccode>TextEncoder\u003C\u002Fcode> turns the string into UTF-8 bytes correctly — emoji, accents, anything — and \u003Ccode>toBase64()\u003C\u002Fcode> takes it from there. No deprecated functions, no silent mis-encoding.\u003C\u002Fp>",{"id":231,"html":232,"text":233,"type":141,"level":43},"b33","The option you&#39;ll actually reach for: URL-safe tokens","The option you'll actually reach for: URL-safe tokens",{"id":235,"html":236,"type":124},"b34","\u003Cp>Standard base64 uses \u003Ccode>+\u003C\u002Fcode> and \u003Ccode>\u002F\u003C\u002Fcode>, both of which need escaping in a URL or a filename. \u003Ccode>toBase64()\u003C\u002Fcode> takes an \u003Ccode>alphabet\u003C\u002Fcode> option so you don&#39;t hand-roll the replacement:\u003C\u002Fp>",{"id":238,"code":239,"type":131,"language":145,"highlight":240},"b35","const token = crypto.getRandomValues(new Uint8Array(16))\n  .toBase64({ alphabet: \"base64url\", omitPadding: true });\n\u002F\u002F \"Kx8pQ2v9mZt3dF7h...\"  — safe to drop straight into a URL, no `+`, `\u002F`, or `=`",[],{"id":242,"html":243,"type":124},"b36","\u003Cp>\u003Ccode>omitPadding: true\u003C\u002Fcode> strips the trailing \u003Ccode>=\u003C\u002Fcode> characters too — the part that trips up anyone who&#39;s ever copy-pasted a base64 token into a query string and watched it get mangled.\u003C\u002Fp>",{"id":245,"html":246,"text":246,"type":141,"level":31},"b37","The part worth being honest about",{"id":248,"html":249,"type":124},"b38","\u003Cp>\u003Ccode>Uint8Array.prototype.toBase64()\u003C\u002Fcode> and \u003Ccode>Uint8Array.fromBase64()\u003C\u002Fcode> reached Baseline &quot;newly available&quot; in September 2025 — Chrome 140, Firefox 133, Safari 18.2, and the versions after. That&#39;s all three engines, not a single-vendor experiment, which is why it&#39;s worth adopting now rather than waiting. It is \u003Cem>not\u003C\u002Fem> in anything older than that, though, so if your app still supports last year&#39;s browsers, feature-detect before you rely on it:\u003C\u002Fp>",{"id":251,"code":252,"type":131,"language":145,"highlight":253},"b39","const hasNativeBase64 = typeof Uint8Array.prototype.toBase64 === \"function\";",[],{"id":255,"html":256,"type":124},"b40","\u003Cp>Keep your \u003Ccode>btoa\u003C\u002Fcode>\u002F\u003Ccode>atob\u003C\u002Fcode> path behind that check for a while, rather than ripping it out the day you read this.\u003C\u002Fp>",{"id":258,"html":259,"text":259,"type":141,"level":31},"b41","The report export, fixed",{"id":261,"html":262,"type":124},"b42","\u003Cp>The 40-page PDF that used to crash the share button now encodes in one line, with no upper bound tied to how many arguments a function call can hold. The emoji that used to throw a \u003Ccode>DOMException\u003C\u002Fcode> round-trips cleanly through \u003Ccode>TextEncoder\u003C\u002Fcode>. Neither fix required a library — both were already shipping in the runtime, waiting for the hand-rolled version to get retired.\u003C\u002Fp>",{"id":264,"html":265,"type":124},"b43","\u003Cp>If you&#39;ve still got a \u003Ccode>String.fromCharCode(...bytes)\u003C\u002Fcode> or an \u003Ccode>unescape(encodeURIComponent(...))\u003C\u002Fcode> somewhere in your codebase, it&#39;s probably not broken yet. It&#39;s just waiting for a big enough file or a weird enough character. What&#39;s the oldest base64 hack still running in your production code — do you know what size file would finally break it?\u003C\u002Fp>",{"id":267,"html":268,"type":124},"b44","\u003C!-- quiz:start -->",{"id":270,"html":271,"text":271,"type":141,"level":31},"b45","🧠 Test yourself",{"id":273,"html":274,"type":124},"b46","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fuint8array-tobase64\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":276,"html":277,"type":124},"b47","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":279,"html":280,"type":124},"b48","\u003C!-- quiz:end -->",{"id":282,"type":283},"b49","divider",{"id":285,"html":286,"type":124},"b50","\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":288,"html":289,"type":124},"b51","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":291,"type":292,"items":293,"ordered":18},"b52","list",[294,295,296],"⭐ \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>","The feature was simple: let someone export a report and share it as a single link, PDF baked right in as a data URL. Worked first try. Worked in the demo. Worked for two weeks in production.\n\nThen someone exported a 40-page report, and the share button threw:\n\n```\nRangeError: Maximum call stack size exceeded\n```\n\nNot a network error. Not a permissions error. The encoding step itself — the part of the code nobody had touched since the first version — blew the call stack on a PDF that was, by file standards, perfectly ordinary.\n\n## The code that worked right up until it didn't\n\n```js\nasync function shareReport(url) {\n  const res = await fetch(url);\n  const bytes = new Uint8Array(await res.arrayBuffer());\n  const base64 = btoa(String.fromCharCode(...bytes));\n  return `data:application\u002Fpdf;base64,${base64}`;\n}\n```\n\nThis is the pattern you'll find in half the Stack Overflow answers for \"convert ArrayBuffer to base64 JavaScript.\" It reads fine. It passed code review. It's also a time bomb, and the fuse length depends on file size.\n\n**Before you keep reading: what do you think `...bytes` is actually doing to `String.fromCharCode`?**\n\nIt's not passing an array. `String.fromCharCode` doesn't take one — it takes a list of arguments, one per character code. The spread operator expands every single byte into its own argument in that function call. A 2KB test file is 2,000-ish arguments. Fine. A 40-page PDF is tens of thousands of bytes, which means tens of thousands of arguments in one call — and every JS engine caps how many arguments a function call can take. Cross that line and you don't get a graceful error, you get a stack blow-up with a message that tells you nothing about where it came from.\n\n## It gets worse with text, not just files\n\nSwap the binary file for something simpler — base64-encoding a string for a shareable URL — and `btoa()` has a *second* failure mode:\n\n```js\nbtoa(\"Hello 👋 World\");\n\u002F\u002F DOMException: Failed to execute 'btoa' on 'Window':\n\u002F\u002F The string to be encoded contains characters outside of\n\u002F\u002F the Latin1 range.\n```\n\n`btoa()` was never built to handle arbitrary text. Its actual contract is a \"byte string\" — every character has to map to a single byte, U+0000 through U+00FF. That range covers plenty of accented Latin letters (`café` and `José` actually encode fine, just not as UTF-8), but step outside it — an emoji, an em dash, a name with a character like `Ł` or a Cyrillic letter in it — and it's an instant throw.\n\nThe classic fix you'll find for this one is uglier than the bug:\n\n```js\nbtoa(unescape(encodeURIComponent(text)));\n```\n\nIt works, in the sense that it doesn't throw. It also routes your string through `unescape()` — a function the spec deprecated decades ago and keeps around purely so this exact hack doesn't break the web. If you've ever wondered why base64-encoding a string in JavaScript feels like three functions wearing a trenchcoat, this is why: `btoa()` does one narrow job, and everything else is a workaround bolted on from outside.\n\n## Why both bugs have the same root cause\n\n`btoa()` and `atob()` operate on strings, not bytes. Any time your actual data is binary — a fetched file, a crypto digest, Unicode text — you have to convert it into a byte-string-shaped JavaScript string first. That conversion step is where both bugs live: `String.fromCharCode(...bytes)` is how you build that string for binary data, and it's the one that blows the call stack. `encodeURIComponent` plus `unescape` is how you build it for Unicode text, and it's the one that's a deprecated-function sandwich.\n\nThe actual problem was never base64. It was the round trip through a string that base64 never needed in the first place.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fuint8array-tobase64\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 fix: encode the bytes directly\n\nAs of late 2025, `Uint8Array` has two methods that skip the string entirely:\n\n```js\nasync function shareReport(url) {\n  const res = await fetch(url);\n  const bytes = new Uint8Array(await res.arrayBuffer());\n  const base64 = bytes.toBase64(); \u002F\u002F no spread, no stack limit\n  return `data:application\u002Fpdf;base64,${base64}`;\n}\n```\n\nOne call. No `String.fromCharCode`, no spread, no argument-count ceiling, because nothing is being passed as individual arguments — `toBase64()` walks the bytes internally and hands back a string. Decoding mirrors it exactly:\n\n```js\nconst bytes = Uint8Array.fromBase64(base64);\n```\n\nFor the text case, pair it with `TextEncoder`\u002F`TextDecoder` instead of the `unescape` hack:\n\n```js\nconst base64 = new TextEncoder().encode(\"Hello 👋 World\").toBase64();\nconst text = new TextDecoder().decode(Uint8Array.fromBase64(base64));\n\u002F\u002F text === \"Hello 👋 World\"\n```\n\n`TextEncoder` turns the string into UTF-8 bytes correctly — emoji, accents, anything — and `toBase64()` takes it from there. No deprecated functions, no silent mis-encoding.\n\n### The option you'll actually reach for: URL-safe tokens\n\nStandard base64 uses `+` and `\u002F`, both of which need escaping in a URL or a filename. `toBase64()` takes an `alphabet` option so you don't hand-roll the replacement:\n\n```js\nconst token = crypto.getRandomValues(new Uint8Array(16))\n  .toBase64({ alphabet: \"base64url\", omitPadding: true });\n\u002F\u002F \"Kx8pQ2v9mZt3dF7h...\"  — safe to drop straight into a URL, no `+`, `\u002F`, or `=`\n```\n\n`omitPadding: true` strips the trailing `=` characters too — the part that trips up anyone who's ever copy-pasted a base64 token into a query string and watched it get mangled.\n\n## The part worth being honest about\n\n`Uint8Array.prototype.toBase64()` and `Uint8Array.fromBase64()` reached Baseline \"newly available\" in September 2025 — Chrome 140, Firefox 133, Safari 18.2, and the versions after. That's all three engines, not a single-vendor experiment, which is why it's worth adopting now rather than waiting. It is *not* in anything older than that, though, so if your app still supports last year's browsers, feature-detect before you rely on it:\n\n```js\nconst hasNativeBase64 = typeof Uint8Array.prototype.toBase64 === \"function\";\n```\n\nKeep your `btoa`\u002F`atob` path behind that check for a while, rather than ripping it out the day you read this.\n\n## The report export, fixed\n\nThe 40-page PDF that used to crash the share button now encodes in one line, with no upper bound tied to how many arguments a function call can hold. The emoji that used to throw a `DOMException` round-trips cleanly through `TextEncoder`. Neither fix required a library — both were already shipping in the runtime, waiting for the hand-rolled version to get retired.\n\nIf you've still got a `String.fromCharCode(...bytes)` or an `unescape(encodeURIComponent(...))` somewhere in your codebase, it's probably not broken yet. It's just waiting for a big enough file or a weird enough character. What's the oldest base64 hack still running in your production code — do you know what size file would finally break it?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fuint8array-tobase64\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":95,"canonical":299,"description":300},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fuint8array-tobase64","btoa() throws on emoji, and the classic String.fromCharCode(...bytes) workaround crashes on large files. toBase64() and fromBase64() on Uint8Array fix both — no string round trip, ","01a18b4e-fbba-7593-896a-8f9181d0677c",{"id":303,"locked":18},"01a18b4e-fbea-73fc-bc4a-f6733be7fc02",[305],{"id":306,"slug":94,"title":307,"_count":308},"01a18b4e-fc0f-76ec-881a-36bc44cab696","btoa() vs toBase64() — do you know why the old way breaks?",{"questions":309},7,[311],{"locale":13,"slug":94},{"id":306,"slug":94,"title":307,"_count":313,"questionCount":309},{"questions":309},[],{"items":316,"meta":439},[317,329,348,366,384,403,421],{"id":93,"slug":94,"title":95,"subtitle":96,"excerpt":97,"coverUrl":98,"locale":13,"readingMinutes":99,"publishedAt":100,"viewCount":318,"likeCount":19,"commentCount":19,"author":319,"vertical":320,"topic":321,"tags":322,"_count":327,"playground":328,"hasQuiz":17,"hasPlayground":17},27,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":50,"name":109},[323,324,325,326],{"slug":50,"name":51,"color":96},{"slug":46,"name":47,"color":96},{"slug":78,"name":79,"color":96},{"slug":58,"name":59,"color":96},{"assessments":116},{"slug":94},{"id":330,"slug":331,"title":332,"subtitle":96,"excerpt":333,"coverUrl":334,"locale":13,"readingMinutes":335,"publishedAt":336,"viewCount":337,"likeCount":19,"commentCount":19,"author":338,"vertical":339,"topic":340,"tags":341,"_count":346,"playground":347,"hasQuiz":17,"hasPlayground":17},"01a18647-e99b-71d4-a0db-6ad3f6778daf","getcurrentposition-not-a-promise","getCurrentPosition() Has No Default Timeout. That's the Trap.","A \"find my location\" button that works instantly on your laptop and hangs forever on a phone with a weak signal isn't flaky — it's getCurrentPosition() doing exactly what its defaults tell it to do. No timeout, no cached position, and a watcher nobody told to stop.","\u002Fmedia\u002Fcovers\u002Fgetcurrentposition-not-a-promise.png",6,"2026-11-04T06:10:25.023Z",84,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":50,"name":109},[342,343,344,345],{"slug":50,"name":51,"color":96},{"slug":46,"name":47,"color":96},{"slug":82,"name":83,"color":96},{"slug":58,"name":59,"color":96},{"assessments":116},{"slug":331},{"id":349,"slug":350,"title":351,"subtitle":96,"excerpt":352,"coverUrl":353,"locale":13,"readingMinutes":335,"publishedAt":354,"viewCount":355,"likeCount":19,"commentCount":19,"author":356,"vertical":357,"topic":358,"tags":359,"_count":364,"playground":365,"hasQuiz":17,"hasPlayground":17},"01a18121-921f-706e-9714-f48280c4dc61","offscreencanvas-canvas-off-main-thread","Your Canvas Is Freezing the Page. OffscreenCanvas Fixes That.","Click a button while your canvas animation is running and the whole page stalls — because the pixels it draws, your layout, and your input handling all queue up on the same main thread. OffscreenCanvas moves the drawing itself into a worker, so the UI keeps responding no matter how much canvas work is happening.","\u002Fmedia\u002Fcovers\u002Foffscreencanvas-canvas-off-main-thread.png","2026-11-03T06:10:56.984Z",124,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":50,"name":109},[360,361,362,363],{"slug":50,"name":51,"color":96},{"slug":46,"name":47,"color":96},{"slug":66,"name":67,"color":96},{"slug":54,"name":55,"color":96},{"assessments":116},{"slug":350},{"id":367,"slug":368,"title":369,"subtitle":96,"excerpt":370,"coverUrl":371,"locale":13,"readingMinutes":87,"publishedAt":372,"viewCount":373,"likeCount":19,"commentCount":19,"author":374,"vertical":375,"topic":376,"tags":377,"_count":382,"playground":383,"hasQuiz":17,"hasPlayground":17},"01a1623b-51e7-7669-89ad-eb54d67696eb","react-weekly-activity-component","React Activity Component: The State Between Render and Remount","React's Activity component hides a subtree without unmounting it — what state, effects, and renders actually do, plus a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Freact-weekly-activity-component.png","2026-11-02T06:10:18.569Z",209,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":74,"name":75},[378,379,380,381],{"slug":74,"name":75,"color":96},{"slug":50,"name":51,"color":96},{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"assessments":116},{"slug":368},{"id":385,"slug":386,"title":387,"subtitle":96,"excerpt":388,"coverUrl":389,"locale":13,"readingMinutes":335,"publishedAt":390,"viewCount":391,"likeCount":19,"commentCount":19,"author":392,"vertical":393,"topic":394,"tags":396,"_count":401,"playground":402,"hasQuiz":17,"hasPlayground":17},"01a17bfb-4a13-77fa-bb6c-5c654aec4dd7","scrollbar-gutter-layout-shift","I Deleted My Scroll-Lock Library's Ugly Hack. `scrollbar-gutter` Replaces It.","Opening a modal shifts your whole page sideways because the scrollbar vanishes with it — the bug every scroll-lock library papers over with a JS width measurement and a padding-right hack. `scrollbar-gutter: stable` fixes it in one line now that Safari finally caught up.","\u002Fmedia\u002Fcovers\u002Fscrollbar-gutter-layout-shift.png","2026-11-02T05:31:34.059Z",159,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":62,"name":395},"CSS",[397,398,399,400],{"slug":62,"name":63,"color":96},{"slug":46,"name":47,"color":96},{"slug":54,"name":55,"color":96},{"slug":50,"name":51,"color":96},{"assessments":116},{"slug":386},{"id":404,"slug":405,"title":406,"subtitle":96,"excerpt":407,"coverUrl":408,"locale":13,"readingMinutes":335,"publishedAt":409,"viewCount":410,"likeCount":19,"commentCount":19,"author":411,"vertical":412,"topic":413,"tags":414,"_count":419,"playground":420,"hasQuiz":17,"hasPlayground":17},"01a152c8-59ef-7724-82e4-c6dc7fb6855f","pointer-events-unify-mouse-touch","You're Writing Mouse and Touch Handlers. Pointer Events Does Both.","A draggable card that works with mousedown\u002Fmousemove on desktop and breaks the moment you test it on a phone. The fix isn't a second set of touch listeners — it's deleting the mouse ones and switching to Pointer Events, which was built to handle mouse, touch, and pen as one input.","\u002Fmedia\u002Fcovers\u002Fpointer-events-unify-mouse-touch.png","2026-11-01T14:50:55.724Z",142,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":50,"name":109},[415,416,417,418],{"slug":50,"name":51,"color":96},{"slug":46,"name":47,"color":96},{"slug":78,"name":79,"color":96},{"slug":54,"name":55,"color":96},{"assessments":116},{"slug":405},{"id":422,"slug":423,"title":424,"subtitle":96,"excerpt":425,"coverUrl":426,"locale":13,"readingMinutes":335,"publishedAt":427,"viewCount":428,"likeCount":19,"commentCount":19,"author":429,"vertical":430,"topic":431,"tags":432,"_count":437,"playground":438,"hasQuiz":17,"hasPlayground":17},"01a16761-d787-7313-a57c-88979dbb3a59","requestsubmit-form-validation-fix","Stop Calling form.submit(). Use requestSubmit() Instead.","A Save button living outside the \u003Cform> tag, wired to form.submit(), looks like it works — until a blank required field sails through and the page does a full reload your fetch handler never saw coming. requestSubmit() is the method that actually clicks the button for you.","\u002Fmedia\u002Fcovers\u002Frequestsubmit-form-validation-fix.png","2026-10-28T06:10:26.806Z",251,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":50,"name":109},[433,434,435,436],{"slug":50,"name":51,"color":96},{"slug":46,"name":47,"color":96},{"slug":89,"name":90,"color":96},{"slug":82,"name":83,"color":96},{"assessments":116},{"slug":423},{"page":116,"perPage":309,"total":52,"totalPages":76},"\u003Cdiv class=\"shj shj-lang-plain shj-oneline\" data-lang=\"plain\">RangeError: Maximum call stack size exceeded\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">shareReport\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>url\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> res \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>url\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> bytes \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Uint8Array\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> res\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">arrayBuffer\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> base64 \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">btoa\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-class\">String\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">fromCharCode\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>bytes\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-str\">`data:application\u002Fpdf;base64,\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>base64\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-func\">btoa\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"Hello 👋 World\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F DOMException: Failed to execute 'btoa' on 'Window':\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F The string to be encoded contains characters outside of\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F the Latin1 range.\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-oneline\" data-lang=\"js\">\u003Cspan class=\"shj-func\">btoa\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-func\">unescape\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-func\">encodeURIComponent\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>text\u003Cspan class=\"shj-bracket\">)\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">shareReport\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>url\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> res \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>url\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> bytes \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Uint8Array\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> res\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">arrayBuffer\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> base64 \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> bytes\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">toBase64\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F no spread, no stack limit\n\u003C\u002Fspan>  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-str\">`data:application\u002Fpdf;base64,\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>base64\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-oneline\" data-lang=\"js\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> bytes \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Uint8Array\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">fromBase64\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>base64\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> base64 \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">TextEncoder\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">encode\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"Hello 👋 World\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">toBase64\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> text \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">TextDecoder\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">decode\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-class\">Uint8Array\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">fromBase64\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>base64\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F text === \"Hello 👋 World\"\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> token \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> crypto\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">getRandomValues\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Uint8Array\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">16\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">toBase64\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> alphabet\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"base64url\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> omitPadding\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F \"Kx8pQ2v9mZt3dF7h...\"  — safe to drop straight into a URL, no `+`, `\u002F`, or `=`\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-oneline\" data-lang=\"js\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> hasNativeBase64 \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">typeof\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Uint8Array\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>prototype\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>toBase64 \u003Cspan class=\"shj-oper\">===\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"function\"\u003C\u002Fspan>;\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":450},[]]