Overview

The browser cannot paint until it has the CSS for the page, it hides text while a web font loads, and a synchronous script stops HTML parsing until it runs. Each of these delays First Contentful Paint and often Largest Contentful Paint. This page covers the three fixes: critical CSS, font loading, and script deferral. Server response time, image formats, and performance budgets live in page-speed.

Load fonts with font-display: swap and self-host

Web fonts block text rendering by default. The browser holds the first paint until the font arrives or a timeout fires.

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-var.woff2") format("woff2-variations");
  font-display: swap;
  font-weight: 100 900;
}
  • font-display: swap paints fallback text immediately, then swaps in the web font.
  • Self-host fonts to avoid extra DNS, TCP, and TLS round-trips to a third-party origin. If you must use a font CDN, add preconnect to its origin.
  • Subset the font to the glyphs the site needs (often Latin). Subsetting a large variable font commonly cuts its size several-fold.
  • Use size-adjust, ascent-override, and descent-override in the fallback @font-face to prevent CLS on swap.

Inline critical CSS and defer the rest

CSS is render-blocking by default. The browser parses the entire stylesheet before painting.

  • Extract the above-the-fold CSS with Critical, Critters, or Next.js’s built-in critical CSS plugin.
  • Inline it in <style> inside <head>. Keep it small; about 14 KB of compressed HTML fits in the first TCP round-trip under the common initial congestion window, which is a useful practitioner target.
  • Defer the rest with <link rel="preload" as="style" onload="this.rel='stylesheet'"> or media="print" swap.
  • Audit unused CSS with Coverage in Chrome DevTools. Sites built on large frameworks often ship mostly unused rules.

Defer JavaScript with async, defer, or interaction-gating

JS parse and execute time blocks the main thread, and the cost is highest on mid-tier phones.

  • First-party JS: ship as a module with type="module" (gets defer semantics for free).
  • Analytics, chat widgets, A/B testing, cookie banners: load on requestIdleCallback or after the first user interaction.
  • Audit third-party JS on a schedule. Vendor scripts accumulate, and much of their code never runs on a given page.
  • Tree-shake aggressively. Import individual functions (lodash-es named imports) instead of a whole utility library.