Extreme CSS Optimization

In which I use some unorthodox and even unwise techniques to make this site’s CSS as small as possible.
Stare at CSS long enough, and you can see into the Matrix(Markus Spiske)

One of the best things to ever happen to websites was when developers realized that there should be a separation between their sites’ content and how that content was presented. This really took off when developers began embracing CSS to style and lay out their websites, rather than use HTML-based solutions like nested tables, framesets, and spacer GIFs.

This was often frustrating when CSS was in its infancy and possessed only rudimentary features (and browsers were wildly inconsistent in what they supported). But nowadays, CSS boasts widely supported features like flexbox and CSS grid, web fonts and improved typographic controls, and visual effects including gradients and shadows. You can even vertically center elements with just a few lines of code! We are truly living in a golden age, my friends.

However, that same power and flexibility makes it easy for CSS to be misunderstood and misapplied. Smart, well-intentioned developers can easily end up with complex, bloated CSS full of unnecessary, redundant code — especially if they’re working on a large project and/or in a collaborative environment.

Furthermore, CSS frameworks like Bootstrap and Foundation can tempt developers to take a lackadaisical approach to their CSS implementations, which also results in bloated sites. (To be clear, this is due more to developer ignorance and laziness than any fault of the frameworks themselves.)

Thus, numerous strategies and techniques now exist to improve CSS efficiency and performance, including:

  • Naming schemes and coding styles (e.g., BEM, OOCSS, SMACSS) that enforce coding consistency and reduce redundant code
  • Utility class frameworks (e.g., Tailwind, Tachyons) that break CSS into discrete, single-purpose rules that can be applied as needed
  • Suggestions for improving how text is rendered and web fonts are loaded in order to reduce unsightly design reflows
  • The use of “critical CSS” to inline the most important styles so they take effect as soon as the page loads
  • Web standards like “preload” that tell browsers to load important resources (like stylesheets) before other resources

Furthermore, certain best practices have become widely accepted, from how you should name classes to the types of selectors you should use. All of this is excellent stuff and any web developer worth their salt — front-end or otherwise — ought to learn as much as possible about these things.

But when it came the latest iteration of Opus, I wanted to try something a little different. I wanted to see how far I could go in terms of optimizing my site’s CSS if I tossed aside certain best practices and instead, just focused on making the CSS as small and lightweight as possible.

This is nothing new, of course. Contests like 10K Apart challenge developers to create smaller websites with minimal code. But how would such an approach scale for a fully-featured, content-rich website with different layouts (e.g., article list pages, single articles, single articles with full-screen banner images)? And furthermore, would it require sacrificing any aesthetics or making any design compromises?

Before I break down what I did and why, here’s the result of my efforts: this site’s CSS currently weighs in at around a whopping 6.5 to 7 kb (minified), depending on what page you’re on (more on this later). This was made possible by a few, perhaps unorthodox and even unwise, approaches.


1) Shorten and Abstract the Crap out of My Class Names

It’s a safe bet that most of a given site’s CSS is composed of numerous class selectors. Classes are arguably the workhorse of CSS, since they can be applied to multiple elements on any given webpage. There are many recommendations for how to best name your classes. Regardless of how you name your classes, though, it’s easy to end up with long, overly verbose class names, especially when using a naming scheme like BEM:

  • page-header__links--alt
  • nav__logo--mobile
  • card__image--circle

While such class names may be useful in a “normal” project — for starters, their verbosity makes it fairly easy to guess how and where they’re used — they were simply too long for my purposes on Opus. So I decided to make my classnames as abstract and short as possible. For example:

  • entry became e
  • entries became es
  • entries--list became esL
  • gallery became glry
  • has-overlay became hasO
  • has-overlay--search became hasOS

And so on. As you can see, I kept some of the BEM-inspired naming through the use of camel case. If something was a container for numerous child elements, as was the case with the “entries” class, I simply made it plural (e.g., “es”). And if I could shorten a class name while still leaving it half-way comprehensible (to me, anyway) by removing all vowels or something similar, I did it.


2) Make the CSS Atomic

Utility classes (also known as “atomic CSS”) have caused no small controversy in web development circles. While some notable figures hate the idea of utility classes, I think they’re pretty cool and useful. For starters, they make it easy to build robust prototypes in a short amount of time. I frequently use them to set baseline styles that get me 80% of the way to where a component should look, and then I use more specialized classes for the finishing touches.

So for Opus, instead of adding display: flex; to a bunch of elements to make them flexbox elements, I created a utility class called flx (in keeping with #1 above) and applied it as needed to my HTML. Here are some of the utility classes used by Opus’ current design:

.flx {
    display: flex;
}

.flxWrp {
    flex-wrap: wrap;
}

.flxCol {
    flex-direction: column;
}

.lh1 {
    line-height: 1;
}

.wh100 {
    width: 100%;
    height: 100%;
}

Utility classes can get a little tricky with media queries — e.g., when you need an element to be display: block; at one screen size and display: flex; at another — but I was able to avoid that with this design. With a different design, though, I might need to rethink that. If/when that happens, then I’d use the “responsive suffixes” approach that Harry Roberts outlines in this excellent article.


3) Refactor, Refactor, and Refactor Some More

