JavaScript Where To

4 minute read

JavaScript lives inside your HTML — but where you put it and how you load it changes when it runs and how fast your page feels. Get this wrong and your page is blank for seconds longer than it needs to be. Get it right and the page loads instantly while scripts hum along in the background.

The <script> Tag

Every line of JavaScript on a web page is loaded via the <script> tag. There are exactly two flavours:

<!-- 1. Inline — code lives directly inside the tag -->
<script>
  console.log("Hello from inline JS");
</script>

<!-- 2. External — code lives in a separate .js file -->
<script src="app.js"></script>

The external form is what real projects use. It lets you cache the file, share it across pages, and keep your HTML readable.

Where Can You Put the <script> Tag?

Anywhere inside <head> or <body>. The placement controls when the browser pauses parsing your HTML to download and execute the script.

<!DOCTYPE html>
<html>
<head>
  <!-- Option A: in <head> -->
  <script src="app.js"></script>
</head>
<body>
  <h1>My Page</h1>

  <!-- Option B: at the end of <body> -->
  <script src="app.js"></script>
</body>
</html>

Why Placement Matters

The browser parses HTML top to bottom. When it hits a classic <script> tag, it stops, downloads the file, executes it, and then keeps parsing. That blocking behaviour is what causes the dreaded blank-page-for-2-seconds effect.

HTML parsing  ─────┐       ┌──────────── continues
                   │       │
                   ▼       │
                 <script>  │
                 download  │  ← parsing PAUSED here
                 execute   │
                   │       │
                   └───────┘
Placement Blocks Rendering? Sees the DOM? Use When…
<head> (plain) ❌ Yes — bad ❌ No, DOM not built yet Almost never
Before </body> ✅ No — DOM is ready ✅ Yes Old-school, still works
<head> + defer ✅ No — runs after DOM ready ✅ Yes Best default
<head> + async Partly — runs ASAP ⚠️ Maybe Independent scripts (analytics)
<script type="module"> ✅ Deferred by default ✅ Yes Modern ESM apps

Inline vs External Scripts

<!-- ❌ Avoid: inline -->
<script>
  document.querySelector("h1").style.color = "red";
</script>

<!-- ✅ Prefer: external -->
<script src="/assets/js/app.js" defer></script>
  Inline External
Cached by browser ❌ No — re-downloads on every page ✅ Yes — one cache, many pages
Easy to reuse across pages ❌ Copy-paste ✅ Just reference the same file
Friendly to source maps / linters ❌ Limited ✅ Full tooling
Bypasses strict CSP ❌ No (needs 'unsafe-inline') ✅ Yes
Quick one-liner ✅ Yes Overkill

Tip: Use inline only for tiny bootstraps (e.g. a global window.APP_CONFIG = {...} injected by the server). Everything else goes in an external file.

The defer Attribute

defer tells the browser: “download this script in parallel with HTML parsing, but don’t execute it until the HTML is fully parsed.”

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

This gives you the best of both worlds:

  • Download starts immediately (no waiting)
  • Execution waits for the DOM
  • Multiple deferred scripts execute in document order
HTML parsing  ────────────────────────────────────── done ─▶ run deferred scripts in order
                       ▲
                       │ scripts download in parallel
                  app.js, vendor.js

The async Attribute

async also downloads in parallel — but executes the script as soon as it’s ready, even if HTML isn’t done parsing. Execution order between async scripts is not guaranteed.

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

Use async for scripts that are independent: analytics, ads, error reporters. Don’t use async if a script depends on another script or on the DOM.

defer vs async Side-by-Side

Plain      ────HTML────[stop]download─exec[resume]────HTML────  ← bad
defer      ────HTML───────────────────────────────────HTML────▶ exec (DOM ready)
                   ▲download
async      ────HTML────────────────[exec]────HTML─────HTML────  ← anywhere
                   ▲download
  defer async
Downloads in parallel ✅ ✅
Blocks HTML parsing ❌ ❌
Execution order preserved ✅ Yes ❌ Random
Runs after DOM ready ✅ Yes ❌ As soon as downloaded
Best for App code Analytics, ads, telemetry

Modules — type="module"

ES modules (covered in chapter 46) are deferred by default. They also enable import/export:

<script type="module" src="main.js"></script>
// main.js
import { greet } from "./utils.js";
greet("World");

You don’t need defer on modules — it’s automatic.

A Realistic Modern Template

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My App</title>

  <!-- Analytics: independent, fire-and-forget -->
  <script src="https://analytics.example.com/tag.js" async></script>

  <!-- App code: deferred, runs after DOM is ready -->
  <script src="/assets/js/app.js" defer></script>
</head>
<body>
  <h1>Welcome</h1>
  <!-- No <script> tags at the bottom needed -->
</body>
</html>

Common Mistakes

<!-- ❌ Blocking the page for a script that needs the DOM -->
<head>
  <script src="app.js"></script>   <!-- runs before <body> exists -->
</head>

<!-- ✅ Fix: add defer -->
<head>
  <script src="app.js" defer></script>
</head>

<!-- ❌ Using async for dependent scripts -->
<script src="jquery.js" async></script>
<script src="my-plugin.js" async></script>  <!-- might run before jQuery! -->

<!-- ✅ Fix: use defer to preserve order -->
<script src="jquery.js" defer></script>
<script src="my-plugin.js" defer></script>

Warning: Never mix defer and async on the same tag. If both are present, async wins — defer is silently ignored.

Quick Decision Guide

Need it to run? ───── Is it independent (analytics, ads)?
                              │
                  ┌──── Yes ──┴── No ────┐
                  ▼                       ▼
              async                  Does it use the DOM?
                                          │
                              ┌──── Yes ──┴── No ────┐
                              ▼                       ▼
                            defer                 defer
                          (always safe)         (still safe)

Rule of thumb: Default to defer in <head> for all your app code. Reach for async only when a script truly doesn’t care about your code or the DOM.

What’s Next?

Now that your scripts are loading correctly, let’s actually print something. We’ll look at the four ways JavaScript can output values — to the page, to the console, to alert dialogs, and into HTML directly.

← Previous: JS Introduction Next: JS Output →

Updated: