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=1
flag 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.
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.