Sheath rule
perf-no-render-blocking
async or defer to avoid blocking page rendering.
- Package
- Core
- Category
- Performance
- Default severity
- warning by default
- Auto-fix
- Auto-fix available
(dangerous)
#Why
A plain <script src> stops HTML parsing until the file has been fetched and
run. In <head>, or before renderable content in <body>, nothing below it is
parsed, so nothing below it can be painted. Several such scripts also run one
after another rather than together.
defer fetches in parallel and runs after parsing, in document order. async
fetches in parallel and runs as soon as it arrives, in no guaranteed order.
#Examples
#Bad
<head>
<!-- Render-blocking scripts -->
<script src="/js/analytics.js"></script>
<script src="/js/widget.js"></script>
<script src="/js/app.js"></script>
<script type="module" blocking="render" src="/js/module.js"></script>
</head>
Before the document body starts, an explicit blocking="render" token is
reported even when a script also uses async, defer, or type="module".
Adding defer does not neutralize that effective token, so the rule does not
offer an automatic fix for this case. After <body> starts, the browser cannot
add a script to the render-blocking set, so an otherwise non-blocking body
script is not reported merely for carrying the token.
#Good
<head>
<!-- Async for independent scripts -->
<script src="/js/analytics.js" async></script>
<!-- Defer for DOM-dependent scripts -->
<script src="/js/app.js" defer></script>
<!-- Module scripts are deferred by default -->
<script src="/js/module.js" type="module"></script>
</head>
<!-- Or place scripts at end of body -->
<body>
<!-- content -->
<script src="/js/app.js"></script>
</body>
#async vs defer
Choose an attribute based on whether execution order matters.
| Attribute | Download | Execution | Use Case |
|---|---|---|---|
| (none) | Blocks parsing | Immediately | Critical inline scripts |
async |
Parallel | When ready | Independent scripts (analytics) |
defer |
Parallel | After DOM parse | DOM-dependent scripts |
type="module" |
Parallel | After DOM parse | ES modules |
#Visual Timeline
Without async/defer:
HTML: ═══════░░░░░░░░░░░░░═══════════
↑ blocked ↑ resumes
Script: [download][execute]
With async:
HTML: ═══════════════════════════════
Script: [download][execute]
↑ may interrupt
With defer:
HTML: ═══════════════════════════════●
Script: [download] [execute]
↑ after parsing, before DOMContentLoaded
#Options
Use this option to exempt selected script URLs.
| Option | Type | Default | Description |
|---|---|---|---|
excludePatterns |
array | [] |
URL patterns to exclude |
<?php
'perf-no-render-blocking' => ['warning', [
'excludePatterns' => ['critical.js'],
]],
#Auto-fix
The fixer adds defer, and the fix is dangerous: it is skipped by a plain
--fix and applied only under --dangerous.
<!-- Before -->
<script src="/js/app.js"></script>
<!-- After -->
<script src="/js/app.js" defer></script>
defer does not just delay the download. It moves the script's execution to
after parsing, so anything that used to run between this tag and the end of the
document now runs first:
<head>
<script src="/js/analytics.js"></script>
<script>analytics.track('view');</script> {{-- breaks once the first is deferred --}}
</head>
Dependencies between scripts cannot be determined from the template. Review
the diff, or use async manually where execution order does not matter.
#What's Skipped
- Inline scripts (no
srcattribute) - Scripts with
type="module"(deferred by default) - Non-JavaScript types (JSON-LD, etc.)
- Scripts at the end of
<body>, when only scripts, comments, whitespace, and non-rendering Blade control syntax follow - Scripts inside capture-only
@section,@push,@pushIf,@prepend, and once variants. Their output location is determined by the layout's@yieldor@stack, so this file cannot determine whether the script blocks rendering. A section closed with@showrenders at its definition site and is still checked
<!-- Not reported: renders at the layout's @stack('scripts') -->
@push('scripts')
<script src="/js/chart.js"></script>
@endpush
#Notes
- Checks apply to scripts in
<head>and body scripts that precede renderable content - Scripts at the end of
<body>don't need async/defer - Use
deferwhen script order matters,asyncwhen it doesn't - JavaScript MIME essence matching follows HTML exactly: it is case-insensitive, but surrounding whitespace or parameters make the value a non-JavaScript data-block type
#References
- web.dev: Optimize resource loading - How to optimize render- and parser-blocking resources
- MDN: async and defer - Documentation on script loading attributes
#Related Rules
- perf-lazy-load-images - Defer image loading