Question

Where to Put Script Tags in HTML: defer, async, and Body Placement

Question

When adding JavaScript to an HTML document, where should the <script> tags and JavaScript code be placed?

I understand that placing a normal script in the <head> can delay page parsing, while placing it at the start of the <body> can also delay the visible page content. Does that make the end of the <body> the correct location for scripts?

I am using jQuery, but I would also like to understand the general HTML and JavaScript guidance, including where event-handling code should go instead of inline calls in <a> elements.

Short Answer

You will learn how ordinary, defer, async, and module scripts affect HTML parsing; when to put scripts in the document head or at the end of the body; and how to safely run code that needs page elements or jQuery.

Concept

A browser generally processes an HTML document from top to bottom. When it encounters a classic external script such as:

<script src="app.js"></script>

it normally pauses HTML parsing, downloads the file if necessary, and executes it before continuing. This behavior matters because it can delay the browser from reaching and displaying the rest of the page.

The best placement depends on the script's loading behavior and dependencies:

  • Modern default: place external scripts in <head> with defer. The browser can download the script while it continues parsing HTML. The script runs after parsing is complete, in document order.
  • Alternative: put classic scripts just before </body>. By that point, most page elements have already been parsed, so the script can usually access them. This is still valid and is common in older codebases.
  • Use async only for independent scripts. An async script runs as soon as it finishes downloading, so its execution order is not predictable.
  • Use type="module" for JavaScript modules. Module scripts are deferred by default, making head placement appropriate in most cases.

Script placement is not only about rendering speed. It also determines whether the elements your code needs already exist when the code runs. If code queries #save-button before the browser has parsed that element, the query may return null.

For jQuery, load jQuery before scripts that call $ or jQuery. Then attach event listeners in JavaScript rather than putting JavaScript directly in HTML attributes such as onclick.

Mental Model

Think of HTML parsing as a reader moving through a recipe from top to bottom.

A classic <script> is like an instruction that says: stop reading, fetch this extra page if needed, perform every instruction on it, then continue. If that instruction appears near the start, the reader cannot reach the rest of the recipe yet.

A deferred script is like a note that says: fetch this page in the background, but read and perform it after you finish the main recipe. The reader keeps moving through the HTML, and the script runs when the document is ready.

An async script is like a delivery that must be handled immediately whenever it arrives. It is useful for independent tasks, but it is not dependable when one script must run before another.

Take Quiz

Syntax and Examples

A modern, reliable pattern is to put external JavaScript in the document <head> and use defer:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Profile page</title>

    <script src="app.js" defer></script>
  </head>
  <body>
    <h1>Profile</h1>
    <button id="save-button">Save</button>
  </body>
</html>
// app.js
const saveButton = document.querySelector("#save-button");

saveButton.addEventListener("click", () => {
  console.log("Saved");
});

Because defer waits until HTML parsing is complete before executing app.js, the button exists when querySelector runs.

Multiple deferred scripts

Deferred scripts execute in the same order in which they appear in the HTML:

<head>
  <script src="library.js" defer></script>
  <script src="app.js" defer></script>
</head>

Here, library.js executes before app.js, even if app.js finishes downloading first. This is useful when app.js depends on code from a library.

End-of-body alternative

Without defer, put a classic script near the end of the body:

<body>
  <button id="save-button">Save</button>

  <script src="app.js"></script>
</body>

The button is parsed before app.js executes. However, the script download and execution can still delay the browser from finishing the document.

Async for independent scripts

<script src="analytics.js" async></script>

Use this only when analytics.js does not rely on the DOM being ready and does not need a predictable order relative to other scripts.

Module scripts

<script type="module" src="main.js"></script>

External module scripts are deferred by default. They can normally be placed in the head without adding defer.

Step by Step Execution

Consider this document:

<head>
  <script src="jquery.js" defer></script>
  <script src="app.js" defer></script>
</head>
<body>
  <a id="help-link" href="/help">Help</a>
</body>
// app.js
$("#help-link").on("click", (event) => {
  event.preventDefault();
  console.log("Opening help");
});

