Stop inlining the Newsreader faces into the render-blocking stylesheet

/static/app.css was 305 KB because APP_CSS base64-embedded both woff2
faces into it at runtime, and the browser cannot paint until it has the
whole sheet. Lighthouse mobile put FCP and LCP at 2.4 s for it.

The inlining was justified by a Firefox font-flash theory that the later
FOUC investigation (239125a) disproved: the flash came from theme.js
running ahead of the stylesheet, not from fonts arriving late. So point
the faces back at /static/Newsreader*.woff2, versioned with ASSET_VERSION
so the immutable one-year max-age stays safe. Gzipped, the sheet goes
from 304 KB to 12 KB.

That leaves first paint waiting on a font, so switch both faces from
font-display: block to swap and give them metric-matched local fallbacks
(Georgia, Times New Roman, Noto Serif, DejaVu Serif). The overrides are
computed from Newsreader's own metrics at the body optical size against
@capsizecss/metrics widths, so the fallback occupies the same box and the
swap changes glyph shapes and nothing else: every block on the page lands
at the same y-position either way, and CLS stays at 0.001.

Preload the regular face so it starts with the stylesheet rather than
after it parses; italic stays on demand. theme.js keeps its place after
the stylesheet link.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MD4VWGq6mGcd8Bg67qyx9k
This commit is contained in:
thalladaandClaude Fable 5.1 committed 2026-09-04 19:58:28 +00:00
1 parent 239125a8e5
commit 4c8eeda147
4 files changed
+114 -26

No files matched your search

+60 -3
View File
@@ -9,7 +9,7 @@
font-style: normal;
font-weight: 200 800;
font-stretch: normal;
font-display: block;
font-display: swap;
src: url("/static/Newsreader.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@@ -19,11 +19,66 @@
font-style: italic;
font-weight: 200 800;
font-stretch: normal;
font-display: block;
font-display: swap;
src: url("/static/Newsreader-italic.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Metric-matched local fallbacks for Newsreader.
`font-display: swap` paints text immediately in whichever of these the
platform has; the overrides make that first paint occupy the same box as
Newsreader, so the swap changes glyph shapes and nothing else (CLS 0).
size-adjust = (Newsreader xWidthAvg/upm) / (fallback xWidthAvg/upm)
ascent/descent-override = (Newsreader ascent|descent / upm) / size-adjust
Newsreader (src/web/static/fonts/Newsreader.woff2, wght 400 at opsz 17, the
body size): upm 2000, ascent 1470, descent -530, lineGap 0, and a
frequency-weighted average character width of 837 (0.4185 em). Fallback
widths are @capsizecss/metrics v4.2.0 `xWidthAvg`, which uses the same
weighting, except DejaVu Serif which is measured the same way from
/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf. */
@font-face {
font-family: "Newsreader Fallback Georgia";
src: local("Georgia");
/* xWidthAvg 913 / upm 2048 */
size-adjust: 93.88%;
ascent-override: 78.29%;
descent-override: 28.23%;
line-gap-override: 0%;
}
@font-face {
font-family: "Newsreader Fallback Times";
src: local("Times New Roman"), local("Liberation Serif"), local("Tinos");
/* xWidthAvg 832 / upm 2048 */
size-adjust: 103.02%;
ascent-override: 71.35%;
descent-override: 25.72%;
line-gap-override: 0%;
}
@font-face {
font-family: "Newsreader Fallback Noto Serif";
src: local("Noto Serif");
/* xWidthAvg 481 / upm 1000 */
size-adjust: 87.01%;
ascent-override: 84.48%;
descent-override: 30.46%;
line-gap-override: 0%;
}
@font-face {
font-family: "Newsreader Fallback DejaVu Serif";
src: local("DejaVu Serif");
/* xWidthAvg 1058 / upm 2048 */
size-adjust: 81.01%;
ascent-override: 90.73%;
descent-override: 32.71%;
line-gap-override: 0%;
}
:root {
--paper:#f6f3ec; --paper-2:#ece7db; --ink:#1c1b18; --ink-2:#4f4b43; --muted:#7a7568;
--rule:#d8d2c4; --rule-strong:#1c1b18; --accent:#a3231f; --accent-hover:#7c1a16;
@@ -50,7 +105,9 @@
--color-rule-strong:var(--rule-strong); --color-accent:var(--accent);
--color-accent-hover:var(--accent-hover); --color-loved:var(--loved); --color-good:var(--good);
--color-down:var(--down); --color-warn:var(--warn);
--font-serif:"Newsreader", "Iowan Old Style", "Palatino Linotype", Georgia, serif;
--font-serif:"Newsreader", "Newsreader Fallback Georgia", "Newsreader Fallback Times",
"Newsreader Fallback Noto Serif", "Newsreader Fallback DejaVu Serif",
"Iowan Old Style", "Palatino Linotype", Georgia, serif;
--font-sans:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono:ui-monospace, SFMono-Regular, Menlo, monospace;
}