---
title: "Render-blocking CSS, fonts, and scripts"
slug: "render-blocking-resources"
category: "seo"
tags: ["seo", "performance", "critical-css", "font-loading", "javascript", "fcp"]
status: "stable"
last_updated: 2026-09-27
summary: "How to stop CSS, web fonts, and JavaScript from delaying first paint: inline critical CSS, font-display swap with self-hosted subset fonts, and defer or interaction-gate scripts."
related: ["[[seo/page-speed]]", "[[seo/core-web-vitals]]", "[[frontend/css]]", "[[frontend/astro-performance]]", "[[howto/optimize-core-web-vitals]]", "[[seo/javascript-seo]]"]
---

## 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 [[seo/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.

```css
@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.

## Related

- [[seo/page-speed]]: TTFB, FCP, images, and budgets
- [[seo/core-web-vitals]]
- [[frontend/css]]
- [[frontend/astro-performance]]
- [[howto/optimize-core-web-vitals]]
- [[seo/javascript-seo]]
