<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oleg Sidorkin</title>
    <description>The latest articles on DEV Community by Oleg Sidorkin (@osidorkin).</description>
    <link>https://dev.to/osidorkin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3991527%2Fc4eeeb92-49e4-4e41-bfee-012e85e83d9c.png</url>
      <title>DEV Community: Oleg Sidorkin</title>
      <link>https://dev.to/osidorkin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/osidorkin"/>
    <language>en</language>
    <item>
      <title>Detecting WebGPU support, the right way</title>
      <dc:creator>Oleg Sidorkin</dc:creator>
      <pubDate>Thu, 18 Jun 2026 22:09:24 +0000</pubDate>
      <link>https://dev.to/osidorkin/how-to-detect-webgpu-support-5cc5</link>
      <guid>https://dev.to/osidorkin/how-to-detect-webgpu-support-5cc5</guid>
      <description>&lt;p&gt;WebGPU is shipping in more browsers every release, but "is it supported here?" is trickier than a one-liner. Here's how to detect it correctly, why the obvious check is wrong, and how to fall back cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check most people write (and why it's not enough)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// WebGPU supported... right?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;navigator.gpu&lt;/code&gt; existing only tells you the API surface is present. It does &lt;strong&gt;not&lt;/strong&gt; mean you can actually render. The browser can expose &lt;code&gt;navigator.gpu&lt;/code&gt; and still hand you no usable GPU because the device has no compatible adapter, WebGPU is disabled in settings, or the driver is blocklisted. You only know once you ask for an adapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The correct detection
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getWebGPU&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;supported&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestAdapter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;supported&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request-failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;supported&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-adapter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestDevice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;supported&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;requestAdapter()&lt;/code&gt; is the real gate. It returns &lt;code&gt;null&lt;/code&gt; when there's no usable GPU, which is the case the &lt;code&gt;navigator.gpu&lt;/code&gt; check silently misses. Both &lt;code&gt;requestAdapter()&lt;/code&gt; and &lt;code&gt;requestDevice()&lt;/code&gt; are async, so detection has to be &lt;code&gt;async&lt;/code&gt; too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support isn't all-or-nothing
&lt;/h2&gt;

&lt;p&gt;Two devices that both "support WebGPU" can differ a lot. The adapter tells you what this one can actually do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestAdapter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;     &lt;span class="c1"&gt;// e.g. 'texture-compression-bc', 'float32-filterable'&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTextureDimension2D&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxComputeWorkgroupSizeX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your app needs a specific feature (a compression format, timestamp queries, a higher limit), check &lt;code&gt;adapter.features.has('...')&lt;/code&gt; and the relevant &lt;code&gt;adapter.limits&lt;/code&gt; before you rely on it. Requesting a feature on a device that lacks it throws.&lt;/p&gt;

&lt;h2&gt;
  
  
  Falling back to WebGL 2
&lt;/h2&gt;

&lt;p&gt;Most real apps want WebGPU when available and WebGL 2 otherwise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pickRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestAdapter&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webgpu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webgl2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;gl&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webgl2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Engines like three.js and Babylon.js do this internally with a WebGPU backend and a WebGL 2 fallback, so if you're on one of those you mostly get it for free. If you're writing to the API directly, do the check yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  See a browser's real WebGPU profile without writing code
&lt;/h2&gt;