I think I’ve spent as much time refactoring this site’s CSS as I did writing the original CSS, if not moreso. And every time, it seems, I find something that can be rewritten — or better yet, removed — to further optimize the CSS.

One thing that I’ve become particularly unafraid of doing is breaking apart styles if it makes sense. Consider the following CSS:

.foo {
    position: absolute;
    width: 300px;
    height: 300px;
    border: 3px solid red;
    box-shadow: 0 0 3px 0 rgba(0,0,0,.5);
}

.bar {
    width: 300px;
    height: 300px;
    border: 3px solid blue;
    box-shadow: 0 0 3px 0 rgba(0,0,0,.5);
}

Through refactoring, I might rewrite those styles in order reduce the number of duplicate declarations:

.foo {
    position: absolute;
    border-color: red;
}

.bar {
    border-color: blue;
}

.foo,
.bar {
    width: 300px;
    height: 300px;
    border-width: 3px;
    box-shadow: 0 0 3px 0 rgba(0,0,0,.5);
}

If I were feeling particularly ambitious, I might even replace that last rule set’s multiple class selector with a single utility class, especially if I thought that particular group of styles might be applied to more elements in the future.

This approach can make it harder to manage the CSS in some regards. If I want to remove a class, for example, I may have to remove more instances of it than I would had I not broken it apart in the first place. But the grouping of similar styles can also make it easier to manage in some ways, like helping to reveal where I’ve written redundant styles.

Also, there were plenty of times where breaking apart classes actually increased the size of the CSS, if only marginally. In those situations, I usually left the classes alone. In other words, if being inconsistent meant a decrease in filesize, I was fine being consistently inconsistent.


4) Use System Fonts

Hundreds of excellent, good-looking web fonts are now available for web developers to use, many of them free due to Google Fonts. (I’m pretty partial to Alegreya, myself.) And the future of web typography continues to look bright with the advent of, and growing support for, variable fonts. But for all their benefits, web fonts still come with some performance trade-offs:

  • They can represent a new set of HTTP requests
  • They add to the overall filesize of a page (especially if you’re using lots of them and/or lots of weights and variants)
  • Even quick-loading fonts can cause unsightly flashes of unstyled content or design reflows

The easiest way to circumvent this is to use the fonts that come with operating systems. Since they’re already on the computer, there’s nothing to load, hence no performance hit, flash, or reflow. In the past, this meant using “browser-safe” fonts like Georgia, Trebuchet MS, and Verdana that were guaranteed to be on most computers. More recently, developers have begun using system font stacks.

I’m using something in-between for Opus. This is my current font stack:

font-family: 'Avenir Next','Segoe UI',sans-serif;

While it’s technically not MacOS’ system font — that’d be San Francisco, if you’re curious — Avenir Next has been been installed on Macs since at least OS X 10.10 (“Yosemite”). After using Futura for years at my day job, I thought I’d never use another geometric sans serif font ever again. However, there’s something warm and approachable about Avenir Next that immediately endeared it to me.

Segoe has been Microsoft’s system font since Windows Vista. Segoe UI is a sub-font designed specifically for user interfaces.

People not using a Mac or Vista will see their platform’s default sans-serif font. If they’re still using Windows XP, for instance, they’ll probably see Arial while Ubuntu users will probably see Ubuntu.

In any case, since no web fonts are being loaded, the website’s text will look the way it’s supposed to immediately, with no flash or reflow.


5) No Love for Older Browsers

It’s always a nice feeling when you can stop supporting an older browser. So for this latest design, I’ve basically stopped supporting IE altogether. Looking at the analytics, I saw that practically nobody uses IE 10 to access Opus anymore and that for the last three months, IE 11 accounted for only 1.2% of the site’s traffic (and IE 11 traffic has been steadily decreasing).

Among other things, this allowed me to leverage custom properties (aka, CSS variables) to further reduce the size of the site’s CSS, as well as CSS grids to achieve certain aspects of the layout.

You should always consult your own analytics before dropping browser support, of course. You may very well find that IE9 accounts for a significant amount of your site’s traffic. If that’s the case, then a) you have my deepest condolences, and b) you’ll need to make very different decisions regarding the CSS that you (don’t) use.


6) Inline All the Things!

Arguably the most common way to add CSS to a page is to link to an external stylesheet:

<link rel="stylesheet" href="path/to/stylesheet.css">

There are definite advantages to this approach: not only does it reinforce the content/presentation separation, but browsers can cache CSS files, meaning subsequently visited pages will load faster.

