Hosting & headers
SharedArrayBuffer is only exposed when window.crossOriginIsolated === true. That flag — not CORS — is the gate, and your host must opt the document into it on every response.
This page applies only when your pool uses sharedMemory. A message-only connectWorker/WorkerPool (no sharedMemory contract) needs none of these headers — SharedArrayBuffer is never touched.
Required response headers
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corpWithout them the SDK can't create the shared buffer: crossOriginIsolated is false and SharedArrayBuffer is undefined.
Per-server configuration
export default defineConfig({
server: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Resource-Policy': 'same-site',
},
},
});"serve": {
"builder": "@angular/build:dev-server",
"options": {
"headers": {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
"Cross-Origin-Resource-Policy": "same-site"
}
}
}async headers() {
return [{
source: '/:path*',
headers: [
{ key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
{ key: 'Cross-Origin-Embedder-Policy', value: 'require-corp' },
{ key: 'Cross-Origin-Resource-Policy', value: 'same-site' },
],
}];
}add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "same-site" always;Embedding your app in an iframe
Cross-origin embedding requires four independent pieces — if any one is missing, Chrome blocks the navigation (ERR_BLOCKED_BY_RESPONSE) or the child loads without isolation:
- The parent page is itself cross-origin isolated.
- The iframe element delegates isolation:
allow="cross-origin-isolated". Without it, a fully-configured child is still blocked. - The child sends COEP + COOP — a cross-origin document embedded under a
require-corpparent must be capable of isolation itself. - The child sends CORP —
same-sitesuffices when embedder and app share a site (e.g. differentlocalhostports);cross-originfor arbitrary embedders.
<iframe src="https://app.example.com" allow="cross-origin-isolated"></iframe>Measured requirement matrix
COEP parent embedding cross-origin children; Chrome's block reason for each child header set:
| Child headers | Result | blockedReason |
|---|---|---|
| COEP + COOP + CORP same-site | loads, isolated, SharedArrayBuffer live in-frame | — |
| COEP + COOP, no CORP | blocked | corp-not-same-origin-after-defaulted-to-same-origin-by-coep |
| CORP same-site, no COEP | blocked | coep-frame-resource-needs-coep-header |
| no headers | blocked | corp-not-same-origin-… |
Separately, frame-ancestors CSP controls who may embed your app — an independent axis from CORP:
Content-Security-Policy: frame-ancestors 'self' https://docs.example.comStatic hosts that can't set headers (GitHub Pages)
When the server can't send headers at all, a service worker can inject them — it intercepts every response in its scope and rewrites headers before the document parses. This repo's Pages deploy ships coi-sw.js (same idea as coi-serviceworker): each entry page registers ./coi-sw.js inline and reloads once after first install — a document is only isolated if it was fetched while controlled.
- The SW uses
COEP: credentiallessfor the islands demo (its map island loadsno-corstile images thatrequire-corpwould block) andrequire-corpelsewhere. Browsers withoutcredentiallesssupport just stay non-isolated. - Degrade, don't break: islands can run fully without SAB —
mode: 'poll'onmountIsland/<Island>(ordoorbell: falseonconnectIslandWorker) skips the doorbell contract so the pool never touchesSharedArrayBuffer. Checkwindow.crossOriginIsolatedand pick the mode. - Workloads that are shared memory can't degrade — show a notice instead of failing on worker bootstrap.
Gotchas
localhost≠127.0.0.1. Chrome treats them as different sites:CORP: same-sitebetweenlocalhostports passes, the same layout on127.0.0.1fails. Match hostnames between embedder and child, or useCORP: cross-origin.- Avoid
credentiallessiframes — a credentialless frame can never be cross-origin isolated, soSharedArrayBufferis undefined inside it. - Dev servers don't hot-reload header config — restart after editing
angular.json,next.config.ts, etc. - Serve
index.htmlwithCache-Control: no-store(or stamp its URL with a per-build?v=) — assets are content-hashed, but the stable document URL is exactly what stale caches hold onto.