# No synchronous render-blocking scripts in `<head>`

- **Pattern:** `ab-001790` (`marketing-page-speed.resource-optimization.render-blocking-resources`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001790
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001790)

## Why it matters

A synchronous `<script>` tag in `<head>` without `async` or `defer` halts the HTML parser entirely until the script downloads, parses, and executes — this is the most severe form of render-blocking resource under ISO-25010 time-behaviour measurement. Google Tag Manager, Hotjar, and Meta Pixel are frequently copied from vendor docs as bare `<script src>` tags, each adding its full download time as a sequential blocking delay before any page content renders. Two synchronous third-party scripts on a 4G connection can add 400–600ms of blocked parser time, pushing FCP and LCP into 'needs improvement' territory and penalising the page in Google's Page Experience scoring.

## Severity rationale

High because a single synchronous script in `<head>` blocks HTML parsing for the duration of its download, adding its full latency directly to FCP and LCP regardless of the script's functional purpose.

## Remediation

Replace bare `<script>` tags with Next.js `Script` component, specifying the correct strategy for each use case — `afterInteractive` for analytics and pixels, `lazyOnload` for non-critical widgets.

```tsx
import Script from 'next/script'

// Google Tag Manager — fires after hydration
<Script
  src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX"
  strategy="afterInteractive"
/>

// Hotjar — can wait until idle
<Script
  src="https://static.hotjar.com/c/hotjar-XXXXXX.js"
  strategy="lazyOnload"
/>
```

`beforeInteractive` is only appropriate for scripts that genuinely need to run before hydration — A/B testing tools sometimes require it for flicker prevention, but verify with the tool's documentation before using it. The default should always be `afterInteractive`.

## Detection

- **ID:** `render-blocking-resources`
- **Severity:** `high`
- **What to look for:** Check the `<head>` section (root layout, HTML template, `_document.tsx`) for `<script>` tags without `async` or `defer`. Any script in `<head>` without these attributes blocks HTML parsing and rendering. This is the most common mistake with third-party marketing tools: analytics tags, A/B testing scripts, and ad pixels are often pasted directly from vendor documentation which includes synchronous `<script>` tags. Count every CSS and JavaScript resource in the <head> that blocks rendering. Enumerate each with its URL and size.
- **Pass criteria:** No `<script>` tags in `<head>` (or before the closing `</body>`) lack `async` or `defer`. Next.js Script component is used with appropriate strategy (`beforeInteractive` only when genuinely required, `afterInteractive` or `lazyOnload` everywhere else). At least 1 implementation must be confirmed.
- **Fail criteria:** One or more `<script>` tags in `<head>` are synchronous (no `async`, no `defer`). `beforeInteractive` strategy used for scripts that do not genuinely require it. Do NOT pass if more than 2 render-blocking CSS files are loaded in the <head> without media queries or critical CSS extraction.
- **Skip (N/A) when:** No `<head>` scripts present (no third-party scripts).
- **Cross-reference:** The `script-loading-strategy` check verifies that JavaScript does not block the critical rendering path alongside CSS.
- **Detail on fail:** `"2 synchronous <script> tags in root layout <head>: Google Tag Manager and Hotjar snippet pasted verbatim from vendor docs. Both block HTML parsing."`
- **Remediation:** Make every marketing script asynchronous:

  ```tsx
  import Script from 'next/script'

  // Google Tag Manager — afterInteractive is correct
  <Script
    src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX"
    strategy="afterInteractive"
  />

  // A/B testing tools (Optimizely, VWO) — may need beforeInteractive
  // but verify with the tool's documentation first
  <Script
    src="https://cdn.optimizely.com/js/YOUR_ID.js"
    strategy="beforeInteractive"
    // Only use this if the tool genuinely requires it for flicker prevention
  />
  ```

## External references

- iso-25010 performance-efficiency.time-behaviour

Taxons: performance

HTML version: https://auditbuffet.com/patterns/ab-001790
