Web Font Optimization with Claude Code: Practical Guide
Optimize web fonts with Claude Code: preload, font-display, variable fonts, subsetting, CLS/LCP, and safe prompts.
Font Optimization Is a Rendering Problem
Web fonts are usually discussed as a design decision, but the failure mode is performance. A slow font can hide text, swap a heading after the user has started reading, move a call-to-action button, or delay the Largest Contentful Paint element. Japanese, Chinese, Korean, and other large character sets make the risk sharper because a single font family can be much larger than the rest of the first view.
This guide shows how to use Claude Code to implement web font optimization without turning the site into a pile of hints. The goal is not to preload everything. The goal is to choose the font loading strategy, preload only critical files, use font-display deliberately, reduce font bytes with variable fonts and subsetting, decide between self-hosting and third-party delivery, and verify the result with Core Web Vitals evidence.
The official references matter here because browser behavior has details. MDN documents the font-display descriptor, and MDN’s rel="preload" guide explains the preload shape, including font crossorigin. For the broader performance model, use web.dev’s Best practices for fonts, Optimize web fonts, and Web Vitals.
Choose the Loading Strategy Before Editing
Claude Code can make code changes quickly, so the dangerous prompt is “make fonts faster.” That usually produces scattered preload tags, duplicated Google Fonts CSS, or a visual regression in the fallback stack. Start by telling Claude Code which text appears above the fold, which family is required for brand identity, and which fonts can be delayed or removed.
| Use case | Practical strategy | Why it works | Common failure |
|---|---|---|---|
| Japanese or multilingual blog | Use system fonts for body text, then apply a subset web font to headings | Reading stays available even if the web font is late | The display font accidentally becomes the body font |
| SaaS dashboard | Self-host a Latin variable font for UI labels and numbers | One file can cover many weights | Unused italics, axes, or language ranges stay in the file |
| Landing page hero | Preload only the WOFF2 file used by the LCP heading | The conversion text appears early | All weights are preloaded and compete with the hero image |
| Legacy icon font | Replace with SVG or a component icon library | Removes a competing font request | Pseudo-element CSS breaks silently |
For a content site, the most reliable optimization is often boring: keep the body readable with system fonts and reserve the web font for high-impact headings. For a product UI, a variable font such as Inter can be a good fit because the interface may need 400, 500, 600, and 700 weights. For a landing page, the font and hero image compete for the same first-view budget, so pair this article with the Claude Code image optimization guide. For wider performance work, see performance optimization with Claude Code.
Implement Preload and Preconnect in Astro
preload is a strong browser hint. Use it for one or two font files that are definitely used in the first viewport. Do not preload every weight, every language, or fonts used only after scrolling. If you self-host fonts from your own origin, you usually do not need preconnect. If you use Google Fonts or another third-party provider, preconnect can reduce connection setup time for the CSS and font origins.
Here is a copy-pasteable Astro layout pattern. Keep the boolean explicit so a future Claude Code change does not leave third-party hints in a self-hosted setup.
---
// src/layouts/BaseLayout.astro
const criticalFonts = [
{ href: "/fonts/inter-var-latin.woff2", type: "font/woff2" },
{ href: "/fonts/noto-sans-jp-latin-kana.woff2", type: "font/woff2" },
];
const usesGoogleFonts = false;
---
<html lang="en">
<head>
{criticalFonts.map((font) => (
<link rel="preload" href={font.href} as="font" type={font.type} crossorigin />
))}
{usesGoogleFonts && <link rel="preconnect" href="https://fonts.googleapis.com" />}
{usesGoogleFonts && <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />}
<link rel="stylesheet" href="/styles/fonts.css" />
</head>
<body>
<slot />
</body>
</html>
Review the generated HTML after implementation. The href must match the file referenced by @font-face. The preload must include as="font", type="font/woff2", and crossorigin. Most importantly, the preloaded font must actually be used soon. Chrome’s warning about a preloaded resource not being used is a signal to remove the hint, not a harmless message to ignore.
Use font-display Without Creating CLS
font-display: swap is the common default because it renders text with a fallback font first and swaps to the web font when it arrives. That avoids invisible text, but it can still create Cumulative Layout Shift if the fallback font has different metrics. font-display: optional can be better for non-critical body text on slow networks because the browser may keep the fallback instead of swapping late.
The following CSS combines a Latin variable font, a Japanese subset, and fallback metric overrides. The exact size-adjust values should be measured for your font pair, but the structure is the safe starting point.
/* public/styles/fonts.css */
@font-face {
font-family: "InterVariable";
src: url("/fonts/inter-var-latin.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "NotoSansJPSubset";
src: url("/fonts/noto-sans-jp-latin-kana.woff2") format("woff2");
font-weight: 400 700;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+3000-30FF, U+FF00-FFEF;
}
@font-face {
font-family: "InterFallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
:root {
--font-ui: "InterVariable", "InterFallback", system-ui, sans-serif;
--font-ja: "NotoSansJPSubset", "Hiragino Sans", "Yu Gothic", sans-serif;
}
body {
font-family: var(--font-ui);
}
article {
font-family: var(--font-ja);
}
MDN documents unicode-range, which lets a font face cover only part of the character space. That does not magically reduce the file. It tells the browser when the face applies. You still need a smaller WOFF2 file if you want fewer bytes.
Reduce Bytes with Variable Fonts and Subsetting
Variable fonts can replace multiple static files when you need several weights. They are not automatically smaller. A variable font with many axes and broad language coverage can still be heavy. Ask Claude Code to inspect which weights and scripts are actually used before switching.
Subsetting is the more aggressive move: create a smaller font file that contains only the characters you need. It works well for navigation labels, hero headings, and Latin or kana ranges. It is riskier for article body text because new content may introduce characters that are not in the subset. Treat body subsetting as a build process, not a one-time manual export.
# Create a WOFF2 subset with Latin, punctuation, kana, and full-width forms.
python -m pip install "fonttools[woff]"
mkdir -p public/fonts
pyftsubset ./vendor-fonts/NotoSansJP-Regular.ttf \
--output-file=./public/fonts/noto-sans-jp-latin-kana.woff2 \
--flavor=woff2 \
--layout-features='*' \
--unicodes="U+0000-00FF,U+3000-30FF,U+FF00-FFEF"
This command intentionally excludes most kanji. That is fine for a header and navigation subset, but it is not enough for a Japanese article body. If you want body text coverage, extract characters from your MDX files and feed them to pyftsubset with a text file, or create a separate kanji subset. Also verify the font license before self-hosting or modifying a font file.
Decide Between Self-Hosted and Third-Party Fonts
Self-hosting gives you predictable URLs, long cache headers, exact preload targets, and full control over subsetting. It also makes you responsible for license compliance, updates, and build scripts. Third-party delivery, such as Google Fonts, is convenient and often well maintained, but it introduces external connections and an extra CSS request before the font file.
| Decision point | Self-hosted fonts | Third-party fonts |
|---|---|---|
| Connection setup | Same origin | DNS, TLS, CSS origin, font origin |
| Preload target | Exact WOFF2 file | Harder if CSS decides the URL |
| Caching | You control headers and filenames | Provider controls headers |
| Subsetting | Fully customizable | Depends on provider features |
| Operations | More responsibility | Easier setup, more dependency |
For most performance-sensitive pages, self-host critical first-view fonts and delay decorative fonts. For quick prototypes, third-party fonts are acceptable if you use display=swap, preconnect only the required origins, and avoid loading unused weights. Never leave both the provider CSS and self-hosted CSS active at the same time.
Verify with Lighthouse and a WebPageTest-Style Waterfall
After implementation, do not trust the visual result alone. Run Lighthouse for a lab signal, then inspect the network waterfall like WebPageTest: HTML, CSS, critical font, hero image, JavaScript. You want the critical font to start early without delaying the LCP image or the CSS needed to render the page.
URL="https://example.com/"
npx --yes lighthouse "$URL" \
--only-categories=performance \
--chrome-flags="--headless" \
--output=json \
--output-path=./lighthouse-fonts.json
node -e "const r=require('./lighthouse-fonts.json'); for (const id of ['largest-contentful-paint','cumulative-layout-shift','font-display']) console.log(id, r.audits[id]?.displayValue ?? r.audits[id]?.score ?? 'n/a')"
Use this checklist in the browser or WebPageTest waterfall:
| Check | Good signal | Bad signal |
|---|---|---|
| Font count | One or two critical WOFF2 files early | Many weights start before CSS settles |
| Preload usage | Preloaded font is used in the first viewport | ”Preloaded but not used” warning |
| CLS | Heading and CTA keep their size during swap | Button or heading jumps after font load |
| LCP | Font does not delay the hero text or image | Font request blocks the LCP element |
| Repeat view | Cache removes font transfer cost | Fingerprints or headers prevent caching |
Common failures are concrete. A bold font is preloaded but the first view only uses regular text. Google Fonts CSS remains after self-hosting, so the browser downloads two font families. font-display: swap is added, but fallback metrics are not adjusted and the CTA moves. A Japanese subset excludes long vowels, full-width digits, or punctuation. These are exactly the bugs Claude Code can miss unless the acceptance criteria name them.
Claude Code Prompts for Safe Implementation
Split the work into audit, implementation, and verification. Start with a read-only audit.
Audit web font loading in this Astro site. Do not edit files yet.
Find:
- @font-face, Google Fonts, Fontsource, and CSS import locations
- Font files used above the fold
- preload and preconnect hints that are missing or unnecessary
- CLS or LCP risks caused by font swapping
- Candidates for self-hosting, variable fonts, and subsetting
Return:
- A prioritized table of changes
- Files that should be edited and files that must not be touched
- Verification commands and residual risks
Then ask for a narrow implementation.
Implement web font optimization only in these files:
- src/layouts/BaseLayout.astro
- public/styles/fonts.css
- generated files under public/fonts/
Acceptance criteria:
- Preload only WOFF2 files used in the first viewport
- Do not add preconnect when fonts are self-hosted
- Every @font-face has a deliberate font-display value
- Fallback metrics are adjusted to reduce CLS
- Existing routes, article slugs, hero images, and unrelated content are untouched
Verification:
- npm run build
- node scripts/check-code-fences.mjs
- Lighthouse check for LCP, CLS, and font-display
- Report what could not be verified
The prompt is deliberately strict because font optimization touches layout, network priority, build output, and sometimes licensing. Claude Code should be asked to prove the result, not just change CSS.
Practical Result and CTA
In Masa’s workflow, the biggest improvement came from narrowing the task before editing. “Optimize fonts” produced noisy diffs. “Preload only the font used by the LCP heading, keep body text readable with system fonts, and report CLS risk” produced smaller and safer changes. On a Japanese article page, a Latin and kana subset made the first view feel steadier, but body coverage still required a separate character extraction process. The final check that mattered most was a slow-mobile waterfall plus a manual view of the heading and CTA during font swap.
If you are building a team workflow, put the font rules in CLAUDE.md so the same mistakes do not return in the next redesign. Start with the free cheat sheet for safe Claude Code commands, use the product templates when you want reusable prompts and review checklists, and consider Claude Code training when Core Web Vitals, article quality, and monetization CTAs need to be improved together.
Self-check before publication: this article includes more than three concrete use cases, copy-pasteable Astro/CSS/bash examples, official MDN and web.dev links, internal links, specific pitfalls, a natural CTA, and a hands-on verification note.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Permission Receipt Pattern: Record Scope, Proof, and Rollback
A permission receipt pattern for Claude Code: allowed actions, approval boundaries, proof commands, rollback, and revenue CTA checks.
Safe Agent Harness Design for Claude Code and Codex: Permissions, Checks, and Rollback
Build a practical agent harness for Claude Code and Codex with policy, planning, verification, and recovery layers.
Claude Code Subagents: A Practical Guide to Safe Agent Delegation
Claude Code subagent guide for safe parallel article and code work: delegation rules, prompts, pitfalls, and checks.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.