A Guide to Fast Page Loads
nateberkopec.com
nateberkopec.com
First of all, a script tag will block DOM construction, but in any sane modern browser will not block loading of subresources like stylesheets, because browsers speculatively parse the HTML and kick off those loads even while they're waiting for the script. So the advice to put CSS before JS is not necessarily good advice. In fact, if your script is not async it's actively _bad_ advice because while the CSS load will start even before the JS has loaded, the JS will NOT run until the CSS has loaded. So if you put your CSS link before your JS link and the JS is not async, the running of the JS will be blocked on the load of the CSS. If you revers the order, then the JS will run as soon as it's loaded, and the CSS will be loading in parallel with all of that anyway.
Second, making your script async will help with some things (like DOMContentLoaded firing earlier and perhaps getting something up in front of the user), but can hurt with other things (time to load event firing and getting the things the user will actually see up), because it can cause the browser to lay out the page once, and then lay it out _again_ when the script runs and messes with the DOM. So whether it makes sense to make a script async really depends on what the script does. If it's just loading a bunch of not-really-used library code, that's one thing, but if it modifies the page content that's a very different situation.
Third, the last bullet point about using DomContentLoaded instead of $(document).ready() makes no sense, at least for jQuery. jQuery fires $(document).ready() stuff off the DomContentLoaded event.
The key thing for making pages faster from my point of view is somewhat hidden in the article, but it's this:
> This isn't even that much JavaScript in web terms - 37kb gzipped.
Just send less script. A lot less. The less script you're sending, the less likely it is that your script is doing something dumb to make things slow.
[Disclaimer: I'm a browser engine developer, not a frontend or full-stack web developer.]
Scripts without an async attribute will cause the browser to stop where it's at in the DOM and wait for the external script to download. The browser will not start downloading any stylesheets that come after the script tag until that's done. This is all straight from Google and can be verified in Chrome Timeline. https://developers.google.com/speed/docs/insights/BlockingJS
> So if you put your CSS link before your JS link and the JS is not async, the running of the JS will be blocked on the load of the CSS.
Downloading CSS isn't required to fire DomContentLoaded, so most browsers will download it in a non-blocking fashion. Chrome does not behave in the way in which you describe, which is shown plainly in the article. Again, this can be verified in Chrome Timeline.
All that said, I'm curious as to what browser engine you work when where what you said is the case - I don't doubt it, but you're not describing the way Chrome (and, I suspect, Webkit) works.
Right-o on the bit about DomContentLoaded, will fix.
Script async controls the latter. WebKit (and consequently Chrome) have a "preload scanner" (you can Google for those words to find posts about it) that attempt to parallelize the former in all cases. That is to say, a <script> followed by a stylesheet should always fetch both in parallel. The "async" tag controls whether the browser waits for the script to finish loading and executing before rendering the rest of the page.
I think this is a relevant snippet of Chrome code:
https://code.google.com/p/chromium/codesearch#chromium/src/t...
That's from the preload scanner, where it's deciding whether to fetch a URL referenced on the page. As far as I understand it this runs in parallel with any script execution blocking etc.
(Disclaimer: I worked on Chrome but never on this part.)
1. Network blocking. Requests must wait until the previous one finished. Browsers in 2005 might have done this but not anymore. Even in terrible document.write() scenarios, browsers will still try to do additional work/requests.
2. JS Execution blocking. The difference between [async] and [defer]. Browsers default to executing JS in order, which sucks if an early script takes forever to download. And layout/rendering is typically awaiting all this script to execute anyway.
3. Render blocking. (Or technically, layout blocking). Can the browser try to display pixels before this script has finished downloading or executing? By default, it cannot, but an [async] attribute at least allows the browser to.
#2 and #3 definitely matter, with render blocking behavior usually being the most important. [async] and decreasing script request count are very good.
Everyone does, as far as I'm aware. (And I'm pretty sure this is a case where one can invoke the Opera-did-it-first meme[0].)
[0]: http://w3cmemes.tumblr.com/post/62942106027/if-you-can-think...
He works on Gecko :) https://github.com/bzbarsky
Sort of. The browser will keep looking for things to speculatively download that are past the point where the DOM construction got blocked by the script. In Gecko, this actually speculatively tokenizes the HTML; in WebKit and Blink there's a separate preload scanner that doesn't do a full tokenization pass. In both, the script shouldn't block the sheet downloads starting. I'm fairly certain IE has a similar setup, but I haven't looked at their code, so can't guarantee it.
If Chrome Timeline is showing you that the script load blocks the stylesheet load, then it's either a bug in Chrome Timeline or a bug in Chrome's preload scanner; either way you should report it to them, because their intent is that it not block. Or possibly a case of running out of HTTP connections, of course, though I thought their connection limit was higher than that at this point.
> I'm curious as to what browser engine you work
Gecko. But I've spent a fair amount of time looking through WebKit and Blink sources over the years I've been doing this.
Isn't it more important to benchmark the parsing and execution time of your script? A large well optimised script could execute faster than a small unoptimised script.
that is why you have a sane view :)
on the wed dev trenches, more is better. we are all insane ppl who think adding react to a page will make it spiffier.
This - a thousand times.
This is why I tend to shy away from CMS' like Wordpress and Drupal. The amount of JS they load is insane. Not only do they load JS directly in the <head>, but then load a bunch more at the bottom of the page.
The problem with these is you either download and configure an easy to use plugin every time you need some functionality (image gallery, contact form, meta tags for seo) which load their own scripts and css stylesheet, or you have to write your own and then integrate into whatever CMS you're using.
two good Wordpress examples:
http://worryfreelabs.com/ - 6 scripts in the head, more than a dozen after the <body> tag. 113 requests and a staggering 14.7MB just on the home page.
http://www.thisisyourkingdom.co.uk/ - 9 scripts in head, 4 more in the body and close to a dozen style sheets that were loaded. 68 requests which took close to 9 seconds to load.
You always have to be careful with a CMS, it's very easy to load a lot of unnecessary stuff, but you can still build very fast and lean sites with Drupal with only a bit of knowledge.
It certainly let this 'dev' create a usable piece of software, at least.
PS - also looking forward to Let's Encrypt and http2 in Nginx.
[1] https://www.drupal.org/project/advagg/
[2] https://www.drupal.org/node/2493801
[3] https://docs.google.com/presentation/d/1Vaad5ToVU1lc7zWdN3qN...
The problem with worryfreelabs is all the images haven't been properly compressed. Yes, it's a terrible website, but not for the reasons you're insinuating. They've just put silly big images in. Also the reason for all the script tags is simply that they've not added JQuery UI properly.
And the problem with thisisyourkingdom seems to be a singular plugin, the wordfence plugin, which on its own is taking 8 seconds in a request which seems to be some sort of broken logging (I couldn't be bothered to look at the code so it might be a deliberate hang to make sure the person is really a human, but it's a terrible way of doing it). Everything else is loading in sub-2 seconds.
I'm all for ragging on Wordpress, as long as it's actually its fault ;)
I can't agree. It will likely regress window.load time, but that's a bad metric. Additionally, the reflow cost post-execute will be <100ms, but the delay to the paint caused by a sync script worth 100-5000ms. In very rare occasions would it be preferable to show a white page to a user longer to avoid the DOM change costs.
> DomContentLoaded
Agree with the above. Additionally, I believe all browsers wait for DCL handlers to finish loading before painting (for the common case). But, most pages have ALOT of code attached to DCL. If the handlers are not critical for the first paint (they shouldn't be), then the easy fix would probably be
document.on('DOMContentLoaded', e => { requestAnimationFrame(function(){ /* your doc.ready stuff */ })});
That way you get a paint to the user before your piles of DCL code runs.It really depends on the page. It's pretty easy to come up with pages that take a lot longer than that to reflow (tables, deeply nested flexboxes, etc).
I agree that typically making the script async is a good idea, but it's really worth measuring both ways for the particular script and page in question.
And some don't just preload resources from the speculative parsing—unless invalidated, the speculative parse is the tokenizer stage (into things like start tags, end tags, comments, characters, etc.) of the parser, and they're fed directly into the DOM construction once unblocked.
> > This isn't even that much JavaScript in web terms - 37kb gzipped.
> Just send less script. A lot less. The less script you're sending, the less likely it is that your script is doing something dumb to make things slow.
This. Seriously this. The amount of bloat in JS is insane—it's almost always the case that a lot less could be sent without any loss of functionality nowadays, and that's really, really sad.
I read that in McCoy's voice:
"Dammit Jim, I'm a browser engine developer, no a front-end or full-stack web dev!"
HTTP2 will absolutely change a lot of how this works, you're right. I chose not to cover it as it won't be a real option for most developers until nginx supports it (still in alpha). The skills I'm teaching in the article re: the Timeline can still be applied when HTTP2 gains mass adoption, so you can test for yourself whether or not the recommendations in the article still make sense.
Nginx servers can run SPDY until HTTP/2 is stable, or use an HTTP/2 capable proxy.
> You should have only one remote JS file and one remote CSS file.
I get this in theory, but it's difficult in practice. For example, this post has 7 CSS files and 13 JavaScript files. Also, combining all resources includes assets that aren't needed (CSS rules used on other pages), and also reduces the utility of public CDNs and browser caching.
Combining all the resources will include assets that aren't needed. Part of the reason I made the post about profiling with Timeline was so that you could test tradeoffs like this for yourself.
I don't know what you mean by reducing the utility of CDNs and caching though - surely having only one file actually increases the use of browser caching, since the browser will only make 1 request for CSS and serve the cached copy for every other page?
Personally, I've seen the greatest improvement in performance by avoiding latency more often than total download size. That's why concatenation works so well. It's just much more expensive to initiate two HTTP requests (four, if it does a HEAD first to get the Last Modified date), than one.
That said, I still limit total download size. JQuery is not a big enough contribution to my productivity to warrant an extra 86KB of data. React is like 132KB. Angular is 144KB. That's a lot of KB for anyone riding on the metro.
As a site grows, it's usually better to have 2-3 bundles grouped by frequency of change. E.g. I rarely upgrade 3rd-party files like jQuery, etc. but often make changes to the 1st-party app logic.
I think it depends on your use case. If your page views are mostly new users (e.g. you're a content publisher), then cachability doesn't matter too much. But if you're writing a web app hoping to have high engagement with users that return to the site frequently, it's likely you can be pretty effective with caching.
Also - I kinda doubt that browser caches use a simple LRU replacement policy. I suspect they have some heuristics built in to guess what is most likely to be re-used. I don't know for sure though...it would be interesting to know.
It's currently using 350MB.
Even jQuery from the Google CDN is rarely present in a cache. They are great for navigating around the same site in one session, but not effective for much more than that.
On public CDNs- I mean public CDNs for common libraries (jQuery, Angular, Font Awesome) can improve page load time if a user already has the file. The more sites use the same public CDN, the more users benefit. But if a user doesn't have the file already, it's a loss. So, tradeoffs.
On caching- if you make one JS file and one CSS file for each page, then there is no caching between pages but only necessary info is downloaded for each page. You could make one JS/CSS file for your whole site, but then there are unused assets on each page, and it becomes more difficult to simplify CSS and could increase layout thrashing. Again, tradeoffs.
Really enjoying this discussion, thanks for taking the time to write this up. I have this debate in my head constantly.
Even this has a tradeoff - if, for example, the other JS on your page require JQuery, you can't make the CDN copy of JQuery use an "async" tag, because you can't be certain of the order the JS will execute. And if you're not using the async tag, the first load will suffer.
This is a process and we're giving up a lot of stuff like Google Analytics (however, it's debatable how useful that is with our target audience anyway). As we move forward, we're also combining (compiling) our resources into a single, cacheable SPA.
Any time you incorporate code from another site, you're:
a) leaking information about your visitors
b) increasing the probability of malicious injection
c) for required assets (JS/HTML, usually not CSS), lowering your overall site availability/reliability -- even if that third party site is often more reliable than your site
d) for less frequently used CDN assets or less frequently used sources, possibly also decreasing the speed of site delivery, especially if resources are requested less often than cache expiration (depending on type of edge caching)
e) similarly, possibly slowing down site by increased name resolution requests (this can be mitigated somewhat with pre-fetching)
f) increasing variation in load times (weakest link)
g) trading off latency for bandwidth
The biggest reasons for us, however, are (a) and (b). This is also why TLS should forced enabled with HSTS for many sites. Security trumps efficiency, especially when the differences are measured in milliseconds.
http://i.imgur.com/MoRAVAH.png
EDIT: looks like it's live.js used to check for changes while in dev, probably shouldnt have that on the live site.
Subresource Integrity (SRI) [0] was designed to avoid this potential vulnerability. It's a brand-new browser feature (jus t landed in Chrome stable, is riding the trains to Mozilla stable now) but it's worth taking a look at. Github is already using it [1].
[0]: http://www.w3.org/TR/SRI/ [1]: http://githubengineering.com/subresource-integrity/
The upside is you've combined only the resources you truly need, though the downside is you bust the cache more often because you have twice as many manifests to download the separated content. Could be useful if you have a massive project and breaking it out will make for a significantly smaller download size.
[1] https://docs.google.com/presentation/d/1r7QXGYOLCh4fcUq0jDdD...
If you're displaying movies, maybe you need JavaScript? I don't know if HTML5 video can work without JavaScript (it ought to be able to, but lots of things which ought to be aren't).
If you're accepting user comments, you don't need JavaScript. Forms work just fine without it.
If you're accepting user votes, then you don't technically need JavaScript (but the experience will probably be better with it, unless you're smart).
Really, it's hard to see what one truly needs JavaScript for. Slowing down pages, sure. Destroying your readers' privacy, certainly. Getting root on your readers' computers, no doubt.
Here's a fun site of mine which has no Javascript, other than in iframes which display video hosted elsewhere.[1] (If Firefox would play HTML5 video from a plain .MP4 file, I'd get rid of that, too.)
Since when? The only JS privilege escalation is this Firefox vuln from 2008: https://www.mozilla.org/en-US/security/advisories/mfsa2008-1...
It's a Turing complete language. You truly need it if you want to do any computation in the browser, modify the DOM, work with Canvas or make asynchronous requests.
Yes, we could all go back to the way the web was in the early 90's when every site was nothing but text and images (and the occasional Java applet) but doing so would also disregard a lot of the very interesting things that javascript allows the web to do as a platform for serving applications as well as documents.
It may be true that most of the web might not absolutely need it, but let's not pretend it serves no valid purpose at all.
What annoys me to no end is 'apps' which are really just content browsers. I already have a content browser: it's called…a browser. There are well-defined semantics about how it retrieves content (resources). It works on the command line, within emacs, in a TTY, in a GUI and on my phone. It can be configured to run no untrusted code. It's really, really powerful.
[1] For example HN, doesn't need javascript, but for the vote-buttons, js is nice. With a pure html-page, the difference between a get with a vote-parameter and an ajax request shouldn't be terrible, with gzip compression etc. Maybe it's even possible to abuse something like status 304 to avoid a reload for a "/thisPage?upvote=someid" from "/thisPage" (with CSRF delegated to a document cookie?).
Granted HN might not be the pinnacle of graphic design, or UI -- but there's lots of room for improvement that doesn't have to involve js.
I think a lot of the problem is that it's no longer considered professional to do otherwise. You can put a javascript framework on your resume, but writing your own html, css and javascript comes across as carving your own canoe with a flint axe, and the "minimum viable product" for more and more sites involves more and more javascript by default.
No one even bothers try to optimize, or learning how to think outside the box that jQuery and frameworks put them into because it's just assumed you can pile on as much javascript as you like it'll just work, as if bandwidth and RAM are practically infinite. And for the most part, that point of view is correct... but eventually, the end users are going to notice that things are slower than they should be.
Use appcache (or service workers in newer browsers). Yes appcache is a douchebag, but its far simpler than going through all of these and will have a far bigger improvement
Appcache / service workers give you more absolute control over that caching, generally once data is cached it is loaded immediately from disk forever thereafter (and updated in the background when needed).
Appcache looks interesting, though. Thanks.
https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_...
[1] https://developer.mozilla.org/en-US/docs/Web/API/Service_Wor...
Then again I'm using AppCache as lightly as possible to get around some of the issues people report, essentially managing my own cache in one or more of the local storage options and just using AppCache for the stub that primes/updates the core code in that cache, so switching to using another option just as lightly might not be difficult. I'll have to read around Service Workers and make sure I'm not arranging to shoot myself in all three feet...
I had written quite a bit here on the sorts of things that I do to make very short time-to-first-view sites, but basically the techniques all fall in place if you setup Google Analytics and use their Page Speed Suggestions. You should be able to get to 95% on mobile, 98% on desktop (I lose points on some 3rd party scripts I've included for order processing, so I can't get around it.). It will be a bit difficult your first time, but after that you will know what all the tricks are and future sites you will just make "right" the first time.
[1]: https://www.udacity.com/course/website-performance-optimizat...
[2]: https://www.udacity.com/course/browser-rendering-optimizatio...
It means that if you have listeners to `load` event that are doing stuff, you may want to have the `load` event as fast as possible.
Also, until the load event is raised, browsers display a spinner instead of page's favicon.
Hence for non-critical third-party scripts, you may prefer to actually inject them in JS in onload handler instead of putting them directly in HTML.
A semi-related issue is handling failures of external non-critical scripts (analytics blocked by adblock etc)
I wrote a draft of a blog article on the topic last week:
https://gist.github.com/jakub-g/5286483ff5f29e8fdd9f
Context: We've faced an insane page load time (70s+) due to external analytics script being slow to load (yeah, we should have been loading the app on DOMContentLoaded instead of onload).
For non-critical third-party scripts, you might actually want to do something like doc.on('DOMContentLoaded', e => setTimeout(requestIdleCallback(init3rdparties), 2000));
React/Flux and the node ecosystem are more verbose than I'd like, but they might be onto something by rendering the initial content server-side.
Just spend 5 minutes selecting only the packages that you really use inside your webpage, you can drastically reduce css and js file size
Anyone interested in further reading on the topic should check out anything by those guys, they're the foremost in the field.
Once the page is loaded, use javascript to take over updates (using framework of choice).
It worked great in 2008, hopefully the modern javascript developers can now reinvent the wheel. It'll be a lot easier nowadays what with Node/V8 meaning you can use the same code...
It gives you access to everything Chrome dev tools do, plus so much more:
* speed index as a metric for the visual perception of performance
* easy comparison of cached vs uncached results
* operating on median metrics to ignore outliers
* side-by-side video playback of compared results
* different user agents
* traffic-shaping
* SPOF analysis
* a scriptable API
* custom metrics
I could go on. There's a lot of excellent performance tooling out there but WebPageTest is easily the most useful from my experience.Can WebPageTest (I've never heard of this tool) reach pages that require authentication?
Probably obvious, but you should avoid doing it from one of the public instances. Building your own private instance is as easy as spinning up a prepared EC2 image, or if you have a couple of hours to fiddle about you can do it from scratch on any Windows machine. Details here:
https://sites.google.com/a/webpagetest.org/docs/private-inst...
Do you have any tips for optimizing PHP, where server response times are poor to begin with? I've been trying to optimize a blog as proof-of-concept[1] but it has plateaued at 1.5s load time.
[1]: https://thelastcicada.com/microcaching-with-nginx-for-wordpr...
It's in beta, but contact me if you're interested.
So if .no-js class is available (when browser disabled JS), .my-class element still visible.
If .no-js is not available (when browser enabled JS), .my-class element will be hidden and visible by your JS
See http://stackoverflow.com/questions/6724515/what-is-the-purpo....
If you can't, you can often load a skeleton containing just the HTML & CSS for page layout into the browser, and then fill in the content via JS later. The user cares about responsiveness; if they see the basic structure of the page load, even if there's no content yet, it'll feel more snappy than a page which waits for all the JS to download before rendering anything.
div {
width:100px;
}
Then later in the CSS it's changed to div .biggerdiv {
200px;
}
Or maybe it's javascript that changes it: $('.biggerdiv').style('width:200px;');
but either way it's when an element has some size near the beginning of rendering, then as more information becomes available, it has to change size a few times.Am I getting it?
And in the context of page load, it would be if the dom is changed in many different places (So yes, your example would probably cause layout thrashing).
Is there a way to get stats about page loading from Timeline, that could be used to automatically ensure that the load times are not creeping up, and breaking NFRs?
> Web developers (especially non-JavaScripters, like Rails devs) have an awful habit of placing tons of code into $(document).ready(); or otherwise tying Javascript to page load. This ends up causing heaps of unnecessary Javascript to be executed on every page, further delaying page loads.
> Every time you're adding something to the document's being ready, you're adding script execution that delays the completion of page loads. Look at the Chrome Timeline's flamegraph when your load event fires - if it's long and deep, you need to investigate how you can tie fewer events to the document being ready.
jQuery source for the ready function: https://github.com/jquery/jquery/blob/c9cf250daafe806818da1d...
If you have a 'My Account' section w/several unique rules (say 10k), which is best? A) One website CSS (main.css) and your users download the My Account rules even though they may never use them B) 2 CSS files are used for My Account (main.css and myaccount.css) C) 1 file under My Account that incorporates the main and section rules (main-plus-myaccount.css)?
that is fine if you have a tiny little site. If you are a big company, each micro site will use a piece of the larger set of files. if one use a,js b.js and c.js, when you concatenate you just lost 100% of cache if the user clicks a link to a side of the site that only uses b.js and c.js
likewise, try to load, un-concatenated, common libs from widely used free CDNs.
For instance, the "javascript will be loaded on every page load" part no longer applies. It will be loaded only once, and will fech whatever it needs from then on.
Steps: go to theverge.com, open Timeline. Hit CMD-SHIFT-R for hard refresh, which will automatically trigger timeline. Wait. When the load event fires, Timeline will stop recording.
New Relic does, too! Its a Pro feature called Session Traces.
That's on a 100Mbit connection.