Typesetting CJK at the edge: og:image and font subsetting
2026-06-10
An og:image should not be a pre-generated file — it should be a route that
returns a PNG: satori lays out JSX inside a V8 isolate, resvg (Rust compiled
to WASM) rasterizes it, and no browser is involved anywhere. This site's
/og/<slug>.png works exactly this way.
CJK is the real test: a full CJK family (Noto Sans TC/JP/KR) weighs several
MB per script — far too heavy to ship in a worker. The answer is Google
Fonts' text= parameter — download only the glyphs the title actually
uses. A subset is a few dozen KB, cached for a week through workerd's
Cache API.
/og/<slug>.png → detect CJK → fetch font subset (text=title) → satori → resvg → PNG
The same pipeline covers every CJK script with no per-language work — and the distinctions matter: Traditional and Simplified Chinese are different glyph sets served by different fonts (Noto Sans TC vs SC), and Japanese mixes kana with its own kanji forms:
- エッジで日本語を組版する (Japanese — kana and kanji)
- 邊緣排版與字型子集化 (Traditional Chinese)
- 边缘排版与字体子集化 (Simplified Chinese)
- 글꼴 서브셋 (Korean)
A common alternative is self-hosting fonts at build time (our asset pipeline will do that too), but a build cannot know the glyph set of a dynamic title ahead of time — runtime subsetting is the right call for the og:image case.