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-runtimeAntigravity328

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 $10 for lifetime access
View Membership →

Related Articles

App Dev2026-07-14
When the Deploy Was Green but Users Still Saw the Old Build: Field Notes on a Gate That Verifies Your Shipped Commit Reached the Edge
When a deploy reports success but production keeps serving the old build, a post-deploy gate that verifies your shipped commit actually reached the edge via a build stamp closes the gap. Field notes with real operational numbers.
App Dev2026-07-10
An Agent's ORM Code Made p95 Five Times Slower — Measuring Query Counts and Blocking Them in CI
N+1 queries slip past code review because the code looks correct. Here is a CI gate that measures query counts and judges them by their slope against input size, with working code and real numbers.
App Dev2026-07-09
Background Refresh May Never Run — Measuring Execution Opportunity and Redesigning Around Freshness
Scheduling a BGTaskScheduler or WorkManager job is a request, not a guarantee. Here is how I instrumented execution opportunity, defined freshness as an SLO with p50 and p90 targets, and rebuilt the update path into three layers that hold up when background work never runs.
📚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
See all →