Threads · note 04 / 4 · by Vorluno · Sep 21, 2026

Satori does not read woff2

Generated OpenGraph images fail silently when the font you serve to browsers is not a font the image renderer can parse. The fix is a second copy of the font — in a format the site never ships to users.

The symptom

app/opengraph-image.tsx with Next's ImageResponse. Locally: 500, empty body, nothing in the console worth reading. The same component with the font lines removed: 200.

The cause

ImageResponse renders with Satori, and Satori parses TTF, OTF and WOFF — not WOFF2. Our fonts are self-hosted WOFF2 through next/font/local, which is right for the browser and useless for the renderer.

The fix that holds

Convert once, commit the result, read it from public/ so the standalone image has it too:

python -c "from fontTools.ttLib import TTFont; f=TTFont('cabinet-grotesk-700.woff2'); f.flavor=None; f.save('public/og/cabinet-grotesk-700.ttf')"
const cabinet = await readFile(join(process.cwd(), "public/og/cabinet-grotesk-700.ttf"));
return new ImageResponse(…, { width: 1200, height: 630, fonts: [{ name: "Cabinet", data: cabinet, weight: 700, style: "normal" }] });

Two details that cost us an hour each: fetch(new URL("./font.ttf", import.meta.url)) works in Next's docs and not in a shared package (the asset is not traced); and a dynamic route's opengraph-image.tsx takes params directly — generateImageMetadata is for several images of one page, and using it there produced one image per slug per page.

// next noteA stopped Lenis still eats the wheel