This answer is useful
754
This answer is not useful
Save this answer.
Show activity on this post.
This is a very interesting question. I've always put my CSS <link href="...">
s before my JavaScript <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.js (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 to DOMReady
.
- Delayed sending CSS and/or script to the browser by 500 ms.
- Ran the test 20 times in the three major browsers.
Results
First, with the CSS file delayed by 500 ms (the unit is milliseconds):
Browser: Chrome 18 | IE 9 | Firefox 9
CSS: first last | first last | first last
=======================================================
Header Exec | | |
Average | 583 36 | 559 42 | 565 49
St Dev | 15 12 | 9 7 | 13 6
------------|--------------|--------------|------------
Body Exec | | |
Average | 584 521 | 559 513 | 565 519
St Dev | 15 9 | 9 5 | 13 7
Next, I set jQuery to delay by 500 ms instead of the CSS:
Browser: Chrome 18 | IE 9 | Firefox 9
CSS: first last | first last | first last
=======================================================
Header Exec | | |
Average | 597 556 | 562 559 | 564 564
St Dev | 14 12 | 11 7 | 8 8
------------|--------------|--------------|------------
Body Exec | | |
Average | 598 557 | 563 560 | 564 565
St Dev | 14 12 | 10 7 | 8 8
Finally, I set both jQuery and the CSS to delay by 500 ms:
Browser: Chrome 18 | IE 9 | Firefox 9
CSS: first last | first last | first last
=======================================================
Header Exec | | |
Average | 620 560 | 577 577 | 571 567
St Dev | 16 11 | 19 9 | 9 10
------------|--------------|--------------|------------
Body Exec | | |
Average | 623 561 | 578 580 | 571 568
St Dev | 18 11 | 19 9 | 9 10
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 style sheets 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 style sheet, it would continue parsing the HTML while it fetched the CSS file (in parallel).
Hence, the widely-repeated advice to put style sheets 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)
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; your mileage may vary 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 JavaScript code 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.
File 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);
});
File 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>
File 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>
File test.js
var jsload = Date.now();
$(function() {
$.post('/result' + location.pathname.replace('.html','') + '/' + (jsload - start) + '/' + (bodyexec - start));
});
jQuery was jquery-1.7.1.min.js
Share
Share a link to this answer (Includes your user id)
Copy linkCC BY-SA 4.0
Edit
Follow
Follow this answer to receive notifications
edited Sep 3, 2022 at 17:40
[
](/users/63550/peter-mortensen)
Peter Mortensen
30.9k2222 gold badges107107 silver badges131131 bronze badges
answered Feb 14, 2012 at 6:37
[
](/users/201952/josh3736)
josh3736josh3736
141k3333 gold badges220220 silver badges268268 bronze badges
9
-
146
this is a fantastic answer, thanks for using the science! Per your result "in modern browsers, it looks like linking to CSS first never provides a performance gain", I think the answer to the question title is yes, the old advice of CSS first is clearly invalid.
– Jeff Atwood
Feb 14, 2012 at 7:54
-
Regarding @josh3736's update about the inverse on mobile... this is a case in point to not to jump the gun on this significant change. I'd be curious how other mobile browser behave (webkit, gecko, presto, trident, etc.) as performance in mobile is often more important.
– scunliffe
Feb 14, 2012 at 19:17
-
1
You should also try adding some slowness to printing out the css/js, to simulate speeds of a slow server.
– kirb
Feb 14, 2012 at 21:19
-
How about if you use defer or async? Does that change? (try with inline scripts and without inline scripts) Does that change?
– brunoais
Dec 25, 2012 at 18:28
-
1
"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>
)." I would highlight that much earlier in the answer, like at the top. Including a script
in head
that refers to an external file is almost never correct, from almost any perspective (certainly not a performance one). I don't recall ever having had to do it in real life. The odd line or two of inline script maybe, but that's all. The default, without very good contrary reasons, should be the end of the body.
– T.J. Crowder
Dec 12, 2018 at 11:46
| Show 4 more comments