I would like to run @vercel/og or similar to generate OpenGraph images in Cloudflare Workers. @vercel/og uses satori to convert a layout to svg. And satori uses yoga for the internal layout calculations. However, getting the latest yoga to run in Cloudflare Workers is still tricky.
The Problem: Workers WASM Constraints
Cloudflare Workers has strict requirements for WebAssembly instantiation. Unlike other runtimes, Workers doesn't support compiling WASM from arbitrary data blobs. You must import it as a module:
import yogaModule from './yoga.wasm' // WebAssembly.Module
For comparison, you could produce a WebAssembly.Module in Node.js with WebAssembly.compile(fs.readFile()) and browsers with WebAssembly.compileStreaming(fetch()).
On the positive side, the @cloudflare/vite-plugin, as of 2025, also supports Cloudflare's special syntax by importing via either .wasm or .wasm?module. (There was a time where it did not support this and it was even harder to use wasm in Cloudflare Vite projects.)
Historical Solutions
Yoga 3.0 introduced inline base64 WASM bundling, but this format is incompatible with Workers. Previously, I used shuding/yoga-wasm-web, which was designed for web environments and worked well in Workers. Unfortunately, it hasn't been updated since 2023.
The ecosystem is fragmented. Even Satori (Vercel's SVG generator) now uses an inline patched version of yoga-layout instead of yoga-wasm-web. Many Yoga forks get abandoned over time, making long-term maintenance challenging.
Technical Deep Dive
Yoga is built with emscripten, and the config flag causing Workers incompatibility is SINGLE_FILE=1, which inlines the WASM binary in the JavaScript wrapper.
To make Yoga Workers-compatible, you would also need to override emscripten's instantiateWasm() function. The yoga-wasm-web fork demonstrates this pattern:
import wrapAsm from './yoga/javascript/src_js/wrapAsm.js'
import yoga from './tmp/yoga.mjs'
export default async function (wasm: WebAssembly.Module) {
const mod = await yoga({
instantiateWasm(info, receive) {
WebAssembly.instantiate(wasm, info).then((instance) => {
if (instance instanceof WebAssembly.Instance) {
receive(instance)
} else {
receive(instance.instance)
}
})
},
})
return wrapAsm(mod)
}
The abandoned worker-emscripten-template suggests a similar approach:
import yogaModule from './yoga'
let emscripten_module = new Promise((resolve, reject) => {
emscripten({
instantiateWasm(info, receive) {
let instance = new WebAssembly.Instance(yogaModule, info)
receive(instance)
return instance.exports
},
}).then((module) => {
resolve({
init: module.cwrap('init', 'number', ['number']),
resize: module.cwrap('resize', 'number', ['number', 'number']),
module: module,
})
})
})
Current State & Options
Creating a Workers-compatible Yoga build requires two changes:
- Remove the
SINGLE_FILE=1flag to generate separate WASM files - Create a custom wrapper providing
instantiateWasm
The yoga-wasm-web fork has a clean design. It's a lightweight repo with the main yoga as a submodule, overriding compilation and providing platform-specific wrappers. However, maintaining a personal fork just for this use case seems tedious.
Related Reading
This post explores similar WASM instantiation challenges to my article on CanvasKit WASM in Cloudflare Workers. Both posts demonstrate running Emscripten-based WASM libraries in Cloudflare's non-standard WebAssembly environment.
Conclusion
While Yoga in Cloudflare Workers is technically feasible, the ecosystem fragmentation makes it challenging to maintain. Cloudflare has a non-standard way of loading wasm, and emscripten yoga and satori all seem not particularly interested in supporting this approach.