Render-blocking resources: what blocks the first paint and how to unblock it

You want the first paint fast and the hero visible. This page shows what blocks rendering, how Lighthouse flags it, and the fixes that move your metrics. It is written for founders who ship their own site.

By , founder of Porteur · Updated 14 September 2026 · Markdown

What blocks the first paint and why it hurts

A stylesheet in the head blocks rendering. The browser must download and parse CSS before it can paint anything. That is by design.

A synchronous script in the head blocks HTML parsing. The browser stops, downloads the script, then runs it. Parsing only resumes after it finishes.

Together they delay First Contentful Paint and Largest Contentful Paint. LCP should be 2.5 s or better. Over 4 s is poor, as of 2026.

They also queue work on the main thread. That raises Total Blocking Time in lab tests, the Lighthouse proxy for interaction lag. TBT over 200 ms is a problem in the score.

How to read Lighthouse and PageSpeed Insights for this

PageSpeed Insights shows field data at the top and a Lighthouse run below. Field data is the Chrome User Experience Report over 28 days. It can be per URL or per origin when pages lack traffic. It does not include iOS.

Lighthouse is lab data from one simulated load on a mid-range phone over throttled 4G. Scores vary between runs. The Performance score weights Total Blocking Time 30%, LCP 25%, CLS 25%, First Contentful Paint 10% and Speed Index 10%.

  • Audit to find: Eliminate render-blocking resources. It lists blocking CSS and scripts in the head.
  • Audit to watch: Reduce the impact of third-party code. It shows heavy external scripts.
  • Metrics to track in lab: FCP, LCP and TBT. FCP good up to 1.8 s, poor above 3 s. TBT good up to 200 ms.
  • Field is what counts for Search. The Core Web Vitals assessment uses the 75th percentile of real users. When lab and field disagree, field wins.

Use Lighthouse to verify each change in minutes. Expect the Search Console Core Web Vitals report to reflect field changes after enough visits over 28 days.

Fixes in order of payoff

  • Defer or async every script that is not needed for first paint. Type=module defers by default.
  • Inline critical CSS for above-the-fold content. Load the rest without blocking.
  • Split CSS and remove unused rules. Ship only what the page needs.
  • Avoid @import. It delays CSS discovery and adds requests.
  • Handle web fonts separately. Preload one face used above the fold and use font-display.
  • Trim third-party scripts. Defer, delay on interaction, or remove what nobody uses.
  • If first byte is slow, reduce TTFB with caching and a CDN.

Work from the head down. Get the first paint to appear, then reduce main-thread work, then tidy the rest.

Defer and async JavaScript without breaking pages

Default to defer. It downloads in parallel and executes after parsing, in order. This removes parser blocking and keeps script order stable.

Use async for truly independent scripts where order does not matter. It executes as soon as it arrives, which can reorder execution.

  • Use type=module for modern bundles. Module scripts are deferred by default.
  • Keep inline bootstraps small. Avoid large inline scripts in the head.
  • Move any required inline scripts to run after DOMContentLoaded if they change below-the-fold content.
<!-- Before: synchronous, blocking -->
<head>
  <script src="/js/vendor.js"></script>
  <script src="/js/app.js"></script>
</head>

<!-- After: deferred and modular -->
<head>
  <script src="/js/vendor.js" defer></script>
  <script src="/js/app.js" defer></script>
  <!-- or modern -->
  <script type="module" src="/js/app.mjs"></script>
  <script nomodule src="/js/app.legacy.js" defer></script>
</head>

Third-party scripts raise TBT and hurt Interaction to Next Paint in the field. Remove any that do not earn their place. Defer what remains. Load embeds on interaction with a click-to-load facade. Keep the tag manager container lean. Preconnect only to origins that must be early.

Inline critical CSS and load the rest without blocking

Critical CSS is the minimum styles to render above-the-fold content. On "/", that is the header, hero, and first section. On "/pricing", it is the header, plan grid and CTA.

  1. Extract critical CSS

    Use your build tool or a browser-based audit to capture only above-the-fold rules for your common viewports. Keep it tight.

  2. Inline in the head

    Place a <style> block before any render-blocking link. Keep it high in the head.

  3. Load the remainder non-blocking

    Use a stylesheet link with media="print" and swap to all on load to avoid blocking first paint.

