Question
Should CSS Come Before JavaScript in HTML? Rendering and Loading Explained
Question
In many places online, I have seen the recommendation that CSS files should be included before JavaScript files.
The usual explanation is that the browser's rendering thread needs the CSS first so it can render the page correctly. If JavaScript is loaded first, the browser must parse and execute that JavaScript before continuing to later resources, which supposedly delays rendering because the styles are not yet available.
However, my own testing seems to show something different.
I created a small Ruby server that adds configurable delays to CSS and JavaScript responses so I can test loading behavior:
require 'rubygems'
require 'eventmachine'
require 'evma_httpserver'
require 'date'
class Handler < EventMachine::Connection
include EventMachine::HttpServer
def process_http_request
resp = EventMachine::DelegatedHttpResponse.new(self)
return unless @http_query_string
path = @http_path_info
array = @http_query_string.split('&').map { |s| s.split('=') }.flatten
parsed = Hash[*array]
delay = parsed['delay'].to_i / 1000.0
jsdelay = parsed['jsdelay'].to_i
delay = 5 if delay > 5
jsdelay = 5000 if jsdelay > 5000
delay = 0 if delay < 0
jsdelay = 0 if jsdelay < 0
operation = proc do
sleep delay
if path.match(/.js$/)
resp.status = 200
resp.headers['Content-Type'] = 'text/javascript'
resp.content = "(function(){
var start = new Date();
while (new Date() - start < #{jsdelay}) {}
})();"
end
if path.match(/.css$/)
resp.status = 200
resp.headers['Content-Type'] = 'text/css'
resp.content = 'body { font-size: 50px; }'
end
end
callback = proc do |_res|
resp.send_response
end
EM.defer(operation, callback)
end
end
EventMachine.run do
EventMachine.start_server('0.0.0.0', 8081, Handler)
puts 'Listening...'
end
Then I used this HTML page for testing:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<script type="text/javascript">
var startTime = new Date();
</script>
<link
href="http://10.0.0.50:8081/test.css?delay=500"
type="text/css"
rel="stylesheet"
>
<script
type="text/javascript"
src="http://10.0.0.50:8081/test2.js?delay=400&jsdelay=1000"
></script>
</head>
<body>
<p>
Elapsed time is:
<script type="text/javascript">
document.write(new Date() - startTime);
</script>
</p>
</body>
</html>
When I load CSS first, the page renders in about 1.5 seconds.
When I load JavaScript first, the page renders in about 1.4 seconds.
I see similar results in Chrome, Firefox, and Internet Explorer. In Opera, the ordering does not seem to matter.
From this, it appears that JavaScript execution may wait for CSS to finish loading. If that is true, putting JavaScript first could sometimes be more efficient because the browser starts working on it earlier.
What am I missing here? Is the recommendation to place CSS before JavaScript not always correct?
I understand that other approaches exist, such as using async, moving scripts to the end of the page, deferring work with setTimeout, or using script loaders. My question is specifically about the order of essential CSS and essential JavaScript inside the <head>.
Short Answer
By the end of this page, you will understand why developers often place CSS before JavaScript in the <head>, what browser blocking behavior actually happens, and why timing tests can produce results that seem to contradict the common advice. You will also learn the practical rule: CSS usually goes first for correctness and perceived rendering, while JavaScript placement depends on whether the script is blocking, deferred, async, or dependent on computed styles.
Concept
Browsers do not load and render HTML in one simple straight line. They parse the document, discover external resources like stylesheets and scripts, and coordinate several kinds of work:
- HTML parsing
- CSS downloading and parsing
- JavaScript downloading and execution
- DOM construction
- CSSOM construction
- Layout and paint
A key beginner concept is this:
- A classic
<script src="...">in the document blocks HTML parsing while it is fetched and executed. - A stylesheet does not block HTML parsing in the same way, but it can block rendering and may also delay script execution in some cases.
Why can CSS affect JavaScript?
Because JavaScript can query style-related information, such as:
getComputedStyle(element)
element.offsetWidth
element.clientHeight
If the browser allowed a script to run before relevant CSS was ready, those values could be wrong. To avoid inconsistent behavior, browsers often make scripts wait for earlier stylesheets that could affect the page.
So the usual recommendation exists for two practical reasons:
- Render the page with correct styling as early as possible
- Avoid blocking interactions between stylesheets and synchronous scripts
Your experiment is useful because it reveals something real: script execution and stylesheet loading are not independent. In many browsers, a script that appears after a stylesheet may wait until that stylesheet is available.
That does not mean the recommendation is wrong. It means the recommendation is a simplification of a more accurate rule:
- Put critical CSS early so the browser can style the page as soon as possible.
- Avoid blocking scripts in the head unless they are truly necessary.
- If you must have both in the head, putting CSS first is usually the safer default for correct rendering and less visual instability.
The best answer is not really “CSS always before JavaScript” as an absolute law. It is:
- Blocking CSS should usually come before blocking scripts in the head.
- Non-blocking scripts (
defer, sometimesasync) change the situation. - Performance must be judged by more than one stopwatch number.
A page that paints slightly earlier but unstyled, or paints and then shifts visually, may feel worse than a page that waits a little longer and paints correctly.
Mental Model
Think of the browser as a stage crew preparing a theater scene.
- HTML is the script telling the crew what props and actors exist.
- CSS is the costume and stage design plan.
- JavaScript is the live director giving instructions during setup.
If the director starts giving instructions before the costume plan arrives, the crew may have to pause because those instructions might depend on what costumes or scenery are supposed to look like.
That is why browsers are careful. A script might ask, “How wide is this element?” But width depends on styles. So the browser may wait for CSS before letting the script continue.
Another important idea:
- Rendering a page is not just about finishing downloads fastest
- It is about getting to a correct, stable, visible page efficiently
So even if one resource order gives a slightly smaller raw timing number in a test, that does not automatically make it the better real-world choice.
Syntax and Examples
The resource order in the <head> commonly looks like this:
<head>
<link rel="stylesheet" href="styles.css">
<script src="app.js"></script>
</head>
This means:
- The browser discovers the stylesheet early.
- It starts downloading CSS.
- It later reaches the script.
- The script may wait for earlier CSS before executing.
Better modern pattern for many pages
<head>
<link rel="stylesheet" href="styles.css">
<script src="app.js" defer></script>
</head>
With defer:
- the script downloads in parallel
- HTML parsing continues
- the script runs after parsing is complete
- document order is preserved among deferred scripts
This is often a better choice than a blocking script in the head.
Example: blocking script depending on styles
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
<script>
console.log(document.body); // may be null here because body is not parsed yet
</script>
</head>
<body>
<div class="box">Hello</div>
</body>
</html>
This example shows a different issue: scripts in the head may run before later HTML exists.
Example: script reading layout
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
<script>
window.addEventListener('DOMContentLoaded', () => {
const box = document.querySelector('.box');
console.log(box.offsetWidth);
});
</script>
</head>
<body>
<div class="box">Hello</div>
</body>
</html>
.box {
width: 300px;
padding: 20px;
}
Here the script waits until the DOM is ready, and the browser can safely measure the element after styles are available.
Example: preferred simple setup
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
<script src="app.js" defer></script>
</head>
<body>
<h1>Dashboard</h1>
</body>
</html>
This gives the browser early CSS discovery and avoids a parser-blocking script.
Step by Step Execution
Consider this HTML:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
<script src="app.js"></script>
</head>
<body>
<h1>Hello</h1>
</body>
</html>
Assume:
styles.csstakes 500 ms to downloadapp.jstakes 400 ms to downloadapp.jsruns for 1000 ms
What typically happens
- The browser starts parsing HTML.
- It sees
<link rel="stylesheet">. - The browser starts downloading
styles.css. - Parsing continues until the browser sees
<script src="app.js">. - Because this is a classic script, HTML parsing pauses.
- The browser downloads
app.js. - Before executing
app.js, the browser may wait for earlier stylesheets that could affect script results. - The script executes.
- After script execution finishes, HTML parsing continues.
- Once DOM and CSSOM are ready enough, layout and paint happen.
Why your timing can show JavaScript-first as faster
Now reverse the order:
<head>
<script src="app.js"></script>
<link rel="stylesheet" href="styles.css">
</head>
Possible sequence:
- Browser sees script first.
- It pauses parsing immediately.
- It downloads and runs
app.js. - Only after that does parsing continue to the stylesheet.
- Then CSS downloads.
- Page renders after enough information is ready.
In some artificial tests, this can produce a slightly smaller measured time because the browser starts the script work earlier instead of waiting for CSS before script execution.
But this does not mean users get a better experience. It may simply mean:
- the browser delayed CSS discovery
- the first paint happened differently
- your metric measured one event, not the whole user experience
Important takeaway
A stopwatch reading such as “1.4s vs 1.5s” measures a specific outcome, not necessarily:
- first contentful paint
- styled paint
- layout stability
- interactivity
- visual correctness
That is why browser loading advice must be understood in terms of rendering rules, not just one elapsed-time number.
Real World Use Cases
This concept matters in real applications because front-end performance is rarely about only one file loading faster.
1. Marketing pages and landing pages
These pages need a fast, styled first paint. If CSS is delayed, users may see unstyled content or layout jumps.
2. Dashboards and admin tools
Scripts often read element sizes, initialize charts, or attach UI behavior. If styles are not ready, measurements can be wrong.
3. Component libraries
A modal, tooltip, or grid system may depend on CSS classes for sizing and visibility. Loading order affects whether JavaScript initializes correctly.
4. E-commerce product pages
Critical styles must load early so the page structure is stable. JavaScript for carts, analytics, and widgets should ideally not block initial rendering.
5. Single-page apps
Even app shells benefit from early CSS. Bundled JavaScript is often large, so using defer or modern build tooling is more important than simply swapping CSS and JS order.
6. Server-rendered apps
Pages generated on the server often have visible content immediately. Early CSS helps make that content usable and visually correct before JavaScript hydration or enhancement runs.
Real Codebase Usage
In real codebases, developers usually follow a few patterns instead of debating only “CSS first or JS first?”.
Common practical pattern
<head>
<link rel="stylesheet" href="/assets/app.css">
<script src="/assets/app.js" defer></script>
</head>
Why this is common:
- CSS is discovered early.
- JavaScript does not block parsing.
- The DOM is ready before the script runs.
Guard clauses for DOM-dependent code
Developers often protect code that expects certain elements:
const menu = document.querySelector('.menu');
if (!menu) return;
menu.addEventListener('click', () => {
console.log('menu clicked');
});
This helps when scripts are shared across many pages.
Waiting for the DOM
If defer is not used, developers may wait for DOM readiness:
document.addEventListener('DOMContentLoaded', () => {
const box = document.querySelector('.box');
if (!box) return;
console.log(box.offsetWidth);
});
Avoiding layout reads too early
If JavaScript depends on computed layout, developers often make sure styles are loaded and the element exists before measuring.
Separating critical and non-critical resources
Real apps often:
- load critical CSS early
- defer app logic
- lazy-load analytics or secondary widgets
- avoid blocking the parser with large scripts in the head
Error handling and resilience
In production, scripts can fail or load slowly. Teams prefer resource ordering that still gives users a readable page even if JavaScript is delayed or broken. That is another reason CSS-first remains a strong default.
Common Mistakes
1. Treating “CSS before JavaScript” as an absolute rule
It is a useful default, not a law of physics.
- Blocking CSS generally belongs early.
- But
defer,async, inline critical CSS, and app architecture can change the best choice.
2. Measuring the wrong thing
Beginners often time “when a script writes text” and assume that equals page performance.
That can miss:
- first paint
- styled paint
- layout shifts
- time to interactive
3. Putting blocking scripts in the head unnecessarily
<head>
<link rel="stylesheet" href="styles.css">
<script src="huge-library.js"></script>
</head>
If the script is not required immediately, this slows parsing and often hurts the page more than the CSS order itself.
4. Using async when order matters
Broken example:
<script src="library.js" async></script>
<script src="app.js" async></script>
Problem:
app.jsmay run beforelibrary.js
Use defer when execution order should follow document order.
5. Reading layout before the DOM or styles are ready
Broken example:
<head>
<script>
const box = document.querySelector('.box');
console.log(box.offsetWidth);
</script>
</head>
<body>
<div class="box"></div>
</body>
Problem:
.boxdoes not exist yet, soboxisnull
6. Ignoring visual correctness
A page that displays quickly but flashes unstyled content may feel worse than a slightly slower correctly styled page.
7. Assuming all browsers behave identically
Browser engines optimize loading differently. The general rules are similar, but exact scheduling may vary.
Comparisons
| Situation | Behavior | Good use case | Risk |
|---|---|---|---|
| CSS before blocking script | Styles discovered early; script may wait for CSS | Traditional pages where styling correctness matters | Script may not start immediately |
| Blocking script before CSS | Script starts earlier | Rare cases where script must run immediately and does not depend on styles | CSS discovered later; slower styled render |
CSS + defer script | CSS early, script downloads in parallel, runs after parsing | Best default for many modern pages | Script still waits until parsing completes |
CSS + async script | Script downloads in parallel and runs as soon as ready | Independent scripts like analytics | Execution order is not guaranteed |
| CSS in head + script at end of body | Early styles, late script execution | Simple sites without module bundling | Script downloads later unless preload techniques are used |
defer vs async
| Attribute | Blocks HTML parsing? | Preserves script order? | Best for |
|---|---|---|---|
| none | Yes | Yes | Small essential scripts only |
defer | No | Yes | Main app scripts |
async | No | No | Independent third-party scripts |
Correctness vs raw timing
| Goal | Why it matters |
|---|---|
| Earliest styled paint | Users see a usable page sooner |
| Stable layout | Prevents jumps and poor UX |
| Fast interactivity | Users can click and type sooner |
| Minimal blocking | Keeps browser work flowing efficiently |
The common advice favors correct styled rendering over a narrow benchmark that only times one event.
Cheat Sheet
Default rule
- Put stylesheets early in the
<head>. - Avoid blocking scripts in the
<head>when possible. - Prefer
deferfor main JavaScript files.
Safe modern pattern
<head>
<link rel="stylesheet" href="styles.css">
<script src="app.js" defer></script>
</head>
Key rules
- Classic
<script src="...">blocks HTML parsing. - Stylesheets can block rendering.
- Scripts may wait for earlier stylesheets before executing.
- A timing test can be misleading if it does not measure paint and layout stability.
Use defer when
- the script depends on the DOM
- script order matters
- you want parsing to continue
Use async when
- the script is independent
- order does not matter
- examples: analytics, ads, tracking
Avoid
<script src="app.js"></script>
<link rel="stylesheet" href="styles.css">
unless you have a very specific reason and understand the trade-offs.
Practical answer to the original question
- No, CSS does not always have to precede JavaScript in every possible case.
- Yes, CSS before JavaScript is usually the correct default for blocking resources in the head.
Edge cases
- Inline scripts in the head may run before body elements exist.
- Layout reads like
offsetWidthdepend on styles. - Browser scheduling details differ, but the broad guidance still holds.
FAQ
Does CSS always need to come before JavaScript?
No. It is not an absolute requirement. But for blocking resources in the <head>, placing CSS before JavaScript is usually the best default.
Why can JavaScript wait for CSS?
Because JavaScript may read style-dependent values such as computed styles or element dimensions. Browsers often wait to ensure those values are correct.
If JavaScript first benchmarks faster, should I use that order?
Not automatically. A faster benchmark for one measurement may still produce worse rendering, delayed styling, or layout shifts for users.
Is defer better than moving scripts to the bottom of the body?
Often yes. defer keeps scripts discoverable early while avoiding parser blocking, and it preserves script order.
When should I use async instead of defer?
Use async for independent scripts that do not depend on DOM readiness or other scripts, such as analytics.
Does CSS block HTML parsing?
Not in the same direct way as a classic script. But CSS can block rendering and can also delay later script execution.
Why is CSS-first still recommended if browsers are smart about parallel loading?
Because early CSS discovery improves the chance of an early, correct, styled render, which usually matters more than tiny differences in synthetic timing.
What is the best general pattern for modern web pages?
Load critical CSS early in the head and load main JavaScript with defer.
Mini Project
Description
Build a small HTML page that compares three loading patterns: a blocking script in the head, a deferred script in the head, and a script placed at the end of the body. This project demonstrates how resource order affects parsing, rendering, and DOM availability.
Goal
Create a page that logs when CSS is loaded, when JavaScript runs, and when the DOM is ready, so you can observe the effect of different script-loading strategies.
Requirements
- Create one HTML page with a linked stylesheet and a JavaScript file.
- Add visible content that should be styled by the CSS.
- Log timestamps for script execution and
DOMContentLoaded. - Test one version with a blocking script, one with
defer, and one with the script before</body>. - Observe whether the script can access DOM elements and whether the page styles appear promptly.
Keep learning
Related questions
Angular ngClass Conditional Class Binding Explained
Learn how to use Angular ngClass for conditional classes, fix common binding mistakes, and understand why this template error happens.
CSS :not() Selector for Elements Without a Class or Attribute
Learn how to use the CSS :not() selector to target elements that do not have a specific class or attribute, with examples and common pitfalls.
CSS Font Scaling Relative to Container Size: %, em, rem, vw, and Responsive Text
Learn how CSS font scaling really works and how to make text responsive using %, em, rem, vw, clamp(), and media queries.