We've recently been doing some optimization work on Camping.info to improve user experience through faster web site load times.
This post goes into the details of the optimization steps and their effect on the site's performance.
Measure, Improve, Measure again
To be confident that the changes we will introduce to Camping.info actually improve the performance or perceived performance of the site, we set up an automated test harness on TeamCity using the webpagetest API wrapper node module and a custom powershell wrapper script around that which collects the test results and reports them to TeamCity.
The following paragraphs will go into some detail on the concrete steps we took to improve our users's experience.
Include external script in existing bundle
As described in Avoid Blocking Requests on External Domains, we chose to include the Bugsnag javascript library in our already existing script bundle. This saves one request and one DNS lookup. Here's a look at the start page performance before and after:
The savings are humble but noticeable – the Time To Start Render drops from >1200 ms to 1100-1200 ms, which in practice will correlate with a slightly faster page appearance.
Host jQuery on your own server – or don't
Based on the previous improvement I assumed that saving a DNS lookup alone could already help in improving perceived performance. So for loading jQuery we switched from cdnjs.cloudflare.com to our own domain. It turns out though that this didn't have any impact on rendering or load times.
This is actually a tricky optimization – it depends a lot on who your audience is and what sites they visit. Loading e.g. jQuery from an external host will either save one request because the client's browser might have that resource cached after visiting a totally unrelated site that includes the same library, or your user will pay for an extra DNS lookup as compared to just loading the library from your own server. The decision is up to you.
Load external javascript after window.load
A large block of potential optimization on Camping.info actually concerns the loading, parsing, and execution of javascript. Due to the organic growth of the site over the last 8 years, deferring javascript execution to a point in time after the page has actually rendered turns out to be a complex issue. We still have plenty of pre-loaded or inline javascript blocks, which is mostly due to the way ASP.NET WebForms and its UpdatePanels work. The only easy solution to dependency managament for all of those code blocks was to simply load all dependencies before the HTML that refers to them. This pattern, unfortunately, has lead to one large script bundle that we load in the <head> section of the page because loading it later would mean breaking all inline script block execution. Fixing this will require significant refactoring and thorough testing.
But there still is room for improvement! All external javascript can safely be loaded after the page has rendered. This includes Facebook buttons, the AddThis widget, and advertisment scripts. We already had most of these scripts loading after the window onload event, but additionally deferring loading of connect.facebook.net/en_US/fbds.js showed the following improvement on the start page:
Now, while the render start time did not decrease, the page load time decreased from around 1.8s to 1.5s. Thi is definitely a decent improvement but please don't overrate it – most of the page's content had probably already been loaded even in the old version. But now we can at least be sure that all Facebook assets will definitely be loaded only after all of the page's own assets have been loaded. And that's good.
It turns out that on a different page, the improvement after this single change is actually even more significant:
Here we can see that the deferred loading of the Facebook script actually improves not only the page load time, but also the start render and DOM content ready times.
One script is still being loaded before the onload event – Google Analytics. I couldn't quite convince myself to defer its loading until after onload, because we use it to track some user metrics and timings, and I felt that GA might not report the same quality of results if loaded too late. Please leave your opinions on this topic in the comment section.
Specify image dimensions inline to speed up rendering
The worst grade in our PageSpeed score was actually for not specifying image dimensions, neither in HTML nor in CSS:
So we went ahead and did that for the start page. Here's how that improved our score:
I honestly cannot tell any difference in performance with image dimensions provided. There are several possible causes for this:
- maybe the images in the above-the-fold content are loaded fast enough to not delay page rendering
- maybe the page's CSS allows the browser to start rendering even without knowing the exact image dimensions
- something that I have no clue about at the moment.
Loading CSS file from same domain
To speed up rendering it also seemed to be a good idea to deliver our site's CSS file from the same domain as the HTML, thus saving a DNS lookup during the early stage of page rendering. Actually, the start render time dropped a bit by doing that but unfortunately the page load time increased a bit indeterministically:
It's safe to assume that the additional load time was caused by the fact that all image resources that are referenced in our CSS were now also being retrieved from the main domain instead of the cookieless one which in turn delayed loading of other image resources. For now we reverted this change, but we know that we can further optimize the render process by serving out CSS even faster. It would probably also help a lot if we split our large CSS file into smaller ones that could be loaded per page.
Changes without performance impact
- Wrapping inline javascript blocks in $().ready()
Todos for the next performance sprint
- defer loading of as many javascript files as possible to after the onload event
- combine and minify ASP.NET AJAX's ScriptResource.axd and WebResource.axd files
- load CSS from page domain but referenced images from cookieless domain (try css-url-rewrite)
- load less CSS per page – ideally inline the CSS needed for the above-the-fold content
- use HTML and CSS instead of images for our Google map buttons – this will save a ton of requests on the search page
Where are we at now?
Happy performance tuning!