Hacker News new | past | comments | ask | show | jobs | submit login
A Guide to Fast Page Loads (nateberkopec.com)
597 points by nateberkopec on Oct 7, 2015 | hide | past | favorite | 163 comments



There's a lot of good advice here, but also some misinformation.

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.]


> making your script async … can hurt with other things

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.


> Additionally, the reflow cost post-execute will be <100ms

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.


I think this is what https://github.com/wilsonpage/fastdom was designed to help with... The issue I have with it is it's another library to load...


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

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.


There are two kinds of blocking that are easy to confuse. One is network: whether the browser will wait for a resource to fetch before fetching another. The other is execution: whether the browser will wait for a resource to execute (which itself depends on fetching) before executing the next.

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.)


(Mostly agreeing, just adding some more)... I suppose theres 3 kinds of blocking really:

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.


For #2 it's also worthwhile to remember that stylesheets block scripts, so it's not just scripts blocking other scripts.


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

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


It's a couple years old but this is a pretty good explanation of the preloader - http://andydavies.me/blog/2013/10/22/how-the-browser-pre-loa...


> Scripts without an async attribute will cause the browser to stop where it's at in the DOM and wait

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.


> All that said, I'm curious as to what browser engine you work when where what you said is the case

He works on Gecko :) https://github.com/bzbarsky


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


Err, they're not good examples of bad javascript.

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 ;)


At least Drupal 8 is much better about this, by default it doesn't load any JS unless there is actually anything on the page that requires it. And scripts are added below the main content and not in head. Caching also got much better in Drupal 8 and is enabled by default, which makes a huge difference in speed.

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.


I'll throw in my own 2 cents, in agreement. I have tried to use SPDY, Nginx 'microcaching', Drupal caching to make my own Drupal 7 project load as quickly as possible. I still need to make the font and bootstrap calls local (potentially dropping the fancy font for Georgia or Verdana, even Helvetica...) to really bring the load times down further. There are other settings to tweak still, but it's one example of Drupal not dragging its feet too much.

It certainly let this 'dev' create a usable piece of software, at least.

> https://www.linguaquote.com

PS - also looking forward to Let's Encrypt and http2 in Nginx.


Checkout the AdvAgg module [1] it does async font loading. It was used to speedup the frontend of drupal.org and the results are fairly remarkable [2]. Also if you looking for more ways to speed up drupal checkout my DIY Drupal 7 Performance [3] presentation.

[1] https://www.drupal.org/project/advagg/

[2] https://www.drupal.org/node/2493801

[3] https://docs.google.com/presentation/d/1Vaad5ToVU1lc7zWdN3qN...


Thanks for that - have written up most of the performance work I've done and posted to HN[0] - I see from link [2] above that you retested drupal.org and found render times to have doubled since a recent change. It's so easy to undo all that hard work!

[0] https://news.ycombinator.com/item?id=10418732


I've been thinking of going back to Drupal for some time now. Thanks for the heads up to take another look at it again.


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

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.


