Should CSS always precede Javascript?
This is a very interesting question. I've always put my CSS <link href="...">
s before my JS <script src="...">
s because "I read one time that it's better." So, you're right; it's high time we do some actual research!
I set up my own test harness in Node (code below). Basically, I:
- Made sure there was no HTTP caching so the browser would have to do a full download each time a page is loaded.
- To simulate reality, I included jQuery and the H5BP CSS (so there's a decent amount of script/CSS to parse)
- Set up two pages - one with CSS before script, one with CSS after script.
- Recorded how long it took for the external script in the
<head>
to execute - Recorded how long it took for the inline script in the
<body>
to execute, which is analogous toDOMReady
. - Delayed sending CSS and/or script to the browser by 500ms.
- Ran the test 20 times in the 3 major browsers.
Results
First, with the CSS file delayed by 500ms:
Browser: Chrome 18 | IE 9 | Firefox 9
CSS: first last | first last | first last
=======================================================
Header Exec | | |
Average | 583ms 36ms | 559ms 42ms | 565ms 49ms
St Dev | 15ms 12ms | 9ms 7ms | 13ms 6ms
------------|--------------|--------------|------------
Body Exec | | |
Average | 584ms 521ms | 559ms 513ms | 565ms 519ms
St Dev | 15ms 9ms | 9ms 5ms | 13ms 7ms
Next, I set jQuery to delay by 500ms instead of the CSS:
Browser: Chrome 18 | IE 9 | Firefox 9
CSS: first last | first last | first last
=======================================================
Header Exec | | |
Average | 597ms 556ms | 562ms 559ms | 564ms 564ms
St Dev | 14ms 12ms | 11ms 7ms | 8ms 8ms
------------|--------------|--------------|------------
Body Exec | | |
Average | 598ms 557ms | 563ms 560ms | 564ms 565ms
St Dev | 14ms 12ms | 10ms 7ms | 8ms 8ms
Finally, I set both jQuery and the CSS to delay by 500ms:
Browser: Chrome 18 | IE 9 | Firefox 9
CSS: first last | first last | first last
=======================================================
Header Exec | | |
Average | 620ms 560ms | 577ms 577ms | 571ms 567ms
St Dev | 16ms 11ms | 19ms 9ms | 9ms 10ms
------------|--------------|--------------|------------
Body Exec | | |
Average | 623ms 561ms | 578ms 580ms | 571ms 568ms
St Dev | 18ms 11ms | 19ms 9ms | 9ms 10ms
Conclusions
First, it's important to note that I'm operating under the assumption that you have scripts located in the <head>
of your document (as opposed to the end of the <body>
). There are various arguments regarding why you might link to your scripts in the <head>
versus the end of the document, but that's outside the scope of this answer. This is strictly about whether <script>
s should go before <link>
s in the <head>
.
In modern DESKTOP browsers, it looks like linking to CSS first never provides a performance gain. Putting CSS after script gets you a trivial amount of gain when both CSS and script are delayed, but gives you large gains when CSS is delayed. (Shown by the last
columns in the first set of results.)
Given that linking to CSS last does not seem to hurt performance but can provide gains under certain circumstances, you should link to external stylesheets after you link to external scripts only on desktop browsers if the performance of old browsers is not a concern. Read on for the mobile situation.
Why?
Historically, when a browser encountered a <script>
tag pointing to an external resource, the browser would stop parsing the HTML, retrieve the script, execute it, then continue parsing the HTML. In contrast, if the browser encountered a <link>
for an external stylesheet, it would continue parsing the HTML while it fetched the CSS file (in parallel).
Hence, the widely-repeated advice to put stylesheets first – they would download first, and the first script to download could be loaded in parallel.
However, modern browsers (including all of the browsers I tested with above) have implemented speculative parsing, where the browser "looks ahead" in the HTML and begins downloading resources before scripts download and execute.
In old browsers without speculative parsing, putting scripts first will affect performance since they will not download in parallel.
Browser Support
Speculative parsing was first implemented in: (along with the percentage of worldwide desktop browser users using this version or greater as of Jan 2012)
- Chrome 1 (WebKit 525) (100%)
- IE 8 (75%)
- Firefox 3.5 (96%)
- Safari 4 (99%)
- Opera 11.60 (85%)
In total, roughly 85% of desktop browsers in use today support speculative loading. Putting scripts before CSS will have a performance penalty on 15% of users globally; YMMV based on your site's specific audience. (And remember that number is shrinking.)
On mobile browsers, it's a little harder to get definitive numbers simply due to how heterogeneous the mobile browser and OS landscape is. Since speculative rendering was implemented in WebKit 525 (released Mar 2008), and just about every worthwhile mobile browser is based on WebKit, we can conclude that "most" mobile browsers should support it. According to quirksmode, iOS 2.2/Android 1.0 use WebKit 525. I have no idea what Windows Phone looks like.
However, I ran the test on my Android 4 device, and while I saw numbers similar to the desktop results, I hooked it up to the fantastic new remote debugger in Chrome for Android, and Network tab showed that the browser was actually waiting to download the CSS until the JavaScripts completely loaded – in other words, even the newest version of WebKit for Android does not appear to support speculative parsing. I suspect it might be turned off due to the CPU, memory, and/or network constraints inherent to mobile devices.
Code
Forgive the sloppiness – this was Q&D.
app.js
var express = require('express')
, app = express.createServer()
, fs = require('fs');
app.listen(90);
var file={};
fs.readdirSync('.').forEach(function(f) {
console.log(f)
file[f] = fs.readFileSync(f);
if (f != 'jquery.js' && f != 'style.css') app.get('/' + f, function(req,res) {
res.contentType(f);
res.send(file[f]);
});
});
app.get('/jquery.js', function(req,res) {
setTimeout(function() {
res.contentType('text/javascript');
res.send(file['jquery.js']);
}, 500);
});
app.get('/style.css', function(req,res) {
setTimeout(function() {
res.contentType('text/css');
res.send(file['style.css']);
}, 500);
});
var headresults={
css: [],
js: []
}, bodyresults={
css: [],
js: []
}
app.post('/result/:type/:time/:exec', function(req,res) {
headresults[req.params.type].push(parseInt(req.params.time, 10));
bodyresults[req.params.type].push(parseInt(req.params.exec, 10));
res.end();
});
app.get('/result/:type', function(req,res) {
var o = '';
headresults[req.params.type].forEach(function(i) {
o+='\n' + i;
});
o+='\n';
bodyresults[req.params.type].forEach(function(i) {
o+='\n' + i;
});
res.send(o);
});
css.html
<!DOCTYPE html>
<html>
<head>
<title>CSS first</title>
<script>var start = Date.now();</script>
<link rel="stylesheet" href="style.css">
<script src="jquery.js"></script>
<script src="test.js"></script>
</head>
<body>
<script>document.write(jsload - start);bodyexec=Date.now()</script>
</body>
</html>
js.html
<!DOCTYPE html>
<html>
<head>
<title>CSS first</title>
<script>var start = Date.now();</script>
<script src="jquery.js"></script>
<script src="test.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<script>document.write(jsload - start);bodyexec=Date.now()</script>
</body>
</html>
test.js
var jsload = Date.now();
$(function() {
$.post('/result' + location.pathname.replace('.html','') + '/' + (jsload - start) + '/' + (bodyexec - start));
});
jquery.js was jquery-1.7.1.min.js
There are two main reasons to put CSS before JavaScript.
Old browsers (Internet Explorer 6-7, Firefox 2, etc.) would block all subsequent downloads when they started downloading a script. So if you have
a.js
followed byb.css
they get downloaded sequentially: first a then b. If you haveb.css
followed bya.js
they get downloaded in parallel so the page loads more quickly.Nothing is rendered until all stylesheets are downloaded - this is true in all browsers. Scripts are different - they block rendering of all DOM elements that are below the script tag in the page. If you put your scripts in the HEAD then it means the entire page is blocked from rendering until all stylesheets and all scripts are downloaded. While it makes sense to block all rendering for stylesheets (so you get the correct styling the first time and avoid the flash of unstyled content FOUC), it doesn't make sense to block rendering of the entire page for scripts. Often scripts don't affect any DOM elements or just a portion of DOM elements. It's best to load scripts as low in the page as possible, or even better load them asynchronously.
It's fun to create examples with Cuzillion. For example, this page has a script in the HEAD so the entire page is blank until it's done downloading. However, if we move the script to the end of the BODY block the page header renders since those DOM elements occur above the SCRIPT tag, as you can see on this page.