Question

Should CSS Come Before JavaScript in HTML? Rendering and Loading Explained

cssjavascriptperformance

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:

  1. Render the page with correct styling as early as possible
  2. 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, sometimes async) 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.

Take Quiz

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:

  1. The browser discovers the stylesheet early.
  2. It starts downloading CSS.
  3. It later reaches the script.
  4. 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.css takes 500 ms to download
  • app.js takes 400 ms to download
  • app.js runs for 1000 ms

What typically happens

  1. The browser starts parsing HTML.
  2. It sees <link rel="stylesheet">.
  3. The browser starts downloading styles.css.
  4. Parsing continues until the browser sees <script src="app.js">.
  5. Because this is a classic script, HTML parsing pauses.
  6. The browser downloads app.js.
  7. Before executing app.js, the browser may wait for earlier stylesheets that could affect script results.
  8. The script executes.
  9. After script execution finishes, HTML parsing continues.
  10. 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:

  1. Browser sees script first.
  2. It pauses parsing immediately.
  3. It downloads and runs app.js.
  4. Only after that does parsing continue to the stylesheet.
  5. Then CSS downloads.
  6. 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.

Take Quiz

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.js may run before library.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:

  • .box does not exist yet, so box is null

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

SituationBehaviorGood use caseRisk
CSS before blocking scriptStyles discovered early; script may wait for CSSTraditional pages where styling correctness mattersScript may not start immediately
Blocking script before CSSScript starts earlierRare cases where script must run immediately and does not depend on stylesCSS discovered later; slower styled render
CSS + defer scriptCSS early, script downloads in parallel, runs after parsingBest default for many modern pagesScript still waits until parsing completes
CSS + async scriptScript downloads in parallel and runs as soon as readyIndependent scripts like analyticsExecution order is not guaranteed
CSS in head + script at end of bodyEarly styles, late script executionSimple sites without module bundlingScript downloads later unless preload techniques are used

defer vs async

AttributeBlocks HTML parsing?Preserves script order?Best for
noneYesYesSmall essential scripts only
deferNoYesMain app scripts
asyncNoNoIndependent third-party scripts

Correctness vs raw timing

GoalWhy it matters
Earliest styled paintUsers see a usable page sooner
Stable layoutPrevents jumps and poor UX
Fast interactivityUsers can click and type sooner
Minimal blockingKeeps browser work flowing efficiently

The common advice favors correct styled rendering over a narrow benchmark that only times one event.

Take Quiz

Cheat Sheet

Default rule

  • Put stylesheets early in the <head>.
  • Avoid blocking scripts in the <head> when possible.
  • Prefer defer for 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 offsetWidth depend 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.

Take Quiz

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.
Take Quiz

Keep learning

Related questions

View more