Skip to content

fix: edge runtime WASM bindings for satori yoga and resvg#492

Merged
harlan-zw merged 6 commits intomainfrom
fix/edge-wasm-satori
Mar 2, 2026
Merged

fix: edge runtime WASM bindings for satori yoga and resvg#492
harlan-zw merged 6 commits intomainfrom
fix/edge-wasm-satori

Conversation

@harlan-zw
Copy link
Copy Markdown
Collaborator

🔗 Linked issue

Related to #434

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

Edge runtimes (Cloudflare Workers, Vercel Edge) were failing to generate OG images because satori's yoga WASM binary was resolved from yoga-wasm-web instead of satori/yoga.wasm — the correct binary that matches satori's bundled Emscripten glue. Also fixes Cloudflare Workers blocking WebAssembly.instantiate() by patching calls to use synchronous new WebAssembly.Instance() for pre-compiled modules.

Key changes:

  • Yoga WASM: resolve from satori/yoga.wasm via #og-image/yoga-wasm alias
  • CF Workers: patch WebAssembly.instantiatenew WebAssembly.Instance using oxc-walker AST traversal
  • Vercel Edge: patch unenv Process polyfill (Proxy + private class fields incompatibility)
  • Bundle size: mock unused renderer bindings to avoid bundling large WASM files (e.g. takumi 3.4MB) for renderers that aren't used
  • Tests: cloudflare-satori and vercel-edge-satori e2e test fixtures

- Switch yoga WASM from yoga-wasm-web to satori/yoga.wasm (correct binary for satori 0.16+)
- Add #og-image/yoga-wasm alias in compatibility.ts
- Fix oxc-walker AST type names in patchWebAssemblyInstantiate
- Add vercel-edge Process polyfill patch (Proxy + private class fields)
- Mock unused renderer bindings to avoid bundling large WASM (e.g. takumi 3.4MB)
- Fix cloudflare WASM lazy config (let Nitro's preset fill in lazy: false)
- Add vercel-edge-satori e2e test fixture
…safer codegen

- Use shared `importWasm` util in resvg/wasm.ts and satori/wasm.ts instead of inline double-await
- Fix race condition in instances.ts: use singleton promise for concurrent first calls
- Add null guard to useSatori consistent with useResvg
- Use temp variable in patchWebAssemblyInstantiate to avoid triple arg1 evaluation
- Log err.stack directly instead of redundant message + stack
- Parallelize hash resolution with Promise.all
@harlan-zw
Copy link
Copy Markdown
Collaborator Author

Code review

Found 1 issue:

  1. $fetch fallback reintroduces self-referencing subrequest on CF Workers. The new fallback path in cloudflare.ts calls $fetch(fullPath, { baseURL: origin }) when event.$fetch is unavailable. This makes an outbound HTTP request to the worker's own origin -- the exact pattern that commit ce307ff8 previously removed with the comment: "Use event.$fetch for in-process resolution -- avoids external HTTP round-trips which are unreliable on CF Workers (self-referencing subrequests)". If a CF Workers deployment reaches this fallback (no ASSETS binding, no event.$fetch), font loading will fail or hang due to self-referencing subrequest restrictions.

// Fallback: use event.$fetch for in-process resolution when available,
// otherwise fall back to $fetch with origin (for presets without ASSETS binding)
const internalFetch = (event as any).$fetch as ((path: string, opts: { responseType: string }) => Promise<ArrayBuffer>) | undefined
if (typeof internalFetch === 'function') {
const arrayBuffer = await internalFetch(fullPath, { responseType: 'arrayBuffer' })
return Buffer.from(arrayBuffer)
}
const origin = getNitroOrigin(event)
const arrayBuffer = await $fetch(fullPath, {
responseType: 'arrayBuffer',
baseURL: origin,
}) as ArrayBuffer

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Add type narrowing for callee.property (union includes types without
.name) and null guards for destructured arguments array elements.
…n CF Workers

$fetch with the worker's own origin triggers CF Workers' self-referencing
subrequest restriction. Throw a clear error instead when neither ASSETS
binding nor event.$fetch is available.
@harlan-zw harlan-zw force-pushed the fix/edge-wasm-satori branch from bfe88a2 to 985d846 Compare March 2, 2026 13:11
@harlan-zw harlan-zw merged commit e3293f3 into main Mar 2, 2026
4 of 7 checks passed
@adamdehaven
Copy link
Copy Markdown
Contributor

@harlan-zw can this be released on the v3.x release? We're currently also getting a 500 on Cloudflare

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants