Suggest an editImprove this articleRefine the answer for “Scripts in head”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**A plain `<script src="...">` in `<head>` blocks the HTML parser: the browser stops building the DOM, downloads the file over the network, executes it and only then reads the rest of the markup.** It is obliged to behave this way because the script may change the document through `document.write` or `innerHTML`. While that happens the DOM does not exist yet, the user stares at a white screen, and the FCP, LCP, TTI and TBT metrics all suffer, especially on a slow connection. The fix: add `defer` for the main logic (parallel download, execution after the DOM is built, order preserved), `async` for independent third-party scripts, or move the tag to the end of `<body>`. ```html <script src="main.js" defer></script> <script src="analytics.js" async></script> ``` **Key point:** the problem is not `<head>` itself but synchronous execution: a script without `defer` or `async` halts HTML parsing and delays the first render.Shown above the full answer for quick recall.Answer (EN)Image**A script in `<head>` without a `defer` or `async` attribute stops HTML parsing: the browser has to download and execute the JS before it can carry on building the DOM.** Because of that the user sees an empty page until the file arrives over the network, and every speed metric gets worse. ## Theory ### TL;DR - A synchronous `<script src>` blocks the HTML parser until the file has downloaded and run. - The browser does this because the script may change the document, for example through `document.write`. - In `<head>` this hurts most: the DOM is still empty, so the screen is white. - FCP, LCP, TTI and TBT all suffer, especially on a mobile network. - The cures: `defer` for the main code, `async` for independent scripts, or the tag at the end of `<body>`. - A tiny inline script in `<head>` is acceptable, as long as it really is tiny. ### Quick example ```html <head> <script src="main.js"></script> </head> <body> <h1>Hello!</h1> </body> ``` Step by step: 1. The browser starts reading the HTML. 2. It reaches `<script src="main.js">`. 3. It stops parsing the HTML. 4. It waits for `main.js` to arrive over the network, which can take hundreds of milliseconds. 5. It executes the script. 6. And only then does it carry on parsing the rest of the page. For that whole time the DOM has not been created, the user sees no content, and the FCP and LCP metrics suffer. ### How the browser loads a page When the browser receives an HTML document, it goes through these steps: 1. It parses the HTML line by line, top to bottom. 2. When it meets a `<link>` tag with CSS, it loads the stylesheet, because styles affect rendering. 3. When it meets a `<script>` tag, it stops parsing in order to execute the JS. > Why? > Because the script can change the DOM itself, or even remove elements that come later in the HTML (`document.write`, `innerHTML` and so on). > So the browser is obliged to wait for the JS to finish before it continues parsing. Schematically, the synchronous case looks like this: ```text HTML: <head> -> <script> -> <body> | download [======= JS is downloading =======] | execution [======= JS is executing =======] | only then is the DOM built ``` With `defer` the download happens in parallel and the execution is pushed to the end: ```text HTML and JS download together -> DOM is built -> JS executes ``` ### The problems this causes | Problem | What happens | | --- | --- | | The HTML parser is blocked | The page is not built until the JS has downloaded | | A long "white page" | The user sees emptiness | | Slow networks, worse UX | Especially on 3G and 4G | | Worse Core Web Vitals | LCP, FCP, TTI, TBT all go up | | Scripts get in the way of critical CSS | Critical styles are applied too late | ### How to avoid the blocking **Option 1. `defer`** ```html <script src="main.js" defer></script> ``` The script downloads in parallel with the HTML but executes only after the DOM is fully parsed, just before the `DOMContentLoaded` event. No blocking, a guaranteed execution order, and the ideal choice for most cases. **Option 2. `async`** ```html <script src="analytics.js" async></script> ``` The script downloads asynchronously in the background and executes as soon as it has downloaded, without waiting for anything else. It is used for independent scripts: analytics, advertising, tracking pixels. The execution order is not guaranteed. **Option 3. Move the scripts to the end of `<body>`** ```html <body> ... <script src="main.js"></script> </body> ``` By the time the JS loads, the HTML is already fully parsed, the user can see the content, and the JS runs as the final step. This is the old but still working approach; in modern HTML `<script defer>` has replaced it. ### The effect on metrics | Metric | What degrades with scripts in `<head>` | | --- | --- | | **FCP (First Contentful Paint)** | The first content takes longer to appear | | **LCP (Largest Contentful Paint)** | The main element renders later | | **TTI (Time to Interactive)** | JS blocks the main thread | | **TBT (Total Blocking Time)** | Grows because of heavy synchronous scripts | ### What to do in production | Script type | Where and how to include it | | --- | --- | | Main logic (UI, SPA) | `<script src="main.js" defer>` | | Analytics, advertising | `<script src="metrics.js" async>` | | Inline initialisation | `<script>` inside `<head>`, if the initialisation really is small | | Legacy libraries | Better moved to the end of `<body>` | Summary table: | Scenario | What it does | Consequence | | --- | --- | --- | | `<script>` in `<head>` with no attributes | Blocks parsing | Slow | | `<script defer>` | Downloads in parallel, executes after the DOM | Optimal | | `<script async>` | Downloads and executes independently | Only for independent scripts | | `<script>` at the bottom of `<body>` | Waits for the end of the HTML | No blocking | ### Common mistakes - **Thinking the problem is `<head>` itself.** What blocks is synchronous execution, not the position: `<script defer>` in `<head>` is the best option there is. - **Putting `async` on code that depends on the DOM or on another file.** It can run before the elements exist and break at random. - **Hiding the problem behind `window.onload`.** The script still downloads synchronously and delays parsing, the logic inside simply waits longer. - **Forgetting about render-blocking CSS.** A heavy `<link rel="stylesheet">` also delays rendering, so critical styles are worth inlining. - **Putting a huge inline script in `<head>`.** The network is irrelevant here, but parsing and execution still occupy the main thread. - **Including third-party widgets without `async`.** A slow foreign server then directly delays the rendering of your own page.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.