JavaScript in Head vs Footer: How It Affects the Core Web Vitals

Why defer in the head is the modern best practice, and when footer placement still makes sense

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-03-03

The short answer: defer in the head

Last reviewed by Arjen Karel on March 2026

The classic advice was to put JavaScript in the footer. That advice is outdated. With async and defer supported in every browser, the best place for most scripts is in the <head> with a defer attribute.

The reason comes down to the preload scanner: your browser discovers head scripts immediately and starts downloading them in parallel with HTML parsing. Footer scripts are discovered later, which means a later download start. The result is the same non-blocking behavior, but with faster resource discovery.

According to the 2025 Web Almanac, 85% of pages still fail the render-blocking resources audit. That is an enormous number. Meanwhile, mobile Total Blocking Time increased 58% year over year to a median of 1,916 ms. JavaScript is getting heavier, not lighter. Getting your script placement right is one of the easiest things you can do to improve your First Contentful Paint and Largest Contentful Paint.

How the preload scanner works

The preload scanner is a second HTML parser that runs ahead of the main parser. It quickly scans the raw HTML and starts fetching critical resources (images, CSS, JavaScript) before the main parser reaches them. The preload scanner fetches resources roughly in the order it discovers them.

This is where script placement matters. A script in the <head> is discovered almost immediately. A script at the bottom of the <body> is discovered later, especially on large HTML documents. That delay can cost you hundreds of milliseconds on a mobile connection.

Dynamically injected scripts (created via JavaScript) are invisible to the preload scanner entirely. The scanner only discovers resources that exist in the HTML markup returned by the server. This is why deferring JavaScript via HTML attributes is almost always better than injecting scripts with code.

JavaScript in the head

Placing JavaScript in the <head> of the page gives it the earliest possible discovery by the preload scanner.

Advantages

  1. Early discovery: The preload scanner finds head scripts before any body content. Download starts as early as possible.
  2. Earlier execution: Scripts in the head (with defer) execute before scripts discovered later in the document. For a detailed comparison, see defer vs async JavaScript and the Core Web Vitals.
  3. Code separation: Keeping script references in the <head> separates them from the content markup, making the HTML easier to maintain.

Disadvantages

  1. Render blocking (without defer or async): A plain <script> in the head blocks HTML parsing until the script is downloaded and executed. This kills your First Contentful Paint. Always use defer or async on external scripts in the head to avoid this.
  2. Bandwidth competition: High-priority head scripts compete for bandwidth with your CSS, fonts, and LCP image. On slower connections, this can push your Largest Contentful Paint past the 2.5 second threshold.

When to use head placement

Use the <head> for scripts that are critical to the page experience: your menu, cookie notice, slider, or any script that affects what the visitor sees above the fold. Add defer (or async if execution order does not matter) to prevent render blocking. Feature detection libraries also belong in the head, since they need to run before the body is parsed.

JavaScript in the footer

Placing JavaScript just before the closing </body> tag was the standard performance advice for years. The idea: let the HTML render first, download scripts after.

Advantages

  1. Non-blocking by default: Footer scripts do not block the initial rendering because the HTML above them has already been parsed.
  2. Lower bandwidth competition: By the time the browser reaches footer scripts, your CSS, fonts, and LCP image have already started downloading.

Disadvantages

  1. Late discovery: The preload scanner finds footer scripts later than head scripts. On a large page, this means a later download start and later execution.
  2. Obsolete pattern: <script defer> in the <head> achieves the same non-blocking behavior with earlier discovery. Footer placement was the workaround before defer had universal browser support. That era is over.

When footer placement still makes sense

Footer placement can make sense for scripts you truly do not want competing for bandwidth during the initial page load: analytics, A/B testing tools, or social widgets. But even for these, defer in the head is usually the better choice because of earlier preload scanner discovery.

Modern script attributes

Beyond async and defer, there are two more attributes worth knowing:

type="module": Module scripts are deferred by default. You do not need to add defer to a module script because it already behaves that way. Adding async to a module script overrides the default defer behavior and makes it execute as soon as it finishes downloading.

fetchpriority: By default, async and defer scripts get Low network priority. Adding fetchpriority="high" to an async script gives you non-blocking download at High priority. This is the ideal combination for scripts that are critical to the user experience but should not block rendering. For the full picture, see the guide to resource prioritization and JavaScript priority levels.

A practical script placement strategy

Not all scripts deserve the same treatment. I use a four-tier approach:

  1. Render-critical scripts: Scripts that affect the visible layout of the page (menu, slider, above-the-fold UI). Place in the <head> without defer if they must execute before first paint. Keep these as small as possible because they block rendering and will hurt your Core Web Vitals.
  2. Important scripts: Scripts critical to conversion or interaction (forms, navigation, cookie consent). Place in the <head> with defer or async.
  3. Normal scripts: Scripts that do not affect the first page render (carousels, modals, tabs). Place in the <head> with defer.
  4. Nice-to-have scripts: Scripts you could do without if you absolutely had to (analytics, chat widgets, social sharing). Load these after the page has finished rendering, using the load event or requestIdleCallback. See 16 methods to defer JavaScript for techniques. If you have a lot of unused JavaScript in this category, see how to reduce unused JavaScript.

The DOMContentLoaded and load events let you control execution timing regardless of where the script is placed in the HTML. This is useful for ensuring your code runs at the right moment.

Code examples

Example 1: JavaScript in the head (render-blocking)

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript in the Head Example</title>
    <script>
        function showMessage() {
            alert("Hello from JavaScript in the head!");
        }
    </script>
    <!-- This script blocks rendering -->
    <script src="script.js"></script>
</head>
<body>
    <button onclick="showMessage()">Click Me</button>
</body>
</html>

In this example, script.js in the <head> blocks rendering. The browser will not display the button until the script has been downloaded and executed. Add defer to the script tag to fix this.

Example 2: JavaScript in the footer

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript in the Footer Example</title>
</head>
<body>
    <button onclick="showMessage()">Click Me</button>
    <!-- Script at the end of the body -->
    <script src="script.js"></script>
    <script>
        function showMessage() {
            alert("Hello from JavaScript in the footer!");
        }
    </script>
</body>
</html>

Here, script.js loads after the HTML content, so it does not block rendering. But the preload scanner discovers it later than it would in the head. Using <script defer src="script.js"></script> in the <head> gives you the same non-blocking behavior with earlier download discovery.

Example 3: Using event listeners

<!DOCTYPE html>
<html>
<head>
    <title>Event Listener Example</title>
    <script>
        window.addEventListener('load', function() {
            console.log("Page is fully loaded.");
        });
    </script>
</head>
<body>
    <!-- Page content -->
</body>
</html>

The load event listener ensures the code inside the callback only runs after the page has fully loaded, regardless of where the script is placed. This is useful for non-critical initialization that should wait until everything else is ready.

Verify your changes

After moving scripts from the footer to defer in the <head>, verify the impact with Real User Monitoring. Your FCP and LCP should both improve. Across sites monitored by CoreDash, origins using defer for the majority of their scripts have an 18% faster median FCP than origins still relying on footer placement.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Ask AI why your INP spiked.

CoreDash is the only RUM tool with MCP support. Connect it to your AI agent and query your Core Web Vitals data in natural language. No more clicking through dashboards.

See How It Works
JavaScript in Head vs Footer: How It Affects the Core Web VitalsCore Web Vitals JavaScript in Head vs Footer: How It Affects the Core Web Vitals