Build1 distinct publisher3 min readPublished
A Korean saju service found that satori's HTML/CSS subset gives out at the hanja well before it gives out at Latin text. The fix was a two-face fallback array plus a module-level cache for two 2 MB TTFs.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The mechanism is worth stating precisely, because it changes what you go shopping for. Satori walks the string and, for each character, tries the faces in the array in the order you passed them [6]. Nothing in that loop cares what your font claims to cover. It cares whether the glyph is present. The author's framing is the useful one: this is the `unicode-range` trick from CSS `@font-face`, relocated from the stylesheet into a JavaScript array [8]. So a display face covering Hangul and Latin stays first, and a second face carries only the hanja [7].
The arithmetic on the loader is where the design earns its keep. Two faces at 2 MB each is 4 MB of file reads per image [1]. The comment in `og-fonts.ts` says the loader runs per request [9], which would put the 60 Pinterest pins at 240 MB of font reads with no cache [2]. That figure only transfers if those 60 renders land on one warm instance. The cache is a module-scope variable holding a promise, assigned with `cached ??=` [10], so it lives exactly as long as the module instance does, and every cold invocation pays the 4 MB again. If your deploy target discards module state between requests, the cache buys you nothing and the real fix is a subsetted font rather than memoisation.
Then there is bundling, which is the failure that does not reproduce locally. The path has to be a literal `path.join(process.cwd(), '...')` because Vercel's output file tracing follows the literal string when deciding what to ship with the function, and a dynamically built path leaves the file absent at runtime [12]. Static analysis can only see strings that exist at build time. A route that computes its font path will pass on your laptop, where the file is simply on disk, and 500 in production.
The last edge is Yoga. Satori's layout engine requires that a `div` with more than one child declare `display: 'flex'`, and if it does not you get a runtime error rather than a silent fallback [18]. This is documented, and the author still hit it three times, always on inner wrappers such as the little row holding element, stage and seated god [19]. The standing rule now is that every `div` in an image route gets `display: 'flex'` unless it has exactly one text child, and `span` is only ever for text [20]. A layout engine that throws instead of guessing is doing you a favour, though it does not feel like one on the third occasion.
What makes the font ordering the load-bearing decision rather than a nicety is what the card is for. A day pillar is two characters, and the author calls the hanja the payload [21]. Everything else on the image degrades gracefully. The second face is carrying the two glyphs the card exists to show, which is why coverage order deserved more attention here than any of satori's missing CSS features.
Ranked by verification strength, evidence, and original report placement.
The author runs a small Korean saju (Four Pillars) reading service, and every reading ends with a share card: a PNG carrying the person's day pillar in Chinese characters, a hand-drawn animal, and a one-line epithet in Korean or English.
The same pipeline also generates 60 Pinterest pins and Open Graph images for a few hundred dictionary pages.
All of the images are rendered with satori through Next.js's ImageResponse, with no headless browser: you write JSX and get a PNG.
The brand font is a Korean handwriting face that covers Hangul and Latin but does not cover the hanja a saju chart is made of; rendered with only that font, every pillar came out as tofu boxes.
Satori resolves glyphs per character across the fonts you pass, in the order you pass them.
The fix was a fallback set ordered most specific first: OreumSon (Hangul plus Latin) then OreumHan (hanja), with fontFamily 'OreumSon, OreumHan' in the element tree, so Hangul and Latin come from the first face and hanja fall through to the second.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 29, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
With CRA out of React's docs, the new project default is a rendering decision1 distinct publisher
build
The MCP test that matters: a log tool that fetched the data and then said it failed1 distinct publisher
build
Headless CMS is a hiring plan with a content schema attached1 distinct publisher
build
The route nothing imports: how eleven broken endpoints hid behind one Search Console error1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One builder, code in hand
Every load here is carried by a single dev.to post from the person who shipped the thing — but it hands over the actual loader, the token constant and the animal cache rather than describing them, and points at sixty live pins plus an MIT repo. What is absent is anything measured: no satori version, no render timing, no before-and-after beyond 'it was boxes, now it isn't'.
Real production, one small shop
This is deployed, not sketched: sixty pins on a public URL, Open Graph cards for a few hundred dictionary pages, fonts bundled into Vercel functions. It is also exactly one service run by one person. Whether anyone else orders their font array this way — or how widely satori is used at all — sits outside what this reporting shows.
Undersold, especially the byte cost
Nobody is overselling anything: the cure is called a five-line change and left at that, the dark-mode case is abandoned rather than dressed up as a design choice, and the deterministic-table bit is explicitly labelled 'not a satori trick'. If anything the post buries its most transferable number — two 2 MB fonts re-read per request — in a code comment, when that is the line most readers would need.
Field note with a shop window attached
The author sells the readings these cards advertise, and the post closes by linking both the service and their own library — mild self-promotion, plainly disclosed. The counterweight is that the technical content is trivially falsifiable by anyone who passes two font faces to satori in the wrong order, which makes for a poor advertisement and a decent build log.
Firm on what was built, thin on generality
Confidence splits cleanly. What this shop did is about as well attested as a single account gets — code, tokens, live output. Whether the rules behind it still hold as stated depends on versions nobody names: per-character glyph resolution, Yoga's flex requirement, Vercel following a literal string. Tooling this specific ages quickly, and there is no second publisher here to catch drift.