Building off of my previous post on RWD Bloat, the following is step-by-step how I made my site faster over the course of a few days. For the purposes of this test, I’m hyper-focused on my Speed Index numbers for my Home and Article templates (the 2 most visited templates of my site). Here is my baseline:
||Home|Article| |PageSpeed (Mobile)|79|78| |PageSpeed (Desktop)|93|91| |Speed Index (3G Fast)|1446|1749|
Step 0. Normalize instead of CSS Reset
I made a quick unintended decision to use Node-Sass and Autoprefixer instead of Compass. I also did a quick refactor to “Normalize” instead of using CSS Reset. Curious to know the difference? Me too.
||Reset|Normalize| |CSS Selectors|679|578| |Home Speed Index|1446|1376| |Article Speed Index|1749|1412|
Bullshit! I lost 15% of my selectors by ditching CSS Reset and my Start Render time has dropped anywhere from 100ms-300ms.
Step 1. Unblock webfonts
I added Filament Group’s loadCSS to lazyload my webfonts instead of blocking them in the <head>
.1
||Webfonts in <head>
|Webfonts w/ loadCSS()
|
|Home Speed Index|1376|1327|
|Article Speed Index|1412|1284|
Faster! The downside there’s a pretty heavy FOUT on page loads. Browser gets the HTML, which requests the JS, which injects the CSS, which requests the WOFF… there is a webfont from cookie/localStorage trick, but that seems troublesome.
Step 2. Leaner JavaScript
Ugh. I really wanted this because “jQuery: Bad for Web Performance” would have been awesome HackerNews linkbait. Turns out, I’m more entrenched in jQuery-land than anticipated…
Step 3. SVG Spriting
I had 176 icons in my icon font, but I only use 6 icons sitewide: Home, Archive, About, RSS, Previous, and Next.2 Eventually I want to try putting logos into the same external sprite, reducing the document size, but am focusing an icon system replacement. IcoMoon allows you to download SVG Sprites, to use them, I just reference them like this:
<svg title="Home">
<use xlink:href="/images/spritemap.svg#icon-house"></use>
</svg>
This got me up and running quickly. I ran into a problem around viewBox
and <g>
elements, so I handedited the <defs>
block a bit, switching <g>
elements to <symbol id="icon-XXXXXX" viewBox="0 0 32 32">
3. I also added SVG4Everybody for IE9-11 and (r)Android phone support. The change?
||Icon font|SVG Sprite| |Home Speed Index|1327|1264| |Article Speed Index|1284|1222|
I’ll need to automate this in the future but that feels pretty good, man.
Step 4. CSS Cleanup
My CSS was in an awful state; incredible redundancy and quite possibly the worst filenaming scheme I’ve ever seen. It was my first Sass project. I cleaned up and made better CSS modules, variables to reduce random colors, and added a min-height to my Fusion Ad to prevent the layout from seizing.
After this and without the icon font, I’m down to ~172 selectors in CSS Stats. Let’s see how far good ol’ fashioned house keeping gets us.
||Old CSS|Refactored CSS| |Home Speed Index|1264|938| |Article Speed Index|1222|1126|
Three hundred points! Third-party content was going apeshit (also 28s to fully load a favicon?!?), but this validates to me that cleaning house can be one of the best things you can do for a project. Also saw the Speed Index dip below 1000. I’m nearly at my goal…
Step 5. Critical CSS
With CSS cleaned up, I can begin extracting my Critical CSS and putting that inline in the <head>
. Borrowing from Addy Osmani’s Critical, I came up with a quick Gulp task that would output the CSS into Jekyll’s _include/
folder for me:
var penthouse = require('penthouse');
var Promise = require("bluebird");
var penthouseAsync = Promise.promisify(penthouse);
gulp.task('critical', function(){
penthouseAsync({
url : 'http://daverupert.com/',
css : './stylesheets/style.css',
height: 480
}).then( function (criticalCSS){
require('fs').writeFile('_includes/critical.css', criticalCSS );
});
});
Now critical.css
is inlined and my stylesheet is being lazyloaded by loadCSS()
. I should probably do that uniquely for every template. I noticed I was using loadCSS()
for two separate CSS files, so I decided to include the contents of fonts.googleapis.com in my _typography.scss
. Those @font-face
rules get picked up by my critical CSS task so I need to hand-edit a bit.
||All CSS|Critical CSS| |Home Speed Index|938|735| |Article Speed Index|1126|1123|
There’s some serious blue-link FOUC happening, but the score is very good.4 Although I’m still not sub-1000 on the article page. Looks like third-party things are gumming me up.
Step 6. Final Boss Battle
In an attempt to thwart third-party scripts and win on that Article template, I tried a few things to squeeze out more performance.
- Reorder scripts to make Disqus last. No Improvement.
- Due to the exhaustive number of “Your webfont looks terrible on Windows7 Chrome” complaints I got this weekend, let’s try only webfonts for headings! Eases body text FOUC.
- Moved logos to the SVG sprite. Learned SVG Sprites don’t support linear gradients. Now I have a new Flat® logo. Massive Improvement, but…!!
At this point I ran my site through WebPageTest and PageSpeed and noticed my Homepage Speed Index was now over 1000, but my PageSpeed score had dipped from 100 to 74. GASP! Either PageSpeed Insights changed their algorithm, or now that my document is faster, it’s revealing a whole new set of problems. Back to the drawing board.
- Use Google’s inline rAF() snippet instead of
loadCSS()
. Minimal improvement. - Manually added rules to
critical.css
. Fixes FOUC. PageSpeed improvement. - Lowered Fusion Ad on homepage without breaking my TOS. PageSpeed improvement.
- Inline SVG’d above the fold vectors at top of document5. BINGO BANGO! Winning everything.
It appears the secret was putting a bit more into my first response.
Final Results
||Home:before|Home:after|Article:before|Article:after| |PageSpeed (Mobile)|79|98|78|96| |PageSpeed (Desktop)|93|98|91|97| |Speed Index (3G Fast)|1446|728|1749|1065|
I’ve nearly doubled the speed of my site. Not too shabby for a responsive design with jQuery, two webfonts, third-party ad, third-party comments, all while being tested on a 3G Connection. My Speed Index on Cable is currently 400~414 for both templates.
Hopefully that helps underline the point, as Scott Jehl so eloquently puts it , “How we load assets matters just as much as how many assets we’re loading.”
If any extreme perfinistas (or Googlers) have any quick tips on how to get that Article template to break the glass on 3G, I’d love to know them.
-
Note: Switching from CodeKit to Gulp for script concatenation, I switched from Minify to Uglify. Both files (the control & the experiment with loadCSS) ended up being ~101kb unzipped exactly. ↩
-
TODO: I could/should have made a 6 icon font as a control. Someone (not me) should test that. ↩
-
Warning: Ran
spritemap.svg
through SVGO thinking that would be awesome… well… it wasn’t. SVGO renamed all the IDs (#icon-house
→#b
) so it got non-semantic and hard to use. I’m sure there’s a setting, but hours lost there. ↩ -
Warning: Lazy-loading CSS breaks art directed posts. Will probably have to style off of post-ID. ↩
-
Note: Inline SVG had to be at top of document to render and pass WPT and PSI. ↩