On the downside, external CSS files can be render-blocking, meaning a webpage will stop rendering until the browser has fully download any external CSS files. Which means that if you’ve got a lot of CSS files and/or big CSS files, they can be a noticeable drain on website performance.

However, there’s an easy solution to this: inlining “critical” styles by using the <style> tag in your site’s <head>. Inlining means that the browser never has to make HTTP requests for those styles, and the fastest kind of request is no request at all. (By “critical styles,” I mean any CSS that’s used to style “above the fold” elements and content, i.e., the stuff that a user can see in their browser’s viewport without having to scroll.)

There are some downsides to this approach, though.

First, inlined CSS adds to the size of every webpage that it’s on, unlike an external file which can get cached by the browser. Which means that if you shove a bunch of CSS into your <head>, especially lots of non-critical CSS, you’ll be adding more (and potentially, unnecessary) weight to your site’s pages.

Second, some “critical CSS” approaches require clever JS (e.g., loadCSS) to load any non-critical CSS asynchronously (meaning the web page continues rendering while waiting for the rest of the CSS to load). And while such JS is usually pretty small, it does add another dependency to your site.

But in Opus’ case, neither of those two downsides were applicable. Because the overall CSS is so small to begin with, I had no worries that placing all of it in the <head> would add too much to the overall weight of any given page, and doing so removed the need for any JS or asynchronous loading.


7) Break It Up All Over Again

Since I wanted my CSS to be extreeeeme, plain ol’ inlining wouldn’t be good enough. I took the additional step of breaking up the site’s CSS so that I could inline only those styles necessary for the current page. I use Twig for my site’s templating language so I created several partials — one for “critical” styles that appear on most, if not all, pages; one for banner layout-specific styles; one for the archive-specific styles, and so on — and then included the right partials in the <head> of any given page or layout.

This approach isn’t 100% bulletproof — on any given page, some unnecessary styles probably still get loaded — and given the total size of the site’s CSS, probably wasn’t really necessary in the first place. Breaking up the CSS to be more page-specific is better in theory than practice — for this site, anyway — as it likely doesn’t contain major performance improvements in and of itself. If anything, it actually makes it harder to work with the CSS, since it’s now spread out across multiple files that are brought together only on render.

But remember: this whole “extreme CSS” exercise was never really about what’s necessary or practical, but rather, about how far I could go down this particular rabbit trail.


We’re Not Done Yet

As I mentioned earlier, each round of refactoring seems to reveal yet more ways to streamline or shrink the site’s CSS. (Indeed, after one particular round of refactoring, I had to rewrite significant portions of this article since they were no longer relevant.)

Refactoring is actually kind of fun, albeit in a very nerdy way. I feel like I’m always learning something new about the site even after it seems finished, which keeps the design feeling fresh and dynamic. And I think there are still ways to push the CSS even further.

I’m sure I could abstract my class names, etc., even more than they already are — though at some point you hit the point of diminishing returns. But again, this whole exercise isn’t necessarily about what’s practical. Which brings me to my final point.


YMMV

I’m sure that some of you reading this have been rolling your eyes the whole time, saying “that’s not how you CSS” and listing all of the things you’d do differently. And here’s the thing: I don’t really disagree with you.

The approaches and techniques that I’ve listed above — e.g., abstracting your class names, breaking up your CSS — really make sense only because of the small scope involved. I’d be an idiot to use them on something larger than a personal site that I just so happen to have complete control over.

I’d be an even bigger idiot to use them on a collaborative project. There have been times when I couldn’t, for the life of me, remember what a particular class did and its shorter, more abstract name was no help at all in discerning its purpose or function. Instead, I had to spend several minutes rooting around my templates, trying to rediscover what it did from context. I shudder to think how others would fare if they had to work with the code that I’ve knowingly and willingly created here.

That’s not to say there’s nothing here that can be applied to other projects. At the very least, there’s tremendous value in refactoring, in frequently second-guessing and challenging what you’ve written. You should never assume that your code is finished or that it’s the best it can possibly be, and that goes for CSS, JS, HTML, PHP, or any other language as far as I’m concerned. And using system fonts and inline CSS (though maybe not to the lengths described above) are perfectly legitimate approaches… if they make sense for your particular situation.


High speed internet connections are becoming increasingly prevalent, and browsers and devices are growing more powerful, but those aren’t reasons to stop striving to optimize your CSS. Combining, minifying, and gzipping your stylesheets can have some surprising benefits, but they’re just the tip of the iceberg. Spending even just a little time rethinking and refactoring your CSS, removing unnecessary and redundant styles, and/or experimenting with modern techniques will do even more to improve a site’s performance (and increase your developer skills).

Though what I’ve outlined above may be impractical in many situations, it’s still driven by a desire that all web developers should have — the desire to make their websites as performant and efficient as possible.

Enjoy reading Opus? Want to support my writing? Become a subscriber for just $5/month or $50/year.
Subscribe Today
Return to the Opus homepage