0.33.0+ HarfBuzz: WOFF glyphs render as stacked horizontal bars (Inter Bold)
Summary
Upgrading satori from 0.32.0 to 0.33.4 turns Inter Bold WOFF text into solid “redacted” bars.
The SVG no longer contains real glyph outlines. Every character is the same five stacked rectangles, only translated along the x-axis. Adjacent stacks overlap, so the rasterised PNG looks like black bars instead of letters.
0.32.0 (same font, same markup, same rasteriser) renders Inter correctly.
This started in 0.33.0, which added HarfBuzz text shaping (https://github.com/vercel/satori/pull/735, b2127fe, 2026-08-20). 0.33.1–0.33.4 did not fix it.
Satori’s README still lists TTF, OTF, and WOFF as supported (not WOFF2). This file is WOFF, not WOFF2, and not a variable font.
Versions
| Version | Date | Changelog | Result |
|---|---|---|---|
| 0.32.0 | 2026-08-20 | backdrop-filter | OK — last good release |
| 0.33.0 | 2026-08-20 | feat: Add HarfBuzz text shaping (#735) | broken |
| 0.33.1 | 2026-08-21 | cache / ~12% faster | still broken at 0.33.4 |
| 0.33.2 | 2026-08-21 | better font caching | still broken at 0.33.4 |
| 0.33.3 | 2026-08-21 | opacity processing | still broken at 0.33.4 |
| 0.33.4 | 2026-08-24 | playground Geist tweak | broken (confirmed) |
0.33.4 still depends on both @shuding/[email protected] and [email protected].
Expected
- SVG
<path d="…">is real Inter Bold outlines (curves, counters, distinct shapes per letter). - Rasterised PNG is readable type.
Actual
- Satori emits one
<path>whosedis only M / L / Z rectangles. - There are no cubic or quadratic curves.
- Every “glyph” is five ~44×9px bars, shifted ~26px, overlapping.
First glyph (satori 0.33.4):
M49.2 60.8 L5.4 60.8 L5.4 51.4 L49.2 51.4 L49.2 60.8Z
M49.2 47.8 L5.4 47.8 L5.4 38.4 L49.2 38.4 L49.2 47.8Z
M49.2 34.8 L5.4 34.8 L5.4 25.4 L49.2 25.4 L49.2 34.8Z
M49.2 21.8 L5.4 21.8 L5.4 12.4 L49.2 12.4 L49.2 21.8Z
M49.2 8.8 L5.4 8.8 L5.4 -0.6 L49.2 -0.6 L49.2 8.8ZThe next characters are the same five bars, translated (~75.2, 101.2, 127.2, …). That shared outline + overlap is why the PNG looks redacted rather than like Inter.
Minimal reproduction (Node, ESM)
import fs from 'node:fs';
import satori from 'satori';
import { html } from 'satori-html';
const font = fs.readFileSync('./inter-bold.woff'); // Inter Bold, static WOFF (not WOFF2)
const svg = await satori(
html(`<div style="display:flex;flex-direction:column;width:1200px;height:630px;font-family:Inter;font-weight:700;color:#141a26;font-size:52px;line-height:1.15">Hello Inter</div>`),
{
width: 1200,
height: 630,
fonts: [
{
name: 'Inter',
data: font,
weight: 700,
style: 'normal',
},
],
},
);
fs.writeFileSync('out.svg', svg);
console.log('curves', (svg.match(/[CcQq]/g) || []).length);
console.log('rects', (svg.match(/L[\d.]+ [\d.]+L[\d.]+ [\d.]+L[\d.]+ [\d.]+Z/g) || []).length);0.33.4: out.svg is the 5-bar stacks (curves ≈ 0).
0.32.0: same WOFF produces real Inter outlines.
We also rasterised with @resvg/resvg-js. The bars are already in the SVG, so this is not a Resvg bug.
Font used in production: Inter Bold .woff, passed as a Node Buffer:
fonts: [{ name: 'Inter', data: fs.readFileSync('./inter-bold.woff'), weight: 700, style: 'normal' }]Environment
- OS: macOS (Darwin)
- Node.js: 22.16.0
- Module system: ESM (
"type": "module") - Broken:
[email protected] - Last good:
[email protected] [email protected](HTML string → vnode)- Font: Inter Bold, WOFF, weight 700, style
normal(not WOFF2, not variable) - Canvas: 1200×630
- Found via
eleventy-plugin-og-image(dependencysatori@^0.26.0, overridden). Reproduced with a directsatori()call, so the plugin is not required.
What we already ruled out
These still produced the 5-bar paths on 0.33.4:
- Template / Yoga / extra CSS — a single inline
display:flexdiv is enough. line-height—1.15and60pxproduced the same rectangle paths.fflate0.8.x — the parent app overridesfflateto0.8.3. Pinning satori to 0.32.0 with that override still in place restored glyphs, so this is not a WOFF inflate break from fflate 0.8. (satori itself declares[email protected].)- Resvg / Sharp — the SVG from satori 0.33 already contains the bars.
Workaround
Pin satori to 0.32.0 (last release before #735):
{
"overrides": {
"satori": "0.32.0"
}
}Debugging notes / questions
These would help isolate the HarfBuzz integration:
- WOFF-only, or TTF/OTF too? We did not convert this face to TTF. A same-weight Inter TTF vs WOFF comparison would tell you if outline export is broken for WOFF blobs specifically.
- Shared outline for every code point. Every letter is the same 5-rect path, only translated. That looks like one broken outline reused for every glyph ID, not per-glyph decode noise.
- Who draws the path? 0.33.4 still depends on both
harfbuzzjsand@shuding/opentype.js. Is HarfBuzz returning glyph IDs that then get the wrong outlines from opentype.js, or ishb_draw/ outline export itself wrong for WOFF? - WOFF vs glyf. 0.32 used opentype.js for both parse and path. 0.33 added HarfBuzz shaping. If HarfBuzz is given raw WOFF bytes (or decompressed glyf that still needs WOFF transforms), outline export could collapse to this rectangle pattern.
Related
- https://github.com/vercel/satori/pull/735 — Add HarfBuzz text shaping (regression window)
- https://github.com/vercel/satori/issues/83 — Switch to Harfbuzz
- https://github.com/vercel/satori/issues/791 and https://github.com/vercel/satori/issues/794 — 0.33 WASM/bundling failures; different symptom (install/bundle, not glyph paths)
I searched existing issues and did not find one about WOFF glyphs becoming stacked bars after 0.33.
Broken result below:
The plugin is being used with 11ty and https://github.com/KiwiKilian/eleventy-plugin-og-image
Source: vercel/satori