HTMLAdding Scripts (<script>)

Linking Scripts with the Script Element

The <script> element embeds or references JavaScript in an HTML document. How and where you use it has a direct effect on page load performance, because by default, scripts block HTML parsing.

External vs inline scripts

HTML
<!-- External: src points to a separate .js file -->
<script src="/js/app.js"></script>

<!-- Inline: JavaScript written directly between the tags -->
<script>
  console.log('Hello from an inline script');
</script>
  • src (external) lets browsers cache and reuse the file across pages, and keeps HTML and JS separated.

  • Inline scripts avoid an extra network request, which suits small, page-specific snippets.

  • You cannot use both src and inline content in the same tag — src wins and the inline content is ignored.

Execution timing: default, async, and defer

By default, when the parser hits a script tag, it stops parsing HTML, fetches the script (if external), runs it immediately, and only then resumes parsing. The async and defer attributes change this.

HTML
<script src="/js/blocking.js"></script>
<script async src="/js/analytics.js"></script>
<script defer src="/js/app.js"></script>

Think of it as a timeline. With no attribute, HTML parsing pauses, the script downloads, then runs, then parsing resumes — a full stop. With async, the script downloads in parallel with parsing but runs the instant it finishes, interrupting parsing wherever it happens to be. With defer, the script also downloads in parallel, but execution is held until parsing is completely finished, and deferred scripts run in their original document order.

Attribute

Download

Execution

Blocks parsing?

(none)

Blocking

Immediately, in place

Yes

async

Parallel

As soon as ready (any order)

Briefly, on completion

defer

Parallel

After parsing completes, in order

No

  • Use defer for scripts that need the full DOM and must run in a predictable order (most application code).

  • Use async for independent scripts with no dependencies on the DOM or on each other, like analytics or ads.

  • Avoid the no-attribute default for anything non-trivial — it is the slowest option for the page.

type="module"

HTML
<script type="module" src="/js/app.mjs"></script>
  • Enables native ES module import/export syntax in the script.

  • Module scripts are deferred by default — they behave like defer even without the attribute.

  • Executed in strict mode automatically, and each module has its own top-level scope.

Placement: head vs end of body

Before async/defer existed, the standard advice was to place scripts at the very end of body so they would not block rendering. That advice still works, but async/defer now give you finer control from the head.

Placement

Tradeoff

End of body (no attribute)

Simple and safe, but the browser cannot start downloading the script until it has parsed the entire page

head with defer

Download starts immediately (better), execution still waits for parsing to finish (safe)

head with async

Download starts immediately, executes as soon as ready — best for independent, order-agnostic scripts

Tip
Placing defer scripts in <head> is generally the best default for application code today: downloads start as early as possible, without risking a script running before the DOM it depends on exists.
Warning
async scripts can execute in any order relative to each other and relative to your other code. Never rely on execution order between multiple async scripts.
Note
defer and async have no effect on inline scripts without a src attribute — they only change behavior for externally loaded scripts.