

How we made our home page load faster - skrish
http://blog.deskaway.com/how-we-made-our-home-page-load-faster

======
chrisacky
Few of my own tips, most of which you covered.

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

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

~~~
jamiesonbecker
My guess would be SSL. Varnish doesn't handle SSL directly.

------
niyazpk
Here are a few that were not mentioned:

\- 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:

    
    
       cookie-wrapper.js -> cookie-wrapper.bb9303b809210674.js
    

\- Offload whatever you can from your cookies to localStorage or things like
that to reduce request size.

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

~~~
chriskelley
I believe the lessc compiler requires a node environment and simpleless is
self contained.

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

Google also provides a Page Speed tool
(<https://developers.google.com/pagespeed/>) (also built into Chrome) that
will point out any mistakes you may be making.

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.

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

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

[https://developers.google.com/speed/docs/best-
practices/rend...](https://developers.google.com/speed/docs/best-
practices/rendering#SpecifyImageDimensions)

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.

------
jreposa
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 */ });

~~~
niyazpk
We do something like this:

    
    
      $(function(){
          setTimeout(function(){
              /* load twitter, facebook */
          }, 2000);
      });
    

This tries to make sure that all the essential work to be done on the page is
completed before the less important Twitter, FB buttons are loaded.

~~~
projct
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().)

Timeout hacks are not a performance improvement.

[1] <http://api.jquery.com/ready/> [2]
[http://stackoverflow.com/questions/5263385/jquery-
multiple-d...](http://stackoverflow.com/questions/5263385/jquery-multiple-
document-ready)

~~~
bentlegen
$(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.

[http://stackoverflow.com/questions/4395780/difference-bw-
onl...](http://stackoverflow.com/questions/4395780/difference-bw-onload-and-
document-readyfunction)

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

[http://www.allbuttonspressed.com/projects/django-
mediagenera...](http://www.allbuttonspressed.com/projects/django-
mediagenerator)

~~~
ErikD
Keep in mind that serving already properly minimized png, jpg and gif images
with gzip can actually increase their size.

------
huskyr
Nice overview, for even more tips on performance the best resource is Steve
Souders' blog:

<http://stevesouders.com/>

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

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

------
dshearmur
Kind of looks like you're loading jQuery and not using? That's a good chunk of
bandwidth and execution time there!

------
TomGullen
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:

[http://stackoverflow.com/questions/9502972/stop-pingdom-
coun...](http://stackoverflow.com/questions/9502972/stop-pingdom-counting-
social-media-in-website-speed-report)

~~~
jcampbell1
Wait for a scroll or mousemove event after window.load

Google's crawler doesn't trigger events, but it does run the js past
window.load. If page speed just waited for window.load, it would be useless.

------
whackberry
How we trimmed 2 seconds from page load times: remove AdSense.

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

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

Hope this helps. :)

[1]
[https://developers.google.com/analytics/devguides/collection...](https://developers.google.com/analytics/devguides/collection/gajs/#quickstart)

[2] <http://developer.yahoo.com/performance/rules.html#js_bottom>

[2] <http://dl.dropbox.com/u/571990/settle/map.png>

[3] <http://dl.dropbox.com/u/571990/settle/phone_template_bg.png>

~~~
wgx
Wow this is great stuff. Some of this I knew, but some is new to me. Thanks!

On [2] the map pins are noticeably poorer - but this could probably be
addressed.

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

------
K2h
I'm not a web designer, but to me 400kB sounds like a huge amount of data.

------
iamgopal
ah, I so much wish that my app reaches "optimization" stage.

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

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

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

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

