# 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.

Updated 2026-09-14 · Source: https://porteur.ai/guides/render-blocking-resources

## 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.

> Never use @import in CSS. Link stylesheets with <link rel="stylesheet"> instead.

## 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.

```html
<!-- 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.

```html
<!-- 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

```html
<!-- Before: blocks everything -->
<head>
  <link rel="stylesheet" href="/css/app.css">
  <script src="/js/jquery.js"></script>
  <script src="/js/app.js"></script>
</head>
```

```html
<!-- 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

### Will eliminating render-blocking resources improve rankings?

It can improve Core Web Vitals, which are a ranking signal at the Good thresholds. The signal is smaller than relevance and content. Fix for users first, and let the field data lift in Search Console over the next 28 days.

### Should I use defer or async on scripts?

Prefer defer. It keeps execution order and runs after parsing. Use async only for truly independent scripts, like a widget you do not depend on. Module scripts are deferred by default.

### How much CSS should I inline as critical CSS?

Only what is needed to render the above-the-fold layout and text. Keep it small and page specific. Load the rest non-blocking with media=print and onload. If the inline block grows to cover the full page, you have inlined too much.

### Why do Lighthouse and field data disagree on my fixes?

Lighthouse is one simulated load on a throttled 4G phone. Field data is the 75th percentile of real Chrome users over 28 days. Network, device and cache states differ. Use Lighthouse to validate changes quickly, then watch field metrics settle over time.

### What if I need third-party scripts for analytics or chat?

Keep them, but load them later. Defer by default, or trigger on interaction for embeds. Remove rarely used tools. Keep the tag manager container small and avoid synchronous tags that block parsing.

### Does eliminating render-blocking help Interaction to Next Paint?

It reduces main-thread contention early, which often lowers Total Blocking Time in lab runs. INP in the field also depends on long tasks, heavy handlers and hydration. Break up long work and keep scripts small to improve INP.

## Read next

- [How to read PageSpeed Insights: field data first, then the score](https://porteur.ai/guides/how-to-read-pagespeed-insights): Start with field data, not the score. Learn what Google uses, why scores vary, and how to turn a PageSpeed Insights run into a clear order of fixes.
- [Total Blocking Time: the lab number that stands in for INP](https://porteur.ai/guides/total-blocking-time): What Total Blocking Time measures in Lighthouse, why it weighs 30 percent, how it relates to INP, and the fixes that cut it fast.
- [Largest Contentful Paint: what slows it and what fixes it](https://porteur.ai/guides/largest-contentful-paint): See what usually counts as LCP, what makes it slow, how to fix it in order, and how to read it in Lighthouse and field data.
- [Web fonts and page speed: load one face, and load it first](https://porteur.ai/guides/web-fonts-and-page-speed): Ship crisp type without slowing first paint or shifting layout. Choose one fast path, preload the face above the fold, and stop the swap from moving text.
- [Third-party scripts: the page speed you gave away](https://porteur.ai/guides/third-party-scripts-and-page-speed): See what each third-party script costs and what to do: remove, load on interaction, defer, self-host, facade video and maps, and preconnect smartly.
- [Core Web Vitals for a product site: the three numbers](https://porteur.ai/guides/core-web-vitals): The product founder’s guide to LCP, CLS and INP: what Google measures, why phones decide the pass, common causes, and how to test and fix.
- [JavaScript SEO](https://porteur.ai/glossary/javascript-seo): What JavaScript SEO means, how Googlebot renders, what breaks, how to fix links and content, and how to choose SSR or SSG so pages index fast.

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: https://porteur.ai/
