◉ANTIGRAVITY LABJP
Articles/App Development
▣ App Development/2026-06-23Advanced

When Only the Japanese Turns to Tofu in Your Share Image — Fixing next/og CJK Fonts with Antigravity

In next/og's ImageResponse, the Japanese title renders as empty boxes while English looks fine. Here is the real cause (Satori cannot read woff2), a complete edge implementation that pulls a TrueType subset via Google Fonts css2?text=, and how to get Antigravity to fix it the first time.

next-ogSatoriOGPedge-runtimeAntigravity377

✦ Premium Article

The other day I shipped a new article on Antigravity Lab and, as usual, dropped the link into X and Threads. The share preview popped up, and I froze. The English helper text rendered cleanly, but the Japanese title — the part that actually mattered — was a row of empty boxes (□□□□).

When you run several sites as a solo developer, this "one language quietly breaks" class of bug is the worst kind. Nothing errors. The build passes. It only embarrasses you the moment something gets shared.

This article records the implementation that got rid of those boxes. The subject is next/og's ImageResponse, but the real point is one thing: when you hand an OG-image route to an agent like Antigravity, it will almost always fall into this trap. Why it falls in, and how to make it climb out.

It failed silently because no font was ever passed

I let Antigravity write the first route. The instruction was plain: "Return a 1200×630 OG image with the article title rendered large, via opengraph-image.tsx." In a few seconds the agent produced code that looked entirely reasonable — a few <div>s, the title poured in, returned through ImageResponse. With a local English title, it rendered. Japanese, however, came out as tofu.

Most people suspect font-family at this point, but the cause sits one layer earlier.

Inside ImageResponse is Satori, an engine that converts HTML/CSS into SVG. And Satori bundles no fonts of its own. If a glyph isn't present in a font you explicitly pass through the fonts array, that character is silently dropped. The Latin text appeared only because it happened to fall within the minimal shapes Satori carries internally; Japanese was "a character it never had" from the start.

The agent's code had no fonts entry at all. Because nothing errors, the agent itself concludes it "succeeded." That is precisely the boundary a human has to eyeball at least once.

Passing woff2 doesn't fix it either — the second trap

"So pass a font," you think, and you have Antigravity add fonts. Now you hit a different wall. Many agents reach for the common idiom of fetch-ing a Google Fonts woff2 and passing it along. That fails silently too.

The reason: Satori (and its rasterizer) cannot parse woff2. woff2 is a Brotli-compressed font format, and the formats Satori reads directly are TrueType (ttf), OpenType (otf), and woff. Hand it woff2 and you get either nothing drawn or, depending on the runtime, a thrown exception.

Here is the landscape in one table.

FormatUsable in SatoriTypical sourceNotes
woff2NoBrowser-facing Google FontsBrotli-compressed. The most commonly served, yet unreadable by Satori
woffYesSome CDNsReadable but hard to source
ttf / otfYescss2 fetched server-sideWhat we use here. Subsetting works

So the goal is clear: get a TrueType that contains only the Japanese glyphs you draw, cheaply, on the edge. The full Noto Sans JP is several MB; pulling the whole thing on every request isn't realistic. You only need the dozen-or-so characters in the title.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦The real reason CJK text becomes empty boxes in ImageResponse (Satori cannot parse woff2) plus a complete TrueType-subset implementation
✦How css2?text= fetches only the glyphs you draw — a few dozen KB on the edge — with a subset that includes your fixed labels too
✦A manual clamp that handles overflow within Satori's limits, and the verification step that makes an agent fix this correctly
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

Related Articles

▣ App Dev2026-09-29
Purging Cloudflare Didn't Bring Back the New Wallpapers: the Stale JPEGs Were Held by XPageSpeed on the Origin
An operations record from a wallpaper app: images overwritten at the same URL kept arriving stale even after a Cloudflare purge. Covers the three-way local/origin/edge probe that names the guilty layer, when prefix purge beats per-URL purge, and how to switch off Xserver's XPageSpeed in .htaccess without a 500.
▣ App Dev2026-09-19
I Asked for the Whole Quote PDF Tool at Once, Then Split It Into Four Requests
How I moved my client quote paperwork from copy-and-edit to a small PDF tool built with Antigravity, why the first single-shot request had to be thrown away, and the four smaller requests that replaced it, with working code.
▣ App Dev2026-09-03
A dependency without musllinux wheels doesn't fail on Alpine. It just gets slower
On Alpine, a dependency without musllinux wheels can quietly fall back to pure Python. Protobuf went from 0.043ms to 36.2ms, and pip reported no error.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links