&lt;p&gt;To eyeball what a given browser actually reports, its adapter, every standard feature with green/red status, the limits as a table, plus a live render test that confirms it really works, I put together a free tool:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://app.cinevva.com/tools/webgl-webgpu-checker" rel="noopener noreferrer"&gt;WebGL &amp;amp; WebGPU Checker&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc437o1aj84u2q0i7hswn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc437o1aj84u2q0i7hswn.png" alt=" " width="800" height="815"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Handy for triaging "works on my machine" bug reports: have the reporter open it and paste what they see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;navigator.gpu&lt;/code&gt; only means the API exists, not that you can render.&lt;/li&gt;
&lt;li&gt;Detect by awaiting &lt;code&gt;requestAdapter()&lt;/code&gt; and checking for a non-null result.&lt;/li&gt;
&lt;li&gt;Read &lt;code&gt;adapter.features&lt;/code&gt; and &lt;code&gt;adapter.limits&lt;/code&gt; before relying on anything specific.&lt;/li&gt;
&lt;li&gt;Fall back to WebGL 2 when WebGPU isn't available.&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Fix 'SharedArrayBuffer is not defined': a practical guide to cross-origin isolation</title>
      <dc:creator>Oleg Sidorkin</dc:creator>
      <pubDate>Thu, 18 Jun 2026 21:10:09 +0000</pubDate>
      <link>https://dev.to/osidorkin/fix-sharedarraybuffer-is-not-defined-a-practical-guide-to-cross-origin-isolation-1neh</link>
      <guid>https://dev.to/osidorkin/fix-sharedarraybuffer-is-not-defined-a-practical-guide-to-cross-origin-isolation-1neh</guid>
      <description>&lt;p&gt;If you've ever seen this in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Uncaught ReferenceError: SharedArrayBuffer is not defined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or your multithreaded WebAssembly quietly fell back to a single thread, the cause is almost always the same thing: &lt;strong&gt;your page is not cross-origin isolated.&lt;/strong&gt; Here's what that means, why the browser does it, and exactly how to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Send these two headers on the response for the document that loads your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then confirm in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crossOriginIsolated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// should be true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it's &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;SharedArrayBuffer&lt;/code&gt; is available and Wasm threads work. The rest of this post is the why, the gotchas, and how to verify it without writing code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the browser blocks SharedArrayBuffer
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SharedArrayBuffer&lt;/code&gt; lets multiple threads share memory, which is what makes multithreaded WebAssembly possible. But shared memory also enables very high-precision timers, and those make Spectre-style side-channel attacks easier. After Spectre, browsers pulled &lt;code&gt;SharedArrayBuffer&lt;/code&gt; and only give it back when your page proves it isn't sharing a process with untrusted cross-origin content. That proof is &lt;strong&gt;cross-origin isolation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A page becomes cross-origin isolated when it sends both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Cross-Origin-Opener-Policy: same-origin&lt;/code&gt; — cuts the link to other top-level windows so your page gets its own browsing context group.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cross-Origin-Embedder-Policy: require-corp&lt;/code&gt; — says every subresource must explicitly opt
in to being loaded by you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With both in place, the browser flips &lt;code&gt;self.crossOriginIsolated&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; and restores &lt;code&gt;SharedArrayBuffer&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake almost everyone makes: CORP is not COEP
&lt;/h2&gt;

&lt;p&gt;This one burns a lot of people. There's a third, similarly named header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cross-Origin-Resource-Policy: cross-origin
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Cross-Origin-Resource-Policy&lt;/code&gt; (CORP) is set &lt;strong&gt;by a subresource&lt;/strong&gt; (an image, a script, a font) to declare who is allowed to embed it. It does &lt;strong&gt;not&lt;/strong&gt; isolate your document. If you set CORP on your HTML page expecting &lt;code&gt;SharedArrayBuffer&lt;/code&gt; to show up, nothing happens, because that's not what CORP does.&lt;/p&gt;

&lt;p&gt;The two headers that isolate the &lt;em&gt;page&lt;/em&gt; are &lt;strong&gt;COOP&lt;/strong&gt; and &lt;strong&gt;COEP&lt;/strong&gt;. CORP comes into play only as a way for your &lt;em&gt;subresources&lt;/em&gt; to satisfy COEP (more on that below). Keep them straight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;COOP + COEP → set on your &lt;strong&gt;document&lt;/strong&gt;, turn isolation on.&lt;/li&gt;
&lt;li&gt;CORP → set on &lt;strong&gt;subresources&lt;/strong&gt;, lets them keep loading once COEP is on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Check whether you're actually isolated
&lt;/h2&gt;

