Adding JavaScript to a Page
Before JavaScript can do anything on a page, the browser has to load it. You can write JavaScript directly inside an HTML file or keep it in a separate .js file, and where you put it decides whether your code can see the page at all.
In this lesson you will learn the <script> element, external script files, and the defer, async and type="module" attributes that control when a script runs.
The script element
JavaScript lives inside a <script> element. The browser reads the HTML from top to bottom, and when it reaches a <script>, it runs the code inside it.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Script at the End</title>
</head>
<body>
<h1>Jaipur Sweet House</h1>
<p id="status">Checking...</p>
<script>
const statusText = document.querySelector("#status");
statusText.textContent = "Open today from 9 am to 9 pm";
</script>
</body>
</html>The script sits at the end of <body>, after the paragraph. By the time the browser runs it, the paragraph already exists, so the code can find it with document.querySelector() and change its text.
You do not need a type attribute for normal JavaScript. Older pages often have type="text/javascript", but browsers treat every script as JavaScript by default.
Why the position matters
Put the same script in the <head>, and it breaks:
<head>
<meta charset="utf-8">
<title>Broken</title>
<script>
// runs before the body exists, so this finds nothing (null)
document.querySelector("#status").textContent = "Open today";
</script>
</head>When the browser reaches this script, it has not read the <body> yet. document.querySelector("#status") returns null, a value that means "nothing", and setting text on nothing throws an error such as "Cannot set properties of null". This is one of the most common beginner bugs. The fix is to make the script run after the page is parsed, which the next sections show.
External script files
Writing JavaScript inside HTML is fine for small demos. Real projects keep JavaScript in separate files with the .js extension and link them with the src attribute.
// file: js/app.js
const today = new Date().getFullYear();
document.querySelector("#year").textContent = today;<!-- file: index.html -->
<footer>
<p>© <span id="year"></span> Kabir Electronics, Rohtak</p>
</footer>
<script src="js/app.js" defer></script>External files have clear benefits:
- Cleaner code. HTML stays about structure and JavaScript stays about behaviour.
- Reuse. Ten pages can share one
app.jsfile. - Speed. The browser caches (saves) the file after the first visit, so other pages load faster.
The src path works like an image path: it is relative to the HTML file unless it starts with / or https://. A <script> with src must stay empty, because the browser ignores any code you write between the tags.
defer, async and modules
A plain <script src> in the <head> stops the browser. It downloads the file, runs it, and only then continues reading the HTML. Three options fix this.
| Attribute | Downloads | Runs | Order kept? |
|---|---|---|---|
| none | Pauses the page while downloading | Immediately | Yes |
defer | In the background | After the HTML is fully read | Yes |
async | In the background | As soon as it arrives | No |
type="module" | In the background | After the HTML is fully read | Yes |
- Use
deferfor normal scripts that work with the page. You can then put the script in the<head>, and it still sees every element. - Use
asynconly for independent scripts, such as a visitor counter, that do not depend on the page or on other scripts. - Use
type="module"for modern code that is split into files withimportandexport. Module scripts are deferred automatically, even when they are written inline.
defer and async only work on scripts with a src attribute. A module script is different: even an inline one waits for the page, as this example shows.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Module in the Head</title>
<script type="module">
// module scripts wait until the HTML is read
const statusLine = document.querySelector("#trains");
statusLine.textContent = "12015 Ajmer Shatabdi: on time";
</script>
</head>
<body>
<h1>Train Status</h1>
<p id="trains">Loading...</p>
</body>
</html>You will learn how modules split code into files in Modules.
Which should you use?
For your own pages, follow this simple rule:
- Small demo or single page: a
<script>at the end of<body>. - Real site: external files in the
<head>withdefer, or withtype="module"once you useimport.
Both rules guarantee that the HTML exists before your code runs.
Common mistakes
- Script in the head without
defer. The code runs before the elements exist andquerySelector()returnsnull. Adddefer, or move the script to the end of<body>. - Code inside a script that has
src.<script src="app.js">console.log("hi")</script>ignores the inline code. Use two separate<script>elements. - Wrong file path. If
src="app.js"but the file is in ajsfolder, nothing runs. Open DevTools (pressF12) and check the Console and Network tabs for a 404 error. - Expecting
asyncscripts to run in order. Twoasyncscripts can run in either order. Usedeferwhen one script depends on another.
Key points
- JavaScript goes inside a
<script>element, either inline or linked withsrc. - The browser runs a classic script as soon as it reaches it, so position matters.
- Put scripts at the end of
<body>, or in the<head>withdefer. asyncscripts run as soon as they download, in any order.type="module"scripts are deferred automatically and can useimport.- External
.jsfiles keep code clean, reusable and cached.
Exercise
Create a page with a heading and an empty paragraph that has id="msg". Add a script in the <head> that writes "Welcome to Lucknow Library" into the paragraph. Make sure it works even though the script is in the head.
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Library</title>
<script type="module">
document.querySelector("#msg").textContent = "Welcome to Lucknow Library";
</script>
</head>
<body>
<h1>City Library</h1>
<p id="msg"></p>
</body>
</html>A module script waits until the HTML is read, so the paragraph exists when the code runs. With an external file, <script src="app.js" defer> would work the same way.
Quick Quiz
1. Why does document.querySelector("#box") return null in a plain script in the <head>?
A classic script runs as soon as the browser reaches it, before the rest of the page has been read.
2. What does defer do on <script src="app.js" defer>?
Deferred scripts wait for the page and keep their order, which makes them safe for page code.
3. Which script waits for the page automatically, even when written inline?
Module scripts are deferred by default, whether they are inline or loaded from a file.
Learn JavaScript with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
