font-subsetting-prompt.md asks a coding agent to make a site's web fonts load faster without changing how anything looks: measure the fonts, audit what the site uses, subset each font, split it into a preloaded core file and an extended file behind unicode-range, and prove the result with before and after pixel diffs.
It is a rewrite of a 25-sentence prompt that worked in an earlier project. Every original sentence is still in it (see the coverage map), restructured along Anthropic's prompting guides for Claude Opus 5.5.
- Paste
font-subsetting-prompt.mdinto Claude Code at the repository root. - Fill in
<project_notes>with the framework, where the fonts and@font-facerules live, and the build and production-like serve commands, or delete the block and let the agent find them. - Run at effort
medium, the Opus 5.5 default. The Opus 5.5 guide says to raise effort only where you measured a gain, so raise it if a run misses audit items.
Each factual claim in the original prompt, checked against a primary source.
| Sentence | Claim | Verdict |
|---|---|---|
| S15 | Extended file downloads only when needed | Verified |
| S16 | unicode-range lists only contained characters |
Verified |
| S18 | A fallback face between core and extended wins | Verified |
| S19 | OFL Reserved Font Name forces a rename | Verified, conditional |
| S8 | hyphens: auto draws U+2010 |
Verified in spec |
| S8, S12 | "OpenType features turned on" | Gap, fixed |
| S11 | Fix an axis only if nothing overrides it | Verified |
| S2, S12 | Drop hinting and stay identical | Tension, reported |
| S25 | Embed core font in the stylesheet later | Trade-off noted |
- S15: CSS Fonts 4 §4.5 example: for Latin-only text "only the font for Latin characters is downloaded".
- S16: CSS Fonts 4 §4.5: a code point inside the declared range that the file lacks still triggers the download, then "the effective unicode-range used for font matching excludes this code point" and matching moves on. The page pays for the file and still draws a fallback glyph.
- S18: CSS Fonts 4 §5: when the matched face lacks the glyph, "the next family name is selected". A fallback face with no
unicode-rangecovers every character, so if it sits in the family list before the extended face, it draws those characters. The fix is one shared family name for core and extended, which CSS Fonts 4 §4.5.1 combines into one composite font. - S19: OFL FAQ 2.6 says removing parts of a font for web delivery is modification; FAQ 2.8 says such a font may not use Reserved Font Names; FAQ 3.8 says a rename is needed "Only if there are declared RFNs". Some fonts declare none, Geist among them (
OFL.txtin vercel/geist-font), so the rewrite tells the agent to read the license first. - S8 hyphen: CSS Text 3 says hyphenation is "usually by inserting a hyphen, U+2010". That browsers fall back to U+002D when the font lacks U+2010 is not confirmed from source here.
- S8, S12 features: CSS Fonts 4 §7.1 requires browsers to turn on
rlig,liga,clig,calt,locl,ccmp,markandmkmkwith no CSS at all. An audit of CSS alone would call them unused and drop them, which changes ligatures and mark placement. The rewrite counts them as used. - S11: a fixed
wghtaxis makes the browser synthesize bold for any other weight a rule asks for (font-synthesis-weight, CSS Fonts 4 §2.8.1), which is visible. - S2, S12 hinting: macOS ignores TrueType hints and Windows applies them (general knowledge, not checked here), so a pixel diff on Linux or macOS cannot prove that dropping hinting is invisible on Windows. The rewrite keeps the instruction and makes the agent report the gap with the bytes at stake.
- S25: on a site that inlines its CSS into every HTML page, a data-URL font repeats in every page and needs
font-src data:in a Content Security Policy. The rewrite keeps the idea as later work the agent only assesses.
Where each original sentence lives in the rewrite.
| # | Original sentence starts | Now in |
|---|---|---|
| S1 | Make this site's web fonts load faster | <goal> |
| S2 | Shrink font bytes as far as possible | <goal> |
| S3 | Treat any visual change as a regression | <hard_constraints> 1 |
| S4 | If an optimization would change rendering | <hard_constraints> 1, <report> 5 |
| S5 | Keep the current loading behavior | <hard_constraints> 2 |
| S6 | Don't add font processing to the build | <hard_constraints> 3, step 6 |
| S7 | First measure the current state | step 1 |
| S8 | Then find out what the site actually uses | step 2 |
| S9 | Cut each font down to that | step 3 |
| S10 | Keep the used characters plus Latin-1 | step 3 |
| S11 | Limit variable axes to the ranges in use | step 3 |
| S12 | Drop unused OpenType features and hinting | step 3, step 7 |
| S13 | Split each font into two files | step 4 |
| S14 | A small core with ASCII | step 4 |
| S15 | An extended file behind unicode-range |
step 4 |
| S16 | Each file's unicode-range should list |
step 4 |
| S17 | Watch out for a few things | step 5 heading |
| S18 | A generated fallback face | step 5 |
| S19 | Fonts with a Reserved Font Name | step 5 |
| S20 | Check the generated files | step 5 |
| S21 | Before calling it done, build a baseline | step 7 |
| S22 | Explain every visible difference | step 7 |
| S23 | Re-measure bytes per page | step 7 |
| S24 | When you're done, tell me | <report> |
| S25 | Later, it may be worth embedding | <later> |
From the Opus 5.5, Opus 5 and general prompting guides on platform.claude.com:
- Sections in XML tags (
<goal>,<hard_constraints>,<steps>,<report>,<later>,<working_style>), so instructions, constraints and output shape stay apart. - A reason on each rule, such as why the fallback faces stay untouched or why a fixed axis is risky. The best-practices guide says Claude generalizes from the reason.
- A "Done when" line on every step, and a fix-and-rediff loop when a diff shows a regression, so the agent can tell finished from unfinished.
- Wanted stops named explicitly: the approval question about a rendering change, asked after all other work, and a blocker only the user can clear. The Opus 5.5 guide says the model responds to instructions that name which stops are wanted, and that a text-only message ends its turn.
- A checklist file of the steps and held-back optimizations, from the Opus 5.5 guide on long runs with several parts.
- Progress cadence: one sentence before the first tool call, then updates on important findings.
- The Opus 5 scope paragraph ("Deliver what was asked, at the scope intended") and its subagent limit.
- An example table for the pixel-diff results, because the guides say an example steers output shape better than a description.
- Technical gaps closed: default-on OpenType features,
@font-facedescriptors matching the new axis ranges, a check when a limited axis range moves the default, Windows hinting, a temporary page when no page shows a required category (such as CJK), and a precise test for "sub-pixel rounding".
Left out on purpose: "double-check your work" lines. The Opus 5 guide says the model verifies its own work and such lines cause over-verification. The pixel diffs stay, because they are the deliverable, not a self-check.