&lt;p&gt;One line in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crossOriginIsolated&lt;/span&gt; &lt;span class="c1"&gt;// true once COOP + COEP are correct&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it's &lt;code&gt;false&lt;/code&gt;, the headers aren't reaching the page. The two usual reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Wrong origin.&lt;/strong&gt; You set the headers on a CDN subdomain, but not on the origin actually serving &lt;code&gt;index.html&lt;/code&gt;. Isolation is decided by the document's own response headers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host strips them.&lt;/strong&gt; Some static hosts (GitHub Pages and friends) don't let you set custom response headers at all, so they never arrive.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting the headers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;nginx:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cross-Origin-Opener-Policy&lt;/span&gt; &lt;span class="s"&gt;"same-origin"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cross-Origin-Embedder-Policy&lt;/span&gt; &lt;span class="s"&gt;"require-corp"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Express:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cross-Origin-Opener-Policy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;same-origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cross-Origin-Embedder-Policy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;require-corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Can't control the headers&lt;/strong&gt; (GitHub Pages, itch.io, some CDNs): use a service-worker shim like &lt;a href="https://github.com/gzuidhof/coi-serviceworker" rel="noopener noreferrer"&gt;&lt;code&gt;coi-serviceworker&lt;/code&gt;&lt;/a&gt;, which injects the headers client-side. It's how a lot of Godot and Unity web exports get threads working on hosts that won't set headers for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The side effect to plan for
&lt;/h2&gt;

&lt;p&gt;Once &lt;code&gt;COEP: require-corp&lt;/code&gt; is on, &lt;strong&gt;every cross-origin subresource&lt;/strong&gt; has to opt in, or the browser refuses to load it. Each third-party image, script, or font now needs either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Cross-Origin-Resource-Policy: cross-origin&lt;/code&gt; (or &lt;code&gt;same-site&lt;/code&gt;) on its own response, or&lt;/li&gt;
&lt;li&gt;proper CORS (&lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;) plus a &lt;code&gt;crossorigin&lt;/code&gt; attribute on the tag.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So turning on isolation can break third-party assets until you fix them. If a CDN you use won't send CORP/CORS, you'll need to proxy or self-host those files. This is the part that turns a "two header" change into an afternoon, so budget for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify any URL without writing code
&lt;/h2&gt;

&lt;p&gt;To save the back-and-forth, I built a small free tool that fetches a URL's response headers and tells you whether it's actually cross-origin isolated, with the COOP/COEP values and the CORP-vs-COEP gotcha called out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://app.cinevva.com/tools/cross-origin-isolation-checker" rel="noopener noreferrer"&gt;Cross-Origin Isolation Checker&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's also a longer, copy-paste walkthrough with server configs for more setups here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://app.cinevva.com/tutorials/coop-coep-sharedarraybuffer" rel="noopener noreferrer"&gt;Enable Wasm threads (SharedArrayBuffer) with COOP/COEP&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;SharedArrayBuffer&lt;/code&gt; is gated behind cross-origin isolation (a post-Spectre security move).&lt;/li&gt;
&lt;li&gt;Isolate the page with &lt;code&gt;COOP: same-origin&lt;/code&gt; + &lt;code&gt;COEP: require-corp&lt;/code&gt; on the &lt;strong&gt;document&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CORP&lt;/code&gt; is a different header for &lt;strong&gt;subresources&lt;/strong&gt;, it does not isolate your page.&lt;/li&gt;
&lt;li&gt;Confirm with &lt;code&gt;self.crossOriginIsolated === true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Expect to fix cross-origin subresources that COEP now blocks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Get those right and &lt;code&gt;SharedArrayBuffer&lt;/code&gt;, and your Wasm threads, come back.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
