JORVEL

Font optimization

Fonts are one of the biggest first-paint costs. Loaded badly they cause FOIT (flash of invisible text), CLS (layout shift from fallback metrics), and an extra DNS hop. The helpers in @jorvel/runtime emit the correct <link> tags and @font-face blocks so the browser can preload, swap, and avoid the double-fetch trap.

Pure-data helpers

These functions return plain JSON / strings. They have no DOM dependency, throw nothing on the server, and ship in the edge bundle of @jorvel/ssr. You can paste their output straight into an SSR template head.

Self-host: preload + @font-face

Self-hosting beats third-party CDNs for two reasons: no extra DNS lookup, and Service-Worker caching is straightforward. The preload + @font-face pair gets you swap behavior without a flash of invisible text.

tsx
import { buildFontPreloadLink, buildFontFaceCss } from '@jorvel/runtime';

const preload = buildFontPreloadLink('/fonts/inter.woff2');
// { rel: 'preload', as: 'font', href: '/fonts/inter.woff2', type: 'font/woff2', crossorigin: 'anonymous' }

const css = buildFontFaceCss([
  { family: 'Inter',          src: '/fonts/inter-400.woff2', weight: 400 },
  { family: 'Inter',          src: '/fonts/inter-700.woff2', weight: 700 },
  { family: 'IBM Plex Mono',  src: '/fonts/plex-400.woff2',  weight: 400, unicodeRange: 'U+0000-00FF' },
]);
// @font-face { font-family: "Inter"; src: url("/fonts/inter-400.woff2") format("woff2"); font-display: swap; font-weight: 400; }
// ...

Always crossorigin=anonymous

Preloaded fonts only avoid a double-fetch if the preload link and the eventual @font-face request use the same CORS mode. The helper hard-codes crossorigin="anonymous" for that reason. If your font CDN does not send Access-Control-Allow-Origin: *, the browser will fetch the file twice and the preload becomes a regression.

Wiring into an SSR head

Combine the two helpers in the document head. Put the preload link before the stylesheet so the browser starts the request as early as possible.

apps/shell/src/template.tsx
tsx
import { buildFontPreloadLink, buildFontFaceCss } from '@jorvel/runtime';

const PRELOADS = [
  buildFontPreloadLink('/fonts/inter-400.woff2'),
  buildFontPreloadLink('/fonts/inter-700.woff2'),
];

const FACE_CSS = buildFontFaceCss([
  { family: 'Inter', src: '/fonts/inter-400.woff2', weight: 400 },
  { family: 'Inter', src: '/fonts/inter-700.woff2', weight: 700 },
]);

export function Head() {
  return (
    <head>
      {PRELOADS.map((link) => (
        <link key={link.href} {...link} />
      ))}
      <style dangerouslySetInnerHTML={{ __html: FACE_CSS }} />
    </head>
  );
}

Google Fonts

The composer URL-encodes weight / italic axes correctly and bakes in display=swap. Pair with the preconnect helper so the DNS + TLS handshake to fonts.gstatic.com happens in parallel with HTML parsing.

ts
import { googleFontsUrl, googleFontsPreconnectLinks } from '@jorvel/runtime';

googleFontsUrl({
  families: [
    { family: 'Inter', weights: [400, 700] },
    { family: 'IBM Plex Mono', weights: [{ italic: false, weight: 400 }, { italic: true, weight: 400 }] },
  ],
});
// https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=IBM+Plex+Mono:ital,wght@0,400;1,400&display=swap

googleFontsPreconnectLinks();
// [
//   { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
//   { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: 'anonymous' },
// ]

Variable fonts

Variable fonts ship every weight in one file. Pass a single srcand let Rspack's asset module handle the URL hash. The weight field becomes a range like 100 900:

ts
buildFontFaceCss([
  { family: 'Inter', src: '/fonts/inter-variable.woff2', weight: '100 900' },
]);

Performance checklist

  • Subset before shipping. Use pyftsubset to strip glyph ranges your product never uses — typically Latin Extended cuts file size by 60-80%.
  • Two weights max in the critical path. Each additional weight is another file the browser blocks on. Defer non-critical weights with font-display: optional.
  • Preload only what renders above the fold. Preloading every weight ratchets up bandwidth without improving LCP.
  • Use size-adjust for the fallback. Pair the web font with a system fallback (system-ui) tuned to similar x-height to minimize CLS during the swap window.
  • WOFF2 only. WOFF and TTF/OTF are 30-50% larger and only used by IE11/Android 4. The helper does not emit fallbacks for those formats by default.

CSP impact

Self-hosted fonts need font-src 'self'. Google Fonts needs font-src https://fonts.gstatic.com and style-src https://fonts.googleapis.com. The buildCsp helper in @jorvel/security can take a list of allowed font origins; otherwise the browser blocks the request with no console error.