Skip to content
Issue docs

Render-blocking scripts or stylesheets in the head

Standardrender_blocking_resourcesIssue 191

What is this issue?

The page's <head> loads files that the browser must download — and, for scripts, run — before it can paint anything on screen.

Two kinds of tag do this:

  • A synchronous script: <script src="..."> without async or defer. The browser stops reading the page, fetches the script, runs it, and only then carries on.
  • A stylesheet: <link rel="stylesheet" href="...">. The browser will not paint until it has the CSS, so the page does not flash unstyled.

For a page to pass this check:

  • Every script in the <head> is async, defer or type="module".
  • No more than two stylesheets in the <head> block the first paint.

Why stylesheets get an allowance. Blocking on CSS is intended behaviour — without it, visitors would see a flash of unstyled content. One or two stylesheets (the site's own, plus perhaps a font or framework sheet) are normal. The problem is the head with a stylesheet per plugin, every one of which must arrive before the first pixel. Scripts get no allowance: a script in the head that needs to block rendering is rare, and defer fixes most of them.

What is not counted: scripts at the end of the <body> (the classic fix), type="module" scripts (deferred by definition), nomodule scripts (skipped by modern browsers), data blocks such as application/ld+json structured data, and stylesheets whose media cannot match a screen (print, speech, not all) or that are disabled or alternate.

Why it matters

  • Nothing appears until they arrive. Each blocking file adds at least one network round trip before First Contentful Paint. On a mobile connection that is hundreds of milliseconds per file, and the visitor sees a blank screen the whole time.

  • Largest Contentful Paint follows. The main content cannot paint before the first paint. Render-blocking resources are one of the most common reasons a page fails the LCP threshold in Core Web Vitals.

  • Third-party scripts make it unpredictable. A synchronous script from another domain ties your page's first paint to that domain's speed and availability. If it is slow, your page is slow; if it is down, your page can hang.

  • Lighthouse reports it. "Eliminate render-blocking resources" is a standard Lighthouse opportunity, and Google's page experience signals reward pages that paint quickly.

Effect on the health score

This is a standard issue. It deducts from the Delivery & Trust score for each affected page.

How to fix it

Scripts

Add defer to scripts that work with the page's content. They download in parallel and run, in order, after the HTML is parsed:

<!-- Before -->
<script src="/js/app.js"></script>

<!-- After -->
<script defer src="/js/app.js"></script>

Use async for independent scripts such as analytics, which do not depend on other scripts or on the DOM:

<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"></script>

Or move the script to the end of the <body>.

Stylesheets

  1. Inline the critical CSS — the rules needed to render the top of the page — in a <style> block, and load the full stylesheet without blocking:

    <style>/* critical above-the-fold rules */</style>
    <link rel="stylesheet" href="/css/site.css" media="print" onload="this.media='all'">
    <noscript><link rel="stylesheet" href="/css/site.css"></noscript>
  2. Combine stylesheets that are always used together into one file.

  3. Remove what the page does not use — plugin stylesheets loaded on every page for a feature that appears on one.

  4. Give print styles media="print" so they never block the screen.

Examples

Example 1: A synchronous script

<head>
  <link rel="stylesheet" href="/css/site.css">
  <script src="/js/jquery.min.js"></script>
</head>

Reported: one synchronous script. The single stylesheet alone would pass.

Example 2: Deferred and async scripts

<head>
  <link rel="stylesheet" href="/css/site.css">
  <script defer src="/js/app.js"></script>
  <script async src="https://analytics.example.net/a.js"></script>
  <script type="application/ld+json">{"@context":"https://schema.org"}</script>
</head>

Passes.

Example 3: Too many stylesheets

<head>
  <link rel="stylesheet" href="/wp-content/plugins/slider/slider.css">
  <link rel="stylesheet" href="/wp-content/plugins/forms/forms.css">
  <link rel="stylesheet" href="/wp-content/plugins/gallery/gallery.css">
  <link rel="stylesheet" href="/wp-content/themes/site/style.css">
</head>

Reported: four blocking stylesheets, two more than allowed.

Example 4: Non-blocking stylesheets

<head>
  <link rel="stylesheet" href="/css/site.css">
  <link rel="stylesheet" href="/css/fonts.css">
  <link rel="stylesheet" href="/css/print.css" media="print">
  <link rel="stylesheet" href="/css/rest.css" media="print" onload="this.media='all'">
</head>

Passes: two blocking stylesheets; the print-media ones do not block.

How PixyScan detects this

  1. Reads the HTML the server sent, not the page after JavaScript ran. When a scan renders pages in a browser, scripts and stylesheets that JavaScript adds later appear in the head without async, although they never blocked the first paint. So PixyScan asks the browser for the original response and reads its <head>. If that is not available for a page, the page is skipped for this check rather than judged on the wrong document.

  2. Finds blocking scripts in the <head>: <script> with a src, a JavaScript type (no type, or a JavaScript MIME type such as text/javascript), and neither async nor defer. Skips type="module", nomodule, and any other type (application/ld+json, text/template, ...), which the browser does not run.

  3. Finds blocking stylesheets in the <head>: <link> whose rel includes stylesheet (in any case) but not alternate, with an href, not disabled, and a media that could match a screen. media is judged by its media type: print, speech and not all do not match; all, screen, not print and queries such as (min-width: 600px) might, so they count.

  4. Ignores content inside <noscript> and <template>, which is not loaded.

  5. Reports when there is at least one blocking script, or more than two blocking stylesheets.

  6. Records both counts and up to five of the URLs, resolved to full addresses, scripts first.

What we store

Storage Level

Page Level


Database Table / Prisma Model

audit_issues.details


Stored Fields

Field Type Description
message String How many scripts and stylesheets block the first paint
blockingScriptCount Int Synchronous scripts in the head
blockingStylesheetCount Int Render-blocking stylesheets in the head
sampleUrls String[] Up to five of the blocking URLs, resolved, scripts first
stylesheetAllowance Int How many blocking stylesheets are allowed (2)

Detection Dependencies

  • The HTML document as served by the server

Note

Kept on the finding only. The counts describe one page's markup and are not stored in a column of their own.

Further reading