Execution proceeds as follows:

  1. The browser starts parsing the <head>.
  2. It sees jquery.js with defer and begins downloading it. It does not stop parsing the HTML.
  3. It sees app.js with defer and begins downloading it too.
  4. The browser parses the body and creates the #help-link element.
  5. HTML parsing completes.
  6. The browser executes jquery.js first because it appeared first among deferred scripts.
  7. The browser executes app.js. At this point, both jQuery and #help-link are available.
  8. Later, when the user clicks the link, the event handler prevents normal navigation and logs a message.

If app.js had been a normal script at the top of the document, it could run before the link exists. If it ran before jQuery loaded, $ would cause an error.

Real World Use Cases

  • Application UI code: Load a page's JavaScript with defer so buttons, forms, and navigation elements can be selected after parsing.
  • Libraries and dependent code: Load a library first and an application file second using two deferred scripts, preserving dependency order.
  • Analytics or advertising: Load an independent third-party tracking script with async when its execution order does not affect core functionality.
  • JavaScript applications: Use <script type="module" src="main.js"></script> to load an application's entry module from the head.
  • Legacy server-rendered pages: Place classic scripts before </body> when changing the head markup or build setup is difficult.
  • Inline page data: Put small JSON configuration data in a non-executing script element, then read it from application code when needed.
<script id="page-data" type="application/json">
  {"userId": 42, "theme": "dark"}
</script>
Take Quiz

Real Codebase Usage

In production projects, developers usually separate JavaScript into external files and use a consistent loading strategy.

Preferred baseline

A common HTML entry point is:

<head>
  <script src="/assets/app.js" defer></script>
</head>

This avoids blocking HTML parsing while keeping script declarations in one predictable location.

Event listeners instead of inline attributes

Avoid mixing behavior into markup:

<!-- Avoid -->
<a href="/help" onclick="openHelp(); return false;">Help</a>

Use semantic HTML plus JavaScript:

<a id="help-link" href="/help">Help</a>
const helpLink = document.querySelector("#help-link");

helpLink.addEventListener("click", (event) => {
  event.preventDefault();
  openHelp();
});

This separation makes templates easier to maintain, keeps JavaScript out of HTML strings, and allows the link to work normally if JavaScript is unavailable.

jQuery dependency pattern

When using jQuery from a CDN, the dependent file must appear after it:

<head>
  <script src="https://code.jquery.com/jquery-3.7.1.min.js" defer></script>
  <script src="/assets/app.js" defer></script>
</head>

When code is inserted dynamically

Real applications sometimes add elements after the initial page load. For jQuery, event delegation can handle clicks on matching elements added later:

$(document).on("click", ".remove-item", function () {
  $(this).closest("li").remove();
});

This is a different problem from initial script placement: the document may be ready, but the specific element may not exist yet.

Common Mistakes

Using a blocking script at the top of the page

<head>
  <script src="app.js"></script>
</head>

A classic script without defer or async can pause parsing while it downloads and runs. Prefer defer for page code:

<script src="app.js" defer></script>

Adding async to scripts with dependencies

<script src="jquery.js" async></script>
<script src="app.js" async></script>

This is broken when app.js uses jQuery. Either file may execute first. Use defer for both, in dependency order.

Assuming defer applies to inline scripts

<script defer>
  console.log(document.querySelector("#save-button"));
</script>

The defer attribute only has useful deferred behavior for external classic scripts with src. Do not rely on it for an inline script. Move the code into a deferred external file, place it after the needed markup, or wait for DOMContentLoaded.

Running DOM queries before elements exist

<head>
  <script>
    document.querySelector("#save-button").addEventListener("click", save);
  </script>
</head>
<body>
  <button id="save-button">Save</button>
</body>

At the time the script runs, #save-button has not been parsed, so querySelector returns null. Use an external deferred script or move the script after the button.

Treating links as buttons

If an action does not navigate anywhere, use a button:

<button type="button" id="open-dialog">Open settings</button>

Use <a> when the primary behavior is navigation to a URL. This improves accessibility and avoids unnecessary preventDefault() calls.