<!-- Inline only above-the-fold styles -->
<head>
  <style>
    /* critical.css: header, hero, first section */
    header{display:flex;align-items:center}
    .hero{min-height:60vh;background:#fff;color:black}
    .cta{display:inline-block;padding:.75rem 1rem}
  </style>
  <!-- Load the rest without blocking -->
  <link rel="stylesheet" href="/css/styles.css" media="print" onload="this.media='all'">
  <noscript><link rel="stylesheet" href="/css/styles.css"></noscript>
</head>

Test above-the-fold rendering on a throttled device. The page should paint structure and readable text without waiting for the full CSS file. Avoid inlining everything. Keep the inline block focused on the viewport at load.

Split CSS and remove what pages do not use

Large global CSS forces every page to wait on rules it never applies. That blocks rendering and wastes bandwidth on mobile connections.

  • Code split by route. Ship a lean base for layout and typography, then a page bundle like /css/pricing.css.
  • Remove unused rules. Use your browser’s Coverage panel in devtools to find selectors never hit on "/" or "/guides/getting-started".
  • Stop chaining CSS with @import. Link files separately in the HTML head so the browser discovers them early.
  • Audit component libraries. Import only the parts you use. Avoid full resets and utility sets if you use a handful of classes.

After a clean split, the head shows one small base file loaded non-blocking. Route CSS loads late and does not block the first paint. Above-the-fold content still renders from the inline block.

Handle fonts so they do not block or shift

Web fonts can delay text paint and shift layout. Treat them like a separate budget. They should not block LCP or cause CLS.

  • Use font-display: swap or optional. Text renders with a fallback immediately.
  • Preload the one face used above the fold: <link rel="preload" as="font" href="/fonts/YourFont.woff2" type="font/woff2" crossorigin>.
  • Self-host WOFF2. A third-party host adds a DNS lookup, a connection and a TLS handshake before the file.
  • Subset with unicode-range. Ship only glyphs you need.
  • Prefer a variable font to multiple static files if you need several weights.
  • Or use a system font stack for zero font cost.
  • If the swap changes layout, match metrics on the fallback with size-adjust and ascent-override to avoid CLS.

The fixed head preloads one WOFF2 file and declares font-display. Above-the-fold text paints with the fallback first, then swaps without layout jump when the font arrives.

A small before and after of the head

<!-- Before: blocks everything -->
<head>
  <link rel="stylesheet" href="/css/app.css">
  <script src="/js/jquery.js"></script>
  <script src="/js/app.js"></script>
</head>
<!-- After: paints fast, stays interactive -->
<head>
  <!-- inline critical CSS -->
  <style>header{display:flex}.hero{min-height:60vh}</style>

  <!-- non-blocking CSS for the rest -->
  <link rel="stylesheet" href="/css/app.css" media="print" onload="this.media='all'">
  <noscript><link rel="stylesheet" href="/css/app.css"></noscript>

  <!-- fonts handled separately -->
  <link rel="preload" as="font" href="/fonts/Inter-roman.woff2" type="font/woff2" crossorigin>
  <style>@font-face{font-family:Inter;src:url(/fonts/Inter-roman.woff2) format('woff2');font-display:swap;}</style>

  <!-- scripts deferred -->
  <script src="/js/vendor.js" defer></script>
  <script src="/js/app.js" defer></script>
</head>

Load this on a mid-range phone over simulated 4G. You should see the header and hero paint, then styles fill in without blocking. Scripts execute after parsing, so the page stays responsive.

Check the impact on LCP, TBT and INP

Largest Contentful Paint measures the largest image or block of text in the viewport. It is delayed by slow TTFB, late discovery and render-blocking resources. Fixing the head usually lowers LCP on "/" and "/pricing" first.

Total Blocking Time is lab only. It sums main-thread long tasks between First Contentful Paint and Time to Interactive. Because Lighthouse cannot click your page, TBT is its proxy for Interaction to Next Paint. Lower TBT often predicts a better INP, but validate in the field.

  • Run Lighthouse locally a few times. Look for lower TBT and faster FCP and LCP.
  • Open PageSpeed Insights. Compare the new lab run and, when available, field data.
  • Watch Search Console’s Core Web Vitals report. It updates from CrUX over 28 days at the 75th percentile.
  • If LCP is still slow, check hero image handling: no loading=lazy on the hero, fetchpriority=high, or preload for a CSS background.

Google’s page experience uses the Core Web Vitals signal at the Good thresholds. It is smaller than relevance and content. The Lighthouse score itself is not a ranking factor. Mobile-first indexing is complete, so optimise the mobile experience first.

Common edge cases and how to handle them

  • A script must run before paint: inline a tiny feature detection or CSS class toggle, and defer the heavy script.
  • CSS variables needed site-wide: keep them in the inline critical block. Load the rest non-blocking.
  • Framework hydration inflates TBT: split hydration by island or route. Defer non-critical islands until after load.
  • A single CSS file across pages: still inline per-page critical CSS. The non-blocking link can be shared.
  • A font must be private: self-host with WOFF2 and preload. Use size-adjust on the fallback to prevent a layout jump.

Keep changes simple. Test one tweak at a time on "/" and one key landing page. Check that analytics and consent still work, but later in the load. If something breaks, roll back the last edit and try the safer option, usually defer over async.

Questions

Check my site, free

Drop your homepage URL to see which CSS and scripts block first paint and the top three fixes, free, in about thirty seconds.

  • Free check, no card
  • Read-only, your own accounts
  • Readable by your agent

Read next