- For sprite-ing your images, use some tool which will automatically generate the css for you: http://spritepad.wearekiss.com
- Have a git commit hook for compressing images. (It could be part of your build, but then you are doing the work every time and the build will be slow if you have a lot of images.)
- If you are using LessCSS or something similar (hint: you should), have your compiler compress the css for you. - http://wearekiss.com/simpless
- Have multiple hosts referring to your CDN to facilitate parallel downloads in the browser.
- Have proper versioning in the name of your JS/CSS files and then cache them for ever. Whenever you change your files, the version should change and only that file will be updated in the client. We normally take the hash of the latest git commit on that file:
Can you expand on simpleless a bit, and why it is better than using lessc from the lesscss project? I'm not seeing any good resources on the project page and a googling is turning out to be basically useless to find more info.
> Make sure you define a width and a height for all images on your webpage. This helps save time since the browser doesn’t have to do the work of calculating the dimensions.
This isn't correct in my opinion (it might be technically but not to a level where anyone would notice it), what it actually does it reserves the space in advance for the image as the rest of the page loads. If you don't do this then what happens is that the content keeps being pushed down which makes it seem like it's loading slower. So this impacts perceived loading time over actual loading time.
> what happens is the content keeps being pushed down
What happens is that the browser is re-flowing and re-painting part or all of the page, rather than updating just the area the image is known to occupy. Reflows are expensive, particularly on mobile.
Also of note is that in some circumstances (as mentioned in the link above,) the browser may have to delay rendering some parts of the page until it knows the dimensions of either the image or one of its containing elements, which means rendering the container might have to wait until it fetches enough of the image to calculate the height and width.
Lots of good tips in here already. Yahoo has a great speed guide (http://developer.yahoo.com/performance/rules.html) which I'd suggest reading through, they cover pretty much everything in this blog (and chrisacky's comment above), and go into some more detail.
For testing your site, I recommend WebPageTest (http://webpagetest.org/). Run the test a couple times to get an averaged score, then start tweaking. Watch the HTTP requests. Watch the total download size.
The biggest things to focus on are caching, limiting HTTP requests (combining images into sprite sheets, bundling scripts), and optimizing images. Learn about these.
My favorite tools for optimizing images are PngOut (http://advsys.net/ken/utils.htm) and JpegTran (http://jpegclub.org/jpegtran/). Both are lossless, I generally just run the tools batch through my websites image folder. If you want lossy Jpeg compression (you probably do, just keep the original file safe) then try Jpeg-Optimizer (http://jpeg-optimizer.com/). If working with a large photo on the homepage or something like that, try multiple levels of compression and find the best quality/filesize tradeoff. You can often cut the filesize in half with no noticeable degradation at 100% zoom.
About the twitter/facebook buttons. You can just delay until after the window.load event has occurred. It will have no impact on the page load, since the page is fully rendered at that point.
$(window).load(function() { /* load twitter and Facebook buttons */ });
You are making part of your page always load at least 2 seconds slower than is probably necessary.
$(function(){}); is shortcut for $(document).ready(); which waits until all other external content has been loaded. [1]
If for some reason you are loading something else you need after .ready() fires, and you can't load that inside of a single .ready() call for some reason, you can potentially add multiple callbacks to .ready() (they will be executed in order [2]), or attach a .load() to that particular item instead (maybe even inside your .ready().)
$(document).ready does "not wait until all other external content has been loaded". It fires when the DOM is fully parsed, irrespective of whether there are still images or iframes or other assets still loading.
I think he was getting at the idea that in general each page has some specific-to-the-page JavaScript work to do as soon as it's ready. The delay punts initialization of the less-important plugins, allowing time for the important stuff in a page-generic way. Your point that the delay is a messy way of accomplishing this is certainly valid.
The onload event fires after the page has fully loaded - all scripts, image files, etc. I would say that it is more effective than your snippet for waiting until "essential work" has been finished.
Regarding gzipping, you don't want your webserver doing this on the fly. At least in nginx, you can tell the webserver to load pre-gzipped files, I'm sure apache has something similar.
Put this into nginx.conf: gzip_static on;
Then run this command in /var/www to gzip your content: find . -name "*' + ext + '" -print | xargs -I filename echo "gzip -c filename > filename.gz" | sh
This is a small win (reduced cpu/disk IO), but at two lines it's an even smaller cost.
Also, if you use django mediagenerator, it will handle a lot of this stuff for you.
- Load everything asyncronously. RequireJS etc... (if you don't use AMD loading, then seriously start reading up about it).
- The overhead of including Facebook Like button and Twitter, is like 150kb. It's ridiculous. We load them after the page has loaded. In their place, we load placeholder Facebook buttons. That mimic the like button. (Similar to what TechCrunch does).
- Bundle everything. We use shrinksafe for this. (Sometimes Closure... it really depends on which one is playing nicer at the time of doing the build). This allows us to create enormous JS files of all of our application. What we do is create one "kernel.js" file. This is always loaded first. And once the page has loaded we fire off using AMD loading asyncronously to the actual application.js. I've played around testing on this as to whether it's best to only include the javascript that is relevant to the current page, but in the end we settled on loading absolutely everything. This makes the JS about 280kb gzipped. The user doesn't really ever notice the hit, since we load the "kernel.js" first, which contains the bare bones minimum.
- Use a CSS preprocessor. We use LessJS. We managed to reduce our CSS overhead by an overhead of 20% in file size while using the same sheet. When you use a preprocessor (although we supply the compiled versions) you are making the file gzip heaven. The ungzipped version is larger, but the compression is great. Additionally, maintainability improved by an order of magnitutde that I can't even count. Incidentally, we also provide the entire CSS for the entire site in a single .css file. Again, tests were done with splitting the CSS, but it just makes more sense to provide everything and improve the page loading on second requests (We don't have a high bounce rate so it might make more sense on our types of sites).
- SpriteSheets. You can't really over emphasise the importance of these. HTTP requests are expensive when you are firing off multiple requests to the same domain. Depending on the browser you could hit blocking/waiting pretty quickly. It really pays off to merge your images into a sheet. Don't use a premade sprite sheet with 100 icons on it, where you only use 8. Make your own.
- Cookieless Domains. Serve all of your static assets from cookieles domains. This one isn't neccessarily a major win, since the gain you get is reduced based as you optimize more. However, before I started optimizer, a single request would fire 80 HTTP requests just to static assets. The average user on the site would have about 500bytes worth of cookie data. I set myself the target of getting the request down to 400kb for everything. So like 2% (I can't do maths) of that was being taken up just on the users /GET Request. It's not neccessary! Move everything to a CDN. We use CloudFront.
- Browser Blocking. We don't have this issue any more, but we used to load a lot of assets. As a result we would hit "blocking" really quickly. (This is where the browser stops loading assets from the same domain). This number changes depending on the browser you are using, but the normal number is 6. (Some even as low as 2!). The iPhone is the only phone which is also around 6. Most other phone devices are still at 2. This means that requests to the same domain (where you have lots) really sucks. So as a possibility you might want to consider using an asset serving library. The idea of this is that you provide multiple links to the same assets in your application, and round robin through the sources to make sure that you are rotating between hosts where the asset can be found. If you do implement somethign like this you need to make sure that the user is served the same host otherwise they wouldn't benefit from caching. The "hack" way that we implemented it was to use their HEADERS as a "seed key" which created a cache, which we would then perform some type of modulus caclulation on to serve the correct server. It isn't perfect, but it works.
- Headers. Use correct headers.. expires... cache... if you are serving up cacheable content. Say so! Some study I read recently showed that over a period of a month, rechecking the same 1000 sites daily and checking each asset. Of those that never changed, over 50% didn't make correct use of expiry headers! If you do this you are telling your users that you don't care about performance!
- Speed isn't always about waiting for that DOMContentLoaded to fire. It's also about perceived performance and loading. This means that you should always give your images a height and width attribute too.
- Use a reverse proxy cache! The stack that we use is nginx(port 80) -> varnish -> nginx. This is so that we can benefit from server side includes which are cached in varnished, and rebuilt by nginx.
- Eager Loading. I've only just started experimenting with this as a way of improving my knowledge on machine learning. As I mentioned above we have a really load bounce rate, so it might make more sense for us than it does for your site. Basically, I've started using Markov Models to build a probalistic model of the liklihood of a user navigating to another internal page and figuring out based on all other users which page that might be. I then drip feed load the assets from the other page.
Can you elaborate a little more on the reverse proxy tip? Why the double nginx, varnish in-the-middle approach? I'm not familiar with this technique. The rest of your comment is really good.
Awesome list. I also do the following which seems to work well.
- Use Inject.JS from Linkedin. It allows you to cache the JS files in HTML5 local storage which is a huge deal for mobile clients in particular.
- Performance test your CDN. If you are only targeting users in a non US country then a CDN can be significantly slower.
- Make sure to split your domains e.g. images.x.com, css.x.com, js.x.com as it will allow your browser to download more concurrently.
- Look at OpenResty or nginx/lua combined with Redis as used wisely can do gamechanging things to your performance.
- Use Google Analytics Javascript timing to get a longer term picture of where you need to optimise.
- Be careful how you bundle. Many people may already have popular libraries like Google's JQuery version cached already. So consider splitting those out.
I'm surprised they left out concatenating your javascript files into as few files as you can without breaking caching. The concept is the same as using sprite sheets, with similar gains if you are using many different scripts.
The most important thing for page loading times is reducing the number of network roundtrips required to view a page. A visitor across the globe will have a latency of around 300ms per request. If you include a javascript that adds a link to a stylesheet, this means you have at least 3 serial requests, and the page loading time is now at least 1 second, no matter how large the bandwidth or how much you compress files.
This is also important on mobile. Mobile broadband is optimized for high throughput, but you often have very high latency. Therefore it's especially important to reduce the number of round trips.
With regards to Twitter/FB, frustratingly in my Webmaster tools it shows our homepage loads slowly, and a huge percentage of this loading time is actually the FB/Twitter buttons.
For users this doesn't impact their perceived loading time much at all so when Google reports it as a slower load time it's an unfair bias in my opinion.
It's important to get it fast as it's a well known fact they use it as a metric in their ranking algorithms. I've tried various ways of delaying it but nothing seems to stop it being counted in page load time tools:
We did something similar with KeepAlive on http://settleapp.com - we figured that a new user with an empty cache (it's just a holding site, so most users are first-time visitors) would need to load the index and 23 other assets.
We set keepalive to 24 requests over a single http connection - the whole site loads noticeably quicker as a result.
Your stylesheet is referencing "../img/.png", which clearly doesn't exist. 404s hurt a lot.
You are loading google analytics [1] and jquery synchronously at the top of the page. Moving jquery to right before </body> is advisable at the very least if you can't load it async for some reason, so that it won't block the rest of the page. [2]
Most of your static assets have no caching headers (max-age or expires) at all.
Your map image could be 128 color png (neuquant + pngout) and nearly 50% smaller with no discernable to the human eye loss [3], and similar can be said for the phone template [4].
Your background tile image is unnecessary, or at the very least could be a 1x1 instead of a 100x100.
All of these things will improve performance for everyone, and would have been better targets than messing with keepalive. There is also more left to do...
A somewhat silly tip: don't include the Google PlusOne button. Even when it's set to load after every other asset, and load asynchronously, it still takes ages to load. I've sometimes browsed off the page before it finishes loading.
80% of client side optimization can be done in 2 hours or less. Research shows time and time again that faster websites convert and retain vistors better. It's worth the 2 hours no matter what stage your project is.
I have to disagree with you there. Some optimization steps significantly hamper development and should be saved until near the end of the project. For example, JS minification is very fast and easy, but it makes debugging much more difficult.
And you shouldn't compile things into CSS sprites until you are fairly settled on what images you'll be using. If the look and feel of your site is still in flux, you're wasting time by making sprites.
Use automated tools to minify files on production but not on your development server or localhost.
I'd put CSS sprites into the 20% you do later. Some images are very simple to merge and don't change (such as icons) but the rest, yeah, wait until you sort it out.
>Use automated tools to minify files on production but not on your development server or localhost.
Yeah you can do that, but there are still points in the project where it makes little difference to do that, and it's not hard to get stuck in those points.
Of course, once there's wide support for source maps, you'll be able use minification even in development.
Considering how much most pages can be optimized, its a good tradeoff. How often do you refresh during development? How much time are you waiting for that reendering?
- If you have a lot of images like in a product catalog, try lazy loading the images - http://www.appelsiini.net/projects/lazyload
- For sprite-ing your images, use some tool which will automatically generate the css for you: http://spritepad.wearekiss.com
- Have a git commit hook for compressing images. (It could be part of your build, but then you are doing the work every time and the build will be slow if you have a lot of images.)
- If you are using LessCSS or something similar (hint: you should), have your compiler compress the css for you. - http://wearekiss.com/simpless
- Have multiple hosts referring to your CDN to facilitate parallel downloads in the browser.
- Compress your HTML too before deploying - http://code.google.com/p/htmlcompressor/
- Have proper versioning in the name of your JS/CSS files and then cache them for ever. Whenever you change your files, the version should change and only that file will be updated in the client. We normally take the hash of the latest git commit on that file:
- Offload whatever you can from your cookies to localStorage or things like that to reduce request size.