Tuesday, January 22, 2013

How Much CSS Should You Have?

I've noticed recently that it's becoming more and more common to see websites with WAY too much CSS.   I realized that I've never seen specific guidelines around how much CSS a website should have, and no clear narrative around the impact of total CSS transfer size.  Most people just say "as little as possible" or "just write what you need and then optimize".  On the whole I think these statements are a bit too vague to be useful.  In addition, there are a lot of articles about "optimizing" CSS (a.k.a  writing more efficient selectors), which has quickly diminishing returns.  In this article I will talk specifically about how big your CSS files should be, and talk about some other possible pitfalls when authoring CSS.


The Bytes Matter Most

According to the HTTP Archive, the average website has 35KB of CSS, and CSS transfer size has the highest correlation to render time.  If you look at the graph directly above the one for render time at that link, you will also notice that total transfer size has the highest correlation to page load time.  This makes the case for reducing the overall weight of your CSS pretty strong.  Based on this data and what I've seen in my own work, you should shoot for less than 30KB of CSS on each page of your site (after gzip).  It is true that you should really try to drive this number as low as possible (without wasting your life trying to remove a couple of bytes), but if you have more than 30KB of CSS then it is likely that there is room for improvement with minimal effort.

What About Efficient Selectors?

This is one of the recommendations from the "Make the Web Faster" team at Google, but people often take it too far.  It is true that really bad selectors can torpedo page performance, but it is typically not worth it to go through and optimize every selector.  As long as you are avoiding the universal selector (*) and your DOM is a reasonable size (~2000 elements or fewer), you are better off focusing on removing CSS instead of optimizing selectors.  Deleting unnecessary CSS also has the added benefit of making your code more readable and maintainable.

How To Reduce CSS Size

I am assuming that you are minifying and gzipping your CSS, but if not then please go do that before reading any further.  Once that's done, I suggest watching this talk on the "Top 5 Mistakes of Massive CSS" from Velocity 2010 (video, slides).  I don't want to rehash the entire talk here, but in the interest of saving you some time the five mistakes are:
  1. To much granularity in your CSS (new CSS classes for every little thing)
  2. Stale rules (obsolete CSS)
  3. Unpredictability (inconsistent cascade causes people to write one-off solutions)
  4. Specificity wars (writing extra CSS to override inherited rules)
  5. Duplication (tons of margins/floats/font-sizes)
The talk goes into much more detail on each of these points, so I highly recommend at least flipping through the slides.  I really want to cover a different topic here, which is a new problem that I've seen crop up in recent months: improper use of CSS preprocessors. 

In case you aren't familiar with these tools, the website for LESS (one of the most popular preprocessors at the time of this writing) gives a good overview their benefits.   Unfortunately, it also clearly illustrates one of the dangers of using a preprocessor directly in its documentation.  Take a look at this example code:


This is great right?  Look at how easy it is to use the rounded-corners mixin!  In this example it looks like a boon for maintainability, without a huge bloat in the amount of CSS you have.  Unfortunately, this example is only using the mixin twice.  In a real world scenario, you might have a dozen instances of this mixin, each one generating 5 lines of CSS.  Multiply this by a few dozen mixins, and a basic LESS file that is only a few hundred lines could easily turn into an 80KB CSS file once it is compiled.  This problem could be avoided by using the OOCSS pattern and reusing the same CSS objects instead of repeating the same CSS declarations all over your stylesheet.

Let me be clear, I'm not against CSS preprocessors.  I think they have their place and can be extremely useful.  I just think that it's important to be wary of automatically generated CSS and the unexpected bloat that it can cause.  For example...

Basecamp's CSS

Basecamp is a piece of project management software that I've used in the past, and overall I think it's a good product.  The company behind it, 37Signals, has a popular blog where they have talked about their use of SCSS to speed up development.  I read the post and figured I would look at their compiled CSS (which they serve to every visitor) to see what it looks like.  For starters I ran a test via WebPagetest to get a sense of the size of their CSS:



271K of CSS?!  Holy crap!  That's almost eight times the average from the HTTP Archive.  When we look at the request for this CSS file we can see that the content download took over two seconds and blocked the rest of the page from downloading during that time.


If this doesn't make the case for a smaller CSS file, I don't think anything will.  I'm assuming that this contains all the CSS you would need to use the full version of Basecamp (since Basecamp is a one page app), but even so it's a huge file.  Let's look at it and see if we can figure out why it's so large...

The first thing that's obvious is that the file is not minified.  This is an easy fix, but according to PageSpeed it would only save about 4KB.  Next I ran it through CSS Lint, which turned up a ton of "Disallow overqualified elements" warnings, and when you look at the CSS it is easy to see that this is causing a lot of duplication:

body.extras section.apps article a.rightsignature {
    background: url("/assets/images/extras/rightsignature-x2.png") no-repeat;
    background-size: 60px; 
}
body.extras section.apps article a.dashstack {
    background: url("/assets/images/extras/dashstack-x2.png") no-repeat;
    background-size: 60px; 
}
body.extras section.apps article a.slickplan {
    background: url("/assets/images/extras/slickplan-x2.png") no-repeat;
    background-size: 60px; 
}
body.extras section.apps article a.quoteroller {
    background: url("/assets/images/extras/quoteroller-x2.png") no-repeat;
    background-size: 60px; 
}
body.extras section.apps article a.everest {
    background: url("/assets/images/extras/everest-x2.png") no-repeat;
    background-size: 60px; 
}
body.extras section.apps article a.feeds {
    background: url("/assets/images/extras/feeds-x2.png") no-repeat;
    background-size: 60px; 
}
... about 30 more of these ...

