When CityEats.com by the Food Network begun it was treated much like a web site. I had inherited the front-end code from a truly talented dev I’d worked with before and respected. But the site had pivoted over the past 2 years into a multi-headed web app. Originally a Groupon competitor, it had gone through several iterations, landing more recently as an OpenTable competitor. It was in sore need of optimization and restructuring to aid desktop and mobile render speeds.

This post describes how I used Rails manifests to approach this restructuring, and reduced the initial homepage CSS foot print by 40% for desktop, and 23% for mobile. For the sake of brevity, I will round up file sizes.

This post also targets the homepage, but similar reduction across the site were experienced.

The Numbers

  • Desktop before: 74 KB, after: 29 KB
  • Mobile before: 496 KB, after: 114 KB

Whatever, show me.

I don’t care, why or what, just show me the code.

The History

There were certain assumptions made when the site was designed. It resembled Apple.com, at the time, in how it loaded it’s CSS and JS:

  • It pulled all of the CSS for the entire site on the homepage.
  • Mobile pulled all of the CSS for the desktop, and then applied mobile styles via media-queries

The latter was done, as it was decided, in tandem with the product owner that:

  • we didn’t want to spend extra time developing a mobile version, and
  • chances are it was faster to apply a bandaid to the original styles than to write mobile styles from scratch, or
  • have a duplicate but similar set of styles that would have to be maintained separately.

It quickly became apparent, that the styles for mobile on a good portion of the pages, were radically different, and when styles changed on the desktop often the product owner wanted mobile to remain the same.  So mobile became bloated with copied sections of old desktop styles, and more effort was put into maintaining a separate look while over-writing the desktop redesign.  Short story – it became a mess.

It also became apparent that the approach of downloading all of the CSS on the initial homepage hit wasn’t necessarily good for mobile speeds, nor the speed of the desktop homepage.  The original focus was to speed up the homepage, and limit that initial file hit. (A separate task involved spriting all images into a single file using Compass – but that’s another post.)  My goals were to try and find a way that we could cache what needed caching and pull only the files needed on the individual pages, and this, potentially would have a trickle down benefit for mobile.  My strategy became to find a way to separate groups of pages, and isolate some of the larger pages in the user flow – so that no one page pulled gobs of CSS that it didn’t use – but that we utilized caching when possible to speed up rendering. Some constraints that we faced:

  • Files were currently split somewhat ambiguously into 3 files. The reason for this was that I found an issue in IE when releasing our switch to Rails 3.0, and the asset pipeline.  Originally when I inherited the site it was making 8 HTTP requests for various CSS files (fonts, reset, global, public, and various vendor stylesheets). When switching to the asset pipeline, we went from 8 HTTP requests to 1 and quickly hit the stylesheet limits in IE 6-9 which basically stops parsing/rendering CSS at a specific selector per sheet (4095) in the CSS.
  • We weren’t going to do a deep redesign or refactoring of the CSS (to say responsive) as there was so much of it and we literally had one sprint to get something in place.

So what I settled on was keeping the 3 HTTP request.  I feel in some ways this could at some point be reduced to two.  But what I settled on was a default-base.css, a default-module.css and a default-page.css.

  • default-base would house reset, fonts, and global styles applied to all pages of the site.
  • default-module would house OOCSS for items used in multiple places (not all) in the site (review form styles, vendor styles)
  • default-page would house page-specific styles. The default would house those styles that applied to the smaller static pages. Everything else would be broken out into specific areas or pages. For example, the user profile area had it’s own file that addressed multiple pages, but the restaurant detail page, would pull CSS just for that page given it had a larger footprint.

This approach allowed us to overwrite the module manifest (this was decided to be overkill, given its footprint was pretty minimal) or page level manifests as needed.  The focus for this iteration was to tackle the page level.  In theory this would allow us to ditch the page-level ID that was applied to the body for each pages’ hook (necessary when the styles are in one file), simplifying CSS parsing – a further advantage.  The cons, we would need to maintain multiple manifest files for various areas/pages of the site.  Currently this meant maintaining separate manifests for each page or area.  I didn’t feel this was a disadvantage. I pitched it to the other 4 members of my front-end department, and my partner on the team and they felt this was a decent tradeoff.

The Code

Layout


-# stylesheets
- @base_css ||= "default-base"
- @module_css ||= "default-module"
- @page_css ||= "default-page"

!!!
%html.nojs
 %head
  = stylesheet_link_tag @base_css, :media => 'screen'
  = stylesheet_link_tag @module_css, :media => 'screen'
  = stylesheet_link_tag @page_css, :media => 'screen'

View

If supplying an over-ride in the view do the following, otherwise default would be pulled.

- @page_css = "page-restaurant-detail"

Manifests

default-base
/*
*=# BASE
*= require_self
*= require reset
*= require sass/global
*/
default-module
/*
*=# VENDOR / MODULAR STYLES
*= require_self
*= require sass/unbranded
*= require sass/reviews
*= require sass/pagination
*= require jquery.rating
*= require sass/jquery.tools.scrollable
*= require chosen
*= require sass/vendor-overrides
*/
default-page
/*
*=# page level CSS
*= require_self
*= require sass/public
*/

Page level styles

This is an example of what would replace page-default.css as needed.

/*
*= require_self
*= require sass/page-restaurant-detail
*/

The Results

Of course I didn’t record my findings when things were fresh in my browser, so I had to cull together several screenshots I’d taken when I was analyzing the file sizes, and also fire up the current version of the mobile site, to see the current state of the site. Also note, in the two versions of the desktop, one is showing the New York metro, and the other is showing the DC metro. But the styles pulled for both are the same. And I’m not looking at parsing, or render times – just file-size to keep apples and apples here.

File sizes before any optimization was done, but after Rails asset pipeline.

File sizes before any optimization was done, but after Rails asset pipeline.

After refactoring was applied, isolating HP styles, caching global and vendor.

After refactoring was applied, isolating HP styles, caching global and vendor.

This shows the file size pulled in the mobile.css file. It's pulling all of desktop and mobile on the initial HP view.

This shows the file size pulled in the mobile.css file. It’s pulling all of desktop and mobile on the initial HP view.

This shows the CE site today, 3 HTTP requests, but only pulling what's needed and caching globals.

This shows the CE site today, 3 HTTP requests, but only pulling what’s needed and caching globals.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>