> [Disclaimer: I'm a browser engine developer, not a frontend or full-stack web developer.]

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.


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

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.


If you're going to benchmark (especially on a variety of network connections), that trumps everything else, of course. Measurement of the actual workload is pretty much always better than general discussion about how workloads should be structured.


In the majority of cases, load time dominates over execution time.


>[Disclaimer: I'm a browser engine developer, not a frontend or full-stack web developer.]

I read that in McCoy's voice:

"Dammit Jim, I'm a browser engine developer, no a front-end or full-stack web dev!"


This guide is a comprehensive explanation of Chrome's Network timeline, but the optimisation recommendations are quite skewed towards the front-end. There's a missing piece on server configuration, no mention of CDNs or asset/domain sharding for connection concurrency, server-side or client-side caching. It also doesn't take into account HTTP/1 vs. SPDY & HTTP/2. For example, loading JavaScript modules as individual files can improve performance for SPDY & HTTP/2, because changes in individual files don't expire the entire concatenated bundle. Here's a slide deck called "Yesterday's best practices are HTTP/2 anti-patterns", that re-examines some of Nate's advice: https://docs.google.com/presentation/d/1r7QXGYOLCh4fcUq0jDdD...


I wanted a guide that focused on the one thing all web developers have in common - the page construction process in the browser. We all use different servers, languages, etc.

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.


Okay. I'm pointing these out because it says Full-stack in the title.

Nginx servers can run SPDY until HTTP/2 is stable, or use an HTTP/2 capable proxy.


For those interested, the unstable (alpha) HTTP/2 element of NGINX is this patch for the 1.9.x mainline versions: http://nginx.org/patches/http2/README.txt


Check his previous posts, he does lots of work on backend speed. If you put all that advice into one post, it would be pretty overwhelming.


For anyone that's interested, here's a guide on scaling Ruby servers to 1000RPM, written by the same author: http://www.nateberkopec.com/2015/07/29/scaling-ruby-apps-to-....


Is it me or is 1000 requests per minute a laughable goal? That's 16.6 requests per second, i.e. 60ms per request _without any parallisation_. I'd expect any unoptimised app to be able to reach that goal.


Maybe if you read the first paragraph you'd find out


Article is actually quite good, but it seems Rails is really slow. Twitter 2007 is said to be at 300ms/req. Others come in around 100ms. So it would seem that you don't get 16RPS without some effort.


Then you have to design your bundles in such a way that it contains the least changing files (like external files like jquery, underscorejs etc). The files that are changing frequently can kept together in a separate bundle like your internal app files.


HTTP2 only works for HTTPS sites. For a myriad of reasons, that is not practical for a lot of websites. HTTP 1 best practices are going to be needed for a long time.


I believe the encryption requirement was dropped at the last minute. HTTP2 will serve unencrypted connections.


Browsers aren't supporting that part though, so HTTP2 for the web is only encrypted.


Great technical details in this post. When speeding up page loads, I usually struggle with:

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


Third party CDN's are a security tradeoff. For Userify (an SSH key management company), we're working on eliminating our use of all third-party domains in both our app and main website.

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.


> b) increasing the probability of malicious injection

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/


Absolutely! unfortunately it may take years before the majority of browsers use it. :(


Your site right now has some JS that's in a continuous loop adding the same assets to the page.

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.


Good catch! Fixed.


With HTTP 2 this is actually no longer true. See [1], slide 29. Also e.g. sharding is no longer necessary or even a good idea.

[1] https://docs.google.com/presentation/d/1r7QXGYOLCh4fcUq0jDdD...


One strategy for having 1 CSS and 1 JS file is to have a manifest file per model. So say for example you have User and a Widget model; the user dashboard lies within the User controller, and the product page is within the Widget controller. You can collect only the styles and javascripts needed for widgets into a concatenated file, and separately do the same thing for the user dashboard.

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.


Ha, I should totally fix that. The site uses Jekyll, not Rails, which I'm used to getting the asset pipeline for free with!

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?


Agreed, it's all about tradeoffs.

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.


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

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.


Unfortunately the variety of library versions and public CDNs prevents users from realizing the benefits promised by caching most of the time.


Combining everything into one file reduces browser caching, since a change in any of the N files invalidates the cache.

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.


Except for Chrome, and firefox users who have explicitly configured their cache size, most browsers have a very limited cache, with the average sizing being ~50MB. For everything. I don't know about you, but I've loaded 50MB already today. Because of that, I don't worry about cache hits, because the median visitor will have nothing cached. Instead, worry about total page size, and total number of http requests. Minifying dramatically helps the number of http requests.


Great point, and caches are even smaller on mobile.

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.


Not sure about these numbers, but.. yes, in general browser disk caches are not nearly as effective as people imagine.

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.


I've never configured my cache in Firefox. Just looked at the settings, and, as far as I get what's written (it's not very clear), there's no limit at all on my cache size.

It's currently using 350MB.


Chrome has several different caches (HTTP, DNS, SSL session data, etc.) but looking at just the on disk HTTP cache my laptop is currently using 201MB with the default settings. For anyone else who is interested, you can see the cache stats at chrome://net-internals/#httpCache


199MB here. I think Chrome defaults to 200MB, yeah.


I just checked my preferences on Firefox 39 on OS X, and my cache is 350mb by default (I never changed it).


Excellent point about one file update invalidating the bundle, totally missed that in my argument. And I like your approach in bundling by update frequency!


A bit off-topic, but I recommend Middleman as a nice, well, middle-ground: Middleman builds to static files like Jekyll, but pulls in Rails' asset pipeline features for a better experience.


He's implying that other sites may use the same popular Javascript libraries, so they may already be in the browser cache, before the first page load.


I've never understood why this was a good argument, because it just ignores that there are still going to be a lot of people who hit the worst-case scenario. When optimizing page-load time, you should always optimize for the worst-case scenario, not the best-case.


It really depends on your use-case & what you're doing. In many cases you will likely want to target ~ 95th percentile or similar.


Also, how likely is it that two websites use exactly the same CDN and version of e.g. jQuery or Angular?


This is probably a major contributing factor to the issue.

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.


What he probably means is "I thought I should include a public CDN version of jQuery instead of combining it with my own scripts, so that it is served once across the internet and not just once across my website"


But if you're pulling in shared resources from a public CDN, like jquery, the browser probably already has it cached and won't need to download it at all.


I'd love to see some concrete numbers on public CDN hit rates. It feels to me like of those ideas that sounds great in theory, but in the real world doesn't really work because of the multitude of versions, libraries, and options for public CENsus.


I have some memory of someone doing this and finding the hit rates were abysmal—too many libraries and too many versions. And that was five years ago, and we just have more libraries and more versions today. I'd love to dig up the numbers… I hope they aren't somewhere internal to Opera (where I used to work)!


Pure HTML loads ludicrously fast these days—as in, well-nigh instantaneously. With a single CSS file, you can make it quite attractive. Eschew JavaScript unless you really, truly need it.


I ragged on The Verge for taking 200ms to parse HTML, but if that's all The Verge was, it would be a ludicrously fast site. It's the 3.5 seconds of JS evaluation that tanks the site 's speed.


To be clear, you're saying that Chrome takes 3.5 seconds to evaluate the JavaScript, not download it, right? And what kind of processor (e.g. Intel model number) is that on?


What website nowadays you do not ever truly need JS?


Websites don't need javascript in general[1], webapps often need javascript.

[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.


Unfortunately, every otherwise static site using a framework like AngularJS becomes a webapp by definition, whether they need to be or not.

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.


And some webapps don't need javascript. Google.com still works from a w3m session (as well it should).


Most websites whose primary use is conveying information and documents.


If you're displaying text, then you don't need JavaScript. If you're displaying images, then you don't need JavaScript. If you're displaying short animations, then in theory you don't need JavaScript (I don't know how widely support APNG is though).

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.


I tend to agree. There are far too many purely static sites that just don't need Javascript.

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.)

[1] http://www.aetherltd.com


> Getting root on your readers' computers, no doubt.

Since when? The only JS privilege escalation is this Firefox vuln from 2008: https://www.mozilla.org/en-US/security/advisories/mfsa2008-1...


Any RCE exploit can likely be combined with any other OS vulnerability to get privilege escalation.


>Really, it's hard to see what one truly needs JavaScript for

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.


Yeah, I was being a bit negative. I'm actually not opposed to the idea of web apps (although I think that the sole virtue of HTML+CSS+JavaScript is that it exists): they really can be useful, and there's no good alternative (which is truly sad).

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.


This is a useful guide, however there is one thing missing that will have an order of magnitude improvement over anything that is mentioned.

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


In my experience, appcache only improves second loading time. You need all of these other techniques to improve first loading time. 2nd time is much, much less important to optimize, because returning users have already gotten through the annoyance filter, know what they are in for, has at least subconsciously accepted the necessity of the load time they experienced the first time. It's nice they won't have to experience it a second time, but if first-time loading is very long, your second-time users are going to be much fewer.

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.


Confused; won't the second user experience faster page load with appcache?


AppCache is client-side; it caches a certain set of URLs in your browser so that they are available with no HTTP request. It doesn't work until your browser has hit the page once, downloaded the cache manifest, and saved the files.


Loading more than just the first page the user visits quickly is still really important though. If each link the user visits is slow they're more likely to leave the site and that reduces your chance of a conversion.


That has nothing to do with appcache specifically. You'll get that with regular, ol' HTTP cache control.


I can testify to this, we use appcache for our HTML5 game and it massively improved load times. However, if you are going to use it, be absolutely sure you know what you are doing. There are easy ways to mess up and no going back if you do.


Appcache may not be the best approach for new work. It is deprecated and not recommended for future use.

https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_...


It says that service workers are the replacement but on the service workers page cautions that they are experimental technology and the spec isn't finalized[1]. If you're deploying a new application which should you use?

[1] https://developer.mozilla.org/en-US/docs/Web/API/Service_Wor...


I use service workers for side projects that only target new browsers, and appcache for everything else.


Service Workers is a definite game changer. However, one thing to remember when implementing them is that you will need to be entirely on HTTPS.


That is a pain. I've got a project in early stages that is using AppCache to deal with disconnected use, and AppCache's replacement isn't commonly available yet.

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


Neat - I've never used it. I thought appcache only really helped "offline" web apps. How can it speed up ordinary sites?


Browsers make a best attempt at caching, but they dont have a lot of information to go on huge amounts of data that may be able to be cached and the only way to check if its out of date is by going over the network.

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).


If you're wondering why this got downvoted, I did it accidentally.

Appcache looks interesting, though. Thanks.


Udacity has some really awesome free courses from Google devs about this: Website Performance Optimization[1] and Browser Rendering Optimization[2]

[1]: https://www.udacity.com/course/website-performance-optimizat...

[2]: https://www.udacity.com/course/browser-rendering-optimizatio...


I really recommend "High Performance Browser Networking" book by Ilya Grigorik, it digs deep into the topic of browser performance: http://chimera.labs.oreilly.com/books/1230000000545/index.ht...


One thing that might be non-obvious is that async script, while not blocking `DOMContentLoaded`, blocks `load` event.

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).


Since `load` waits for all iframes and images, you typically don't want JS initialization to be dependent on it.

For non-critical third-party scripts, you might actually want to do something like doc.on('DOMContentLoaded', e => setTimeout(requestIdleCallback(init3rdparties), 2000));


Hi Paul, thanks for sharing the `requestIdleCallback`, I didn't know it, pretty interesting! Though since it's only in Chrome 47+, it will take a while for it to gain market adoption.


Spending the last 6 weeks in East Africa has completely changed my perspective on web performance. And it's not just performance, it's reliability. Every request a page can't work without is another chance to fail.

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.


For sure. Rendering JS apps server-side is pretty much mandatory from a performance perspective.


Another small advice, but less generic, could be to not include all css and js libs of bootstrap, that is modular. I'm mentioning bootstrap because it is the standard de-facto of many web apps.

Just spend 5 minutes selecting only the packages that you really use inside your webpage, you can drastically reduce css and js file size


Doesn't this force you to build it yourself from the less files?


yes but the compilation of boostrap from sass or less is very fast.


This is damn helpful! Thanks for sharing. If you're interested in getting to know more about how other sites perform and how to use chrome devtools to address frontend performance issues, Google developer - Paul Lewis recently started a series on Youtube called Supercharged. Here's the first episode - https://www.youtube.com/watch?v=obtCN3Goaw4


I am heavily indebted to Paul Lewis and Ilya Grigorik's writing, which provided much of the source material in this article.

Anyone interested in further reading on the topic should check out anything by those guys, they're the foremost in the field.


On a static HTML site with no scripts or external resources, I see 100ms of loading/painting in the beginning, then 3000ms of "idle" time at the end, which turns the flame graph into a pixel wide column. What is the point of that?


Something is delaying the load event. You'll have to dig deeper to see what's holding it up - probably a network request for an external resource.


No resources. Happens on example.com, too, fwiw. Weird. Oh, well.


Double-check that you're disabling extensions.


I've had great success in the past doing one very simple thing: on first load send the client the exact html/css that must be loaded on their screen.

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


This is a really nicely put together article, and I'll even admit the animated gifs are funny, but damn if they don't make it impossible to focus on reading the text.


Surely the first bit of advice in any post about analysing website performance should be: USE WEBPAGETEST

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.


> It gives you access to everything Chrome dev tools do, plus so much more:

Can WebPageTest (I've never heard of this tool) reach pages that require authentication?

http://www.webpagetest.org/


It can, using either basic auth or by scripting submission of login forms.

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


It's open source, you can build your own

http://www.webpagetest.org/about


I have been using AppTelemetry plugin for rough numbers on each phase of request. This is much better for performance tuning.

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] http://wpdemo.nestifyapp.com/


You are probably not going to get decent response times with PHP, especially if you're using a framework. If you've already taken care of the basics (opcache, PHP7 or HHVM, WP-specific caching plugins, etc) you would have to settle for techniques such as nginx microcaching [1], which can drop your responses to around 10ms.

[1]: https://thelastcicada.com/microcaching-with-nginx-for-wordpr...


Wow. Microcaching reduced response time from 25 ms to 3 ms. Thanks!


Gratuitous plug, my startup, https://rasterize.io, will give you most of the information in the chrome timeline, for every single visitor to your site. It also analyzes the page and detects a lot of these warnings, and alert when you introduce things that slow the page down.

It's in beta, but contact me if you're interested.


Any tips on how to handle site layouts that depend on the js that is asynchronously loaded via the async attribute? Seems like this can cause a flash of unstyled/unmodified html while that js is loaded and executed.


If you can, pre-render the initial page load into HTML on the server, then attach the JS event handlers after the page has finished loading. Both Google Closure (via Closure Templates) and React (via Node.js) have functionality for this.

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.


Use CSS to hide the unstyled content, and then JavaScript to remove that styling and display it. This will, of course, invoke the ire of those who browse with JavaScript disabled.


How I did it is begin with .no-js class and using Modernizr to remove the class so I can style element for .no-js .my-class -> visibility: visible and .my-class -> visibility: hidden then finally using JS to remove visibility css.

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


Could anyone explain like I'm 5 what "layout thrashing" is? As far as I understand, it's when the size of an element is set in the CSS, like

    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?


Layout thrashing is when the browser layout engine does some work to render the page, but then something changes and it has to do it over again. A typical case is if you have a loop in javascript wherein you both read and write to the dom. For example, if you change the css style of an element and then afterwards try to read its position property. Because changing the style might have affected the position, the browser will have to render parts or whole of the page in order to give you that information. It's a very easy mistake to make and it's not obvious what you have done if you don't look for 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).


Potentially dumb question from a backend dev:

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?


Sure, check out the Navigation Timing API: https://developer.mozilla.org/en-US/docs/Web/API/Navigation_...


Could someone explain this paragraph? I feel like it is making a lot of assumptions or generalizations about the use of $(document).ready();. I do not follow what he is trying to say:

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


Nevermind, he explained this again more clearly in the TL;DR.

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


Can someone explain his point about $(document).ready()? I don't understand how it is different from DomContentLoaded?

jQuery source for the ready function: https://github.com/jquery/jquery/blob/c9cf250daafe806818da1d...


Question about the 1 CSS/JS file rule.

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)?


In the case of 10 Kb files those approaches won’t make any noticeable difference. But when you have large code bases consider how often changes would be made to them: in most cases it is sensible to split your files into two bundles — one for libraries or files that are not going to be updated any time soon, and the other for project-specific code, that is very likely to be tweaked in the course of the time following the release of the project. Thus you leverage caching and don't force the users to download the whole CSS or JS codebase every time you make some small adjustment.


It's a case where you should use your best judgement. You may want your marketing landing page to load faster, so probably wouldn't include your account section css there. You can also lazy-load the My Account assets on the login page.


Depends on how likely the user is to go to their account when they're logged in. :) Generally, I'd say split that into two stylesheets, especially if your site allows access to people without accounts, since non-users would be downloading the account styles for no reason.


"JS and CSS assets must be concatenated"

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.


I like this guide, with the caveat that, if you are doing a single page web application, some of it gets turned upside down.

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.


Regarding layout-thrashing: if only there were a way to hint the dimensions of each element.


Gifs in the sidebar, 10/10


Only the first cycle. After that they're 0/10.


The best explanation of the timeline tool I've ever found. Thank you!


Very helpful stuff but I did go to The Verge and look under the timeline. Scripting was a fraction of 1% of the load time. Have they disabled it because of your article or am I missing something?


Hmm, you must be. Here's my timeline for theverge.com: http://imgur.com/SOpXFZc

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.


Ouch - OK, now I got it. Yeah that's terrible. Scripting is double loading, rendering and painting combined.


LOLZ @the layout thrashing gif! Nice post though, very informative.


Thanks - a well-written article with some really helpful pointers.


...and if you do HTTP/2, you'll get even faster loads if you pretty much break all those rules (some notable exceptions).


True, but that's a post for another day. We've still got another year or so until HTTP/2 gets mass adoption.


Except SPDY has been out for a while so why would anyone have been or continue to wait?


You might want to check your logs...


>While I use New Relic's real user monitoring (RUM) to get a general idea of how my end-users are experiencing page load times, Chrome Timeline gives you a millisecond-by-millisecond breakdown of exactly what happens during any given web interaction.

New Relic does, too! Its a Pro feature called Session Traces.


>47 requests, 7.474,76 KB, 4,68 s

That's on a 100Mbit connection.


the page it's too slow to load... i waited solid 5 minutes... and... still... nothing... to... see...


"bare-metal Node.js"


That was awesome- thanks!


Had to disable the font of the website, it's terrible for reading.


I wish I could find a font everyone liked that isn't Helvetica :(


That's not the problem. When I view on a Apple device it looks good. I use Windows 10 at home and I just couldn't read it.


Google just came out with the AMP project. It looks like google is encouraging publishers to join its initiative in the same way how it herded everyone to get "responsive". At the end of the day, good news for mobile web - https://techbullets.com/




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: