Learn how to defer JavaScript properly to improve page speed, avoid render blocking, and load scripts in the right order without breaking UX.
A slow page often is not slow because your JavaScript is huge. It is slow because the browser is being forced to stop what it is doing, fetch a script, and execute it before it can carry on rendering. If you are trying to work out how to defer JavaScript, that is the bottleneck you are really fixing.
Deferring JavaScript tells the browser, in simple terms, not now, mate. Carry on parsing the HTML, build the page, and run this script once the document has been parsed. That one change can make a noticeable difference to perceived performance, especially on script-heavy pages where every file wants attention immediately.
When you add the `defer` attribute to an external script tag, the browser downloads the script in parallel with HTML parsing. The key difference is that it does not execute the script straight away. Execution waits until the HTML has been fully parsed.
That matters because standard script tags block parsing. The browser sees the script, pauses the page, fetches it if needed, executes it, and only then continues. It is a bit like trying to read a book while someone keeps snatching it out of your hands every three pages.
A deferred script behaves more politely. The browser can keep building the DOM while the script downloads in the background. Once parsing is done, deferred scripts execute in the order they appear in the document.
That last part is important. Order is preserved with `defer`, which makes it safer than `async` when one script depends on another.
In most cases, the implementation is delightfully boring. You add `defer` to your external script tag:
“`html “`
If you have multiple scripts that rely on sequence, you can still defer them:
“`html “`
The browser will download both as it parses the page, then execute `vendor.js` before `app.js`.
That makes `defer` a strong default for front-end behaviour that does not need to run before the HTML is parsed. Think navigation toggles, sliders, tabs, analytics, or most app bootstrapping code.
Deferred loading is usually a good fit when your script needs the DOM to exist before it runs. If your code calls `document.querySelector()` on elements further down the page, `defer` is often exactly what you want.
It is also useful when you want better rendering performance without reshuffling your entire architecture. You do not need a dramatic rewrite. You just need to stop treating every script like it is the main character.
For many sites, especially content-led pages, this can reduce render-blocking behaviour and improve key loading metrics. The browser gets to paint useful content earlier, and users see something happen sooner. That is often half the battle.
`defer` is helpful, but it is not magic glitter for bad loading strategy.
If your deferred script is enormous, the browser still has to download and execute it. You have merely delayed the interruption. That can still be worthwhile, but it does not erase the cost. If the main thread gets hammered by heavy execution after parsing, users may still feel jank when they try to interact.
This is where trade-offs creep in. Sometimes the better fix is code splitting, removing unused dependencies, or loading features only when needed. Deferring a bloated bundle is better than blocking rendering with it, but only by comparison. It is still a bloated bundle.
This is the bit that trips people up.
Both `async` and `defer` allow scripts to download without blocking HTML parsing. The difference is when they execute and whether execution order is guaranteed.
`async` scripts execute as soon as they finish downloading. That means they can interrupt parsing, and they do not respect document order. Great for independent scripts. Slightly cursed for anything with dependencies.
`defer` scripts wait until parsing is complete, and they execute in document order. That makes them much more predictable.
If you are loading analytics, ad scripts, or a completely standalone widget, `async` might be fine. If you are loading your application code or scripts that depend on each other, `defer` is usually the safer option.
So if you are deciding how to defer JavaScript versus when to use `async`, the short version is this: use `defer` for coordinated application behaviour, and use `async` for independent extras.
First, `defer` only works on external scripts with a `src`. If you write an inline script like this:
“`html console.log(‘hello’); “`
The `defer` attribute has no effect. Inline scripts still run immediately when encountered.
Second, deferred scripts execute before the `DOMContentLoaded` event fires. That means if you have listeners waiting for `DOMContentLoaded`, your deferred scripts will run first. Usually that is fine, but it is worth knowing if you are debugging timing issues and starting to question your life choices.
Third, old script patterns sometimes rely on blocking behaviour without realising it. If a script expects another one to have already run, changing loading strategy can expose that dependency. This is not `defer` being broken. It is your codebase telling on itself.
For years, the common advice was to place scripts just before “ so the browser could render the page first. That still works, but `defer` has made the pattern less essential.
With deferred scripts, you can keep script tags in the `head` and still avoid blocking HTML parsing. That can make document structure cleaner and easier to reason about, especially in templated systems.
That said, it depends on your setup. Some legacy projects or third-party integrations behave better with scripts near the end of the body. Modern best practice is not about one sacred placement. It is about understanding what blocks rendering and what does not.
You do not need a PhD in browser internals to verify the change.
Open DevTools and inspect the Network and Performance panels. If a script was previously render-blocking, you should see the browser spending less time stalled during HTML parsing. Lighthouse-style audits will also often flag render-blocking resources before the change and quiet down after it.
More practically, load the page on a slower connection or a lower-powered device. That is where bad script loading habits stop hiding. If content appears sooner and the page feels less sticky during load, you are moving in the right direction.
Just avoid chasing a single score like it is a high score at the arcade. Metrics matter, but so does whether the page actually works well for humans.
Start with scripts that do not need to run before the DOM is parsed. That is usually most of your own front-end code. Apply `defer`, test the page, and check for dependency issues.
If something breaks, look at whether one script assumes another has already executed, whether an inline script is firing too early, or whether a third-party snippet has timing expectations. These are common causes, and they are usually fixable.
For larger sites, be selective. A critical inline script for theme initialisation or anti-flicker behaviour may need to run immediately. A chunky carousel library used halfway down the page almost certainly does not. Not all JavaScript has equal urgency, no matter how loudly it insists otherwise.
Learning how to defer JavaScript is really about learning how browsers prioritise work. Once you see scripts as tasks competing for attention, better decisions get easier. You stop dumping everything into the critical path and hoping the browser will sort it out.
That mindset pays off far beyond one attribute. It helps with lazy loading, code splitting, critical CSS, and all the other performance tweaks that sound fancier than they are. Most of them come down to a simple principle: load what matters first, and stop making users wait for things they do not need yet.
If you want a decent rule of thumb, start by treating `defer` as the default for your external app scripts, then make exceptions only when you have a clear reason. Your browser, and probably your users, will complain a lot less.