Forgetting that third-party scripts can affect performance

Even an async script consumes network, CPU, and potentially privacy budget. Load only necessary third-party code, and test its impact on the page.

Comparisons

ApproachHTML parsingExecution orderBest use
Classic <script src="..."> in <head>Blocks parsing while loading/executingDocument orderRarely ideal for page scripts
Classic script before </body>Parsing has reached page content first; script still blocks when encounteredDocument orderSimple legacy pattern
<script defer src="..."> in <head>Does not block parsingPreserved among deferred scriptsBest general choice for dependent page scripts
<script async src="...">Does not block parsingNot guaranteedIndependent analytics or widgets
<script type="module" src="...">Deferred by defaultModule dependency graph controls loadingModern modular JavaScript

defer versus DOMContentLoaded

ToolMain purpose
deferSchedule an external script to execute after document parsing.
DOMContentLoaded eventRun a callback once the initial HTML document has been parsed.

When your main external script uses defer, most setup code can run directly because the DOM is already parsed. A DOMContentLoaded listener is useful when code may execute in different loading conditions or is intentionally loaded earlier.

defer versus async

  • Choose defer when script order matters or the script needs page elements.
  • Choose async only when the script is independent and can execute at an unpredictable time.
Take Quiz

Cheat Sheet

<!-- Recommended for ordinary page JavaScript -->
<head>
  <script src="app.js" defer></script>
</head>
<!-- Dependencies: preserve order with defer -->
<head>
  <script src="library.js" defer></script>
  <script src="app.js" defer></script>
</head>
<!-- Modern module entry point; deferred by default -->
<head>
  <script type="module" src="main.js"></script>
</head>
<!-- Independent script only -->
<script src="analytics.js" async></script>
<!-- Valid legacy alternative -->
<body>
  <!-- page markup -->
  <script src="app.js"></script>
</body>
  • Classic scripts without attributes block HTML parsing when encountered.
  • defer works for external classic scripts and preserves order.
  • async does not preserve order.
  • Module scripts are deferred by default.
  • Load a library before code that depends on it.
  • Attach listeners with addEventListener or jQuery .on() instead of inline onclick attributes.
  • Use <button> for actions and <a href="..."> for navigation.

FAQ

Should script tags go in the head or body?

For most external application scripts, place them in the <head> with defer. Placing a classic script just before </body> is also valid, especially in older pages.

Is it bad to put a script in the head?

A normal classic script in the head can block HTML parsing. A deferred external script in the head is usually a good choice because it downloads without blocking parsing and runs after parsing.

Should I use defer or async?

Use defer for page code, ordered libraries, and scripts that need the DOM. Use async only for independent code whose execution order does not matter.

Does defer guarantee that the DOM exists?

For a normal document, deferred scripts run after HTML parsing is complete, so elements in that parsed document are available. Dynamically added elements may still require delegation or later initialization.

Do module scripts need the defer attribute?

Usually no. <script type="module" src="main.js"> is deferred by default. Adding defer is unnecessary.

How do I load jQuery and my jQuery code safely?

Load jQuery first, then your application script. Add defer to both scripts so the DOM is parsed first and their order is preserved.

Should JavaScript go inside onclick attributes?

Prefer attaching event listeners from JavaScript. It separates structure from behavior, is easier to test and maintain, and avoids embedding JavaScript in HTML.

Can I put an inline script at the end of the body?

Yes. It runs immediately at that point, after earlier body elements have been parsed. For reusable application code, an external deferred file is often cleaner.

Take Quiz

Mini Project

Description

Build a small page that displays a task list and lets a user mark tasks as complete. The project demonstrates a deferred script in the head, DOM selection after parsing, and event listeners instead of inline JavaScript attributes.

Goal

Create a task list where clicking a task toggles its completed appearance and updates a status message.

Requirements

Include an external JavaScript file loaded from the document head with defer. Create at least three task buttons in the HTML. Toggle a done CSS class when a task is clicked. Show the number of completed tasks in a status message. Do not use inline onclick attributes.

Take Quiz

Keep learning

Related questions

View more