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

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-06-21
A Few Low-Density Phones Lost Their Bundled Wallpaper — The drawable vs nodpi Boundary in Play's Density Splits
App Bundle density splits will happily split images that should never be split, dropping a static resource on one density bucket only. Here is how I reproduced it with bundletool and fixed it by moving to drawable-nodpi or disabling density split — with the decision criteria.
App Dev2026-06-21
Feeding Store Review Guidelines as a PDF to the Agent to Build a Per-App Pre-Release Checklist
Using Antigravity v2.1.4's PDF attachment, this walks through reading the App Store and Play review guidelines into the agent as context and turning them into a pre-release checklist tied to your app's actual features.
App Dev2026-06-20
Agent Config Drifts Quietly Across Environments: Detection and Correction
Across two Macs and an automation host, agent settings slowly diverge and only one side fails. Here is how to surface that config drift with normalized hashing and a correction workflow, from an indie developer's setup.
📚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 →