This is sometimes a symptom of using a preprocessor, and there's a lot of opportunity to refactor this CSS to reduce the duplication.  The "background-size" declaration and the "no-repeat" component value are repeated in every rule, so it makes sense to factor them out.  This pattern of repetition is common throughout this stylesheet, which is the main reason why it is so large.

There are other anti-patterns in this file, but I'll leave it as an exercise to the reader to identify them, and trust that you will believe me when I say that if you have 271KB of CSS after gzip then something is very wrong.

Recap

Total CSS size can have a huge impact on performance, and that's where you should focus your energy as opposed to optimizing individual selectors.  You should target something like 30KB of CSS on each page, and smaller is always better.  To get your CSS that lean you should start by leveraging the techniques that Nicole talked about at Velocity a couple of years ago.  If you are using a CSS preprocessor like LESS, then you should be extremely careful about what your mixins look like, and you should take a long look at the compiled CSS to ensure that you aren't introducing unexpected duplication.

Hopefully by following these tips you can have a faster site, more maintainable CSS, and happier users!

4 comments:

  1. I think is a particularly difficult topic to compare across the board. 30kb is, for all intents and purposes, an arbitrary number due to the different tasks that css files are able to carry out.

    For example with basecamp, they are giving their css a SHA1 and caching it indefinitely. So this hit is a first time thing. Then they are serving all their fonts in the css - all 110kb of them. Fonts as Data URIs do not gzip well so having them in the CSS is a big trade off against fewer requests but again that will only hit the user on initial load.

    I stripped the fonts out of their css and gzipped it to see the result. It came to 21k. Not bad at all - but at 4600 lines of css I'm certain there's still savings to be made.

    CSS blocks render and *is* a massive deal. I'm not gonna make excuses for basecamp's initial page load (which must be painfully slow on mobile) but I'm sure they will have their reasons for it and second guessing these decisions is very difficult unless you have the full story.

    ReplyDelete
    Replies
    1. Hi Ian,

      Thanks for the comment - this is a good discussion to have. I agree that 30kb is somewhat arbitrary, but it is at least a starting point to work from. I think it's fair to state that if you are in that ballpark then you are in decent shape, and anything that is way out of bounds needs an explanation.

      Basecamp is caching their CSS indefinitely, but every time they change it you have to download a new 250KB+ file. Caching helps a lot, but it shouldn't be used as an excuse to ignore first page load considerations. The file could easily expire out of a browser cache, especially on mobile where caches are much smaller (although they might be serving a smaller file to mobile devices, I didn't test that).

      I noticed the fonts in the CSS as well, and that is certainly contributing to the size, but there is still a ton of CSS without the fonts as you mentioned. On top of that, it might make sense for them to pull the fonts out and have them get cached separately if they change their CSS often. Of course it's very possible that they have tested this and it makes sense to inline them, which is why I didn't mention it in the article.

      The comment that "I'm sure they will have their reasons" is a dangerous one. There are certainly reasons why their CSS has so much duplication, but are they good reasons? This kind of audit is something that is worth doing, because it's possible that with a small amount of refactoring they could dramatically reduce the size of the file. I'm not trying to second guess any major decisions, rather I am just trying to point out that the end result is suboptimal. As a result I think it would make sense for 37Signals to take a look at the decisions that led to this end result, and see if there is room to improve.

      Delete
    2. You'd have to assume their is room to improve on their css for sure, even without knowing their architecture so well.

      Couple of points worth mentioning though:

      1. Duplication is such a small deal if you're gzipping. That is, from a performance point of view at least (it might be a maintainability nightmare for them, I don't know). 4600 lines is crazy, but 20k is not. That's not to say you shouldn't work to make it as lean as possible, and employ oocss practises, but the performance gains are likely to be less significant than expected.

      2. This file may never change. It probably does(!) but it may not. They may introduce page or section specific css files further down the funnel. Basecamp may not, again I don't know, but other sites that follow this pattern may. We do on lonelyplanet.com for example (http://www.lonelyplanet.com/singapore/singapore-city/hotels?_l=core) where we house our non-changing platform css in a core css file and a separate application file.

      So yeah, suboptimal. Agreed. But, as with most things in performance, it's a balancing act.

      Delete
    3. Regarding gzipping and duplication - I agree that gzip dramatically improves the situation, since it is a token based compression algorithm and duplicated sequences get optimized out to a large degree. That being said, it shouldn't be an excuse to completely ignore duplication in your CSS. Even if you only save 5KB of CSS by refactoring, if that refactor only takes a few hours and you are working on a high traffic site then it's worth doing.

      Your second point is relevant, but we're getting into pure speculation when we start talking about how often the file changes. It's important to remember that this post is NOT about Basecamp, it is about best practices and general guidelines for authoring CSS. I used Basecamp's CSS file as an example, and as a way to demonstrate some risky patterns. At the end of the day it doesn't really matter that much how many bytes we could save in this specific CSS file - it's the concepts that are important.

      Delete