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:
1 parent
239125a8e5
commit
4c8eeda147
4 files changed
+114
-26
No files matched your search
+47
-22
@@ -214,27 +214,32 @@ pub struct Page {
|
||||
const NEWSREADER: &[u8] = include_bytes!("static/fonts/Newsreader.woff2");
|
||||
const NEWSREADER_ITALIC: &[u8] = include_bytes!("static/fonts/Newsreader-italic.woff2");
|
||||
|
||||
/// The stylesheet with both Newsreader faces embedded as `data:` URIs.
|
||||
/// The stylesheet with both Newsreader faces pointed at versioned URLs.
|
||||
///
|
||||
/// A font referenced by URL is applied *after* first paint whenever the browser
|
||||
/// has to bring it back from the disk cache (Firefox drops fonts from memory as
|
||||
/// soon as no page uses them), which shows as a flash of invisible or fallback
|
||||
/// text on the first navigation after an idle spell. Fonts embedded in the
|
||||
/// render-blocking stylesheet are decoded synchronously, so the first paint is
|
||||
/// already set in Newsreader.
|
||||
/// The faces used to be inlined here as `data:` URIs on the theory that a font
|
||||
/// fetched by URL lands after first paint and flashes. That theory was wrong:
|
||||
/// the flash of unstyled content came from script ordering (a parser-blocking
|
||||
/// `theme.js` ahead of the stylesheet let Gecko paint before the sheet applied,
|
||||
/// Bugzilla 1459305), and moving the script after the `<link>` fixed it. What
|
||||
/// the inlining did cost was real: it tripled the render-blocking stylesheet to
|
||||
/// ~305 KB and pushed first paint out by more than a second on mobile.
|
||||
///
|
||||
/// So the URLs stay URLs, carrying `?v=<ASSET_VERSION>` so the immutable
|
||||
/// one-year `max-age` on `/static/*` is safe across deploys. The fonts are part
|
||||
/// of the `ASSET_VERSION` hash, so a new face mints a new URL. `layout.html`
|
||||
/// preloads the regular face, and the faces use `font-display: swap` behind
|
||||
/// metric-matched local fallbacks, so first paint is immediate and the swap
|
||||
/// shifts nothing.
|
||||
pub static APP_CSS: LazyLock<String> = LazyLock::new(|| {
|
||||
use base64::Engine;
|
||||
let data_uri = |bytes: &[u8]| {
|
||||
format!(
|
||||
"url(data:font/woff2;base64,{})",
|
||||
base64::engine::general_purpose::STANDARD.encode(bytes)
|
||||
)
|
||||
};
|
||||
let version = ASSET_VERSION.as_str();
|
||||
include_str!("static/app.css")
|
||||
.replace("url(/static/Newsreader.woff2)", &data_uri(NEWSREADER))
|
||||
.replace(
|
||||
"url(/static/Newsreader.woff2)",
|
||||
&format!("url(/static/Newsreader.woff2?v={version})"),
|
||||
)
|
||||
.replace(
|
||||
"url(/static/Newsreader-italic.woff2)",
|
||||
&data_uri(NEWSREADER_ITALIC),
|
||||
&format!("url(/static/Newsreader-italic.woff2?v={version})"),
|
||||
)
|
||||
});
|
||||
|
||||
@@ -1100,16 +1105,36 @@ mod tests {
|
||||
let expected = format!("/static/app.css?v={}", ASSET_VERSION.as_str());
|
||||
assert!(html.contains(&expected), "{html}");
|
||||
assert!(!html.contains(&format!("/static/app.css?v={}", crate::VERSION)));
|
||||
// The fonts ride inside the stylesheet; a preload would fetch them twice.
|
||||
assert!(!html.contains("rel=\"preload\""), "{html}");
|
||||
// The regular face is preloaded so it starts downloading alongside the
|
||||
// stylesheet that declares it; the italic face is left to load on demand.
|
||||
let preload = format!(
|
||||
"<link rel=\"preload\" href=\"/static/Newsreader.woff2?v={}\" as=\"font\" type=\"font/woff2\" crossorigin>",
|
||||
ASSET_VERSION.as_str()
|
||||
);
|
||||
assert!(html.contains(&preload), "{html}");
|
||||
assert!(!html.contains("Newsreader-italic.woff2"), "{html}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stylesheet_embeds_both_newsreader_faces() {
|
||||
fn stylesheet_points_both_newsreader_faces_at_versioned_urls() {
|
||||
let css = APP_CSS.as_str();
|
||||
assert_eq!(css.matches("url(data:font/woff2;base64,").count(), 2);
|
||||
assert!(!css.contains("/static/Newsreader"));
|
||||
assert!(css.contains("font-display:block"), "{}", &css[..200]);
|
||||
let version = ASSET_VERSION.as_str();
|
||||
assert!(
|
||||
css.contains(&format!("url(/static/Newsreader.woff2?v={version})")),
|
||||
"regular face missing from stylesheet"
|
||||
);
|
||||
assert!(
|
||||
css.contains(&format!("url(/static/Newsreader-italic.woff2?v={version})")),
|
||||
"italic face missing from stylesheet"
|
||||
);
|
||||
// Unversioned references would be cached forever under a stale URL, and
|
||||
// inlined faces would put ~280 KB back into the render-blocking sheet.
|
||||
assert!(!css.contains("url(/static/Newsreader.woff2)"));
|
||||
assert!(!css.contains("url(/static/Newsreader-italic.woff2)"));
|
||||
assert!(!css.contains("data:font"));
|
||||
// `swap` paints immediately in the metric-matched local fallback.
|
||||
assert_eq!(css.matches("font-display:swap").count(), 2);
|
||||
assert!(!css.contains("font-display:block"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-serif:"Newsreader", "Iowan Old Style", "Palatino Linotype", Georgia, serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, monospace;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-normal:0em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-sm:.25rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--default-mono-font-family:ui-monospace, SFMono-Regular, Menlo, monospace;--color-paper:var(--paper);--color-paper-2:var(--paper-2);--color-ink:var(--ink)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([mLine truncated
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-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-mono:ui-monospace, SFMono-Regular, Menlo, monospace;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-normal:0em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-sm:.25rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--default-mono-font-family:ui-monospace, SFMono-Regular, Menlo, monospace;--color-paper:var(--paper);--color-paper-2:var(--paper-2);--color-ink:var(--ink)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-vaLine truncated
|
||||
+60
-3
@@ -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;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<title>{{ page.title }} · The Daily EPUB</title>
|
||||
{# Start the regular face downloading with the stylesheet rather than after
|
||||
it parses. `crossorigin` is required even same-origin: font fetches are
|
||||
CORS-mode, so without it the preload would not match the @font-face
|
||||
request and the file would be fetched twice. Only the regular face —
|
||||
italic is rare enough that on-demand loading is the right trade. #}
|
||||
<link rel="preload" href="/static/Newsreader.woff2?v={{ page.asset_version }}" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="stylesheet" href="/static/app.css?v={{ page.asset_version }}">
|
||||
{# Keep this synchronous script *after* the stylesheet: Gecko will not run a
|
||||
parser-blocking script while a stylesheet is pending, so the parser stalls
|
||||
|
||||
Reference in new issue
Block a user