Cross-Origin Isolation
Configure COOP, COEP, and CORP headers so SharedArrayBuffer-backed kernels (multi-threaded WASM and geometry pool) load on every browser, including Safari.
@taucad/runtime needs crossOriginIsolated === true for its SharedArrayBuffer-backed features. Two layers own SABs — the active transport allocates the geometry pool and cooperative-abort channel internally, and the multi-threaded OpenCASCADE WASM heap belongs to the kernel — but they share one isolation requirement: the top-level document must be served with the right COOP, COEP, and CORP headers.
Without isolation the runtime still works, degraded: the geometry pool falls back to copy delivery, render abort to wire notification, and auto-selected OCCT kernels to single-threaded WASM. The worker logs a structured warning naming the degradation, so the failure is observable in telemetry rather than silent.
The runtime ships an adapter for each layer that produces responses. Use them; do not duplicate header strings in your app.
The required headers
SharedArrayBuffer is blocked by default to mitigate Spectre-class attacks. To opt in, the document must declare:
| Header | Value | Purpose |
|---|---|---|
Cross-Origin-Opener-Policy | same-origin | Isolates the top-level browsing context. |
Cross-Origin-Embedder-Policy | require-corp | Forces every embedded resource to declare CORP. |
Cross-Origin-Resource-Policy | same-origin | Allows the document and same-origin subresources to load under COEP. |
Safari is strictly conformant: under require-corp, every subresource — including same-origin worker scripts and WASM binaries — must carry an explicit CORP header. Chromium and Firefox are more permissive, which is why a missing CORP header on a worker often appears as a Safari-only eternal-loading bug. Set the headers at every layer that produces a response; missing any one of HTML, worker-script, or WASM responses breaks Safari.
| Layer | Adapter |
|---|---|
| Vite dev/preview server | @taucad/runtime/vite#tauRuntime |
| Next.js | @taucad/runtime/nextjs/config#withTauRuntime |
| React Router SSR (the HTML document) | @taucad/runtime/react-router |
| Express/Connect production server | @taucad/runtime/cross-origin-isolation/express |
| Electron renderer session | @taucad/runtime/electron/main |
| Static host with declarative headers | Your host's config (e.g. netlify.toml, _headers) |
Vite dev/preview
import { tauRuntime } from '@taucad/runtime/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tauRuntime()],
});tauRuntime() includes the isolation adapter plus the runtime's asset and worker invariants. Reach for the lower-level crossOriginIsolation() export only when another integration already supplies those build invariants.
Next.js
import { withTauRuntime } from '@taucad/runtime/nextjs/config';
export default withTauRuntime();withTauRuntime() composes existing application headers with the canonical runtime rules. Use nextRuntimeHeaders() only when another config composer owns the bundler configuration.
React Router SSR
import { applyHandleRequestHeaders } from '@taucad/runtime/react-router';
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
routerContext: EntryContext,
) {
applyHandleRequestHeaders(responseHeaders);
// …existing renderToPipeableStream logic
}Express production server
Mount coiMiddleware() before express.static so worker scripts and WASM responses carry CORP. The middleware also suppresses downstream res.append() of COI headers, so a downstream Response writer (e.g. @react-router/express) cannot duplicate the triple:
import express, { type Express } from 'express';
import { createRequestHandler } from '@react-router/express';
import { coiMiddleware } from '@taucad/runtime/cross-origin-isolation/express';
type ServerBuild = Parameters<typeof createRequestHandler>[0]['build'];
export function createApp(build: ServerBuild): Express {
const app = express();
app.disable('x-powered-by');
app.use(coiMiddleware());
app.use('/assets', express.static('build/client/assets', { immutable: true, maxAge: '1y' }));
app.use(express.static('build/client', { maxAge: '1h' }));
app.all('*splat', createRequestHandler({ build }));
return app;
}Electron
Install the renderer-session headers after app.whenReady() and before creating runtime windows:
import { app } from 'electron';
import { installElectronRuntimeHeaders } from '@taucad/runtime/electron/main';
await app.whenReady();
installElectronRuntimeHeaders();Static hosts
Behind a CDN, configure the headers declaratively. For Netlify:
[[headers]]
for = "/*"
[headers.values]
Cross-Origin-Opener-Policy = "same-origin"
Cross-Origin-Embedder-Policy = "require-corp"
Cross-Origin-Resource-Policy = "same-origin"Verify
Check each response class — HTML document, worker script, WASM binary — for exactly one copy of all three headers:
curl -sI http://localhost:3000/ | grep -i cross-origin
curl -sI http://localhost:3000/assets/file-manager.worker-XXXXX.js | grep -i cross-origin
find apps/ui/build/client/assets -name '*.wasm' -print
curl -sI http://localhost:3000/assets/EMITTED-ASSET.wasm | grep -Ei 'content-type|cross-origin'Then confirm crossOriginIsolated === true in the browser DevTools console.
Cross-origin APIs
When the isolated document fetches a different origin (an analytics or asset CDN), that origin must opt in with Cross-Origin-Resource-Policy: cross-origin. Use the apiHeaders constant or applyApiHeaders() from @taucad/runtime/cross-origin-isolation on your API responses.