Bootstrapping Fast Asynchronous Websites for 14 kB Roundtrip

Basically the point is to serve as much of the page content in the first 14kB roundtrip. It’s done by inlining critical CSS and JS (I think even adding lazy loading script, polyfils, etc. can be critical JS) into the HTML.

But you want to use best-practice:

Part 1: How to start

  1. Go for the HTML5 boilerplate
  2. Remove everything unnecessary from HTML
  3. Inline the Normalize.css
  4. Inline Modernizr.js as distributed with H5BP

1.1 The First Results

Size of response is 24120 bytes (23.9kB) and looks something like this: https://gist.github.com/attitude/87b704afb29152473d34

1.2 Minify CSS + remove every test from Modernizr.js to see posible savings

  1. I’ve minimified Normalize.css
  2. Removed every test from Modernizr.js (now it might be silly to include, I know, just because of the no-js class removal and HTML5 Shiv)

Such response is 10953 bytes (characters) long, roughly 10kB: https://gist.github.com/attitude/38277081a836bc856036

Now you have 4kB of content to add. The ratio of code:content would be 10:4. Not much, but still better than most of the websites out there.

1.3 Remove Modernizr.js

I’ve walked the src of Modernizr.js and extracted the .no-js goodie CSS class conditional hack based on Paul Irish idea.

(function(d) {
var c=d.className,reJS=new RegExp('(^|\\s)no-js(\\s|$)');
c=c.replace(reJS, '$1js$2');
d.className=c;
}(document.documentElement));

Result is only 3.3 kB long:

<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="apple-touch-icon" href="apple-touch-icon.png">
<!-- Place favicon.ico in the root directory -->

<style>/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;}body{margin:0;}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block;}audio,canvas,progress,video{display:inline-block;vertical-align:baseline;}audio:not([controls]){display:none;height:0;}[hidden],template{display:none;}a{background-color:transparent;}a:active,a:hover{outline:0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}dfn{font-style:italic;}h1{font-size:2em;margin:0.67em 0;}mark{background:#ff0;color:#000;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{border:0;}svg:not(:root){overflow:hidden;}figure{margin:1em 40px;}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}pre{overflow:auto;}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em;}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0;}button{overflow:visible;}button,select{text-transform:none;}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}button[disabled],html input[disabled]{cursor:default;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}input{line-height:normal;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;padding:0;}textarea{overflow:auto;}optgroup{font-weight:bold;}table{border-collapse:collapse;border-spacing:0;}td,th{padding:0;}</style>
<script type="text/javascript">
(function(d) {
var c=d.className,reJS=new RegExp('(^|\\s)no-js(\\s|$)');
c=c.replace(reJS, '$1js$2');
d.className=c;
}(document.documentElement));
</script>
</head>
    <body>
        <!--[if lt IE 8]>
            <p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
        <![endif]-->

        <!-- Add your site or application content here -->
        <p>Hello world! This is HTML5 Boilerplate.</p>
    </body>
</html>

Further minifying results in 3.2kB content length leaving room of nearly 11 kB for the content of page (requested resource). This looks promising.

Part 2: Lazy Load the Rest

2.1 Snippet to lazy load any script

This lazy-load script creates a script element and injects it into DOM. The inserted script executes after insertion.

<script>
window.loadRemoteScript = function(url) {
    var element = document.createElement("script");
    element.src = url;
    document.body.appendChild(element);
};
</script>

2.2 Snippet (paired) to track load time to put after <head>:

<script>window.timeStart = Date.now();</script>

2.3 Snippet (paired) to track load time to put before </body>:

<script>
(function(w){
    var delta = Date.now() - w.timeStart;

    console.log(delta); // Remove in production

    // Fast internet, takes less than 1s to load
    if (delta <= 1000) {
        // lazy-load any concatenated script, even Angular/Ember/other app script, etc.
    }
}(window));
</script>

Most of it can be combined (& minified later)

Final Boilerplate

<!doctype html>
<html class="no-js" lang="">
    <head>
        <script>window.timeStart = Date.now();</script>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title></title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="apple-touch-icon" href="apple-touch-icon.png">
        <!-- Place favicon.ico in the root directory -->

        <style>/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;}body{margin:0;}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block;}audio,canvas,progress,video{display:inline-block;vertical-align:baseline;}audio:not([controls]){display:none;height:0;}[hidden],template{display:none;}a{background-color:transparent;}a:active,a:hover{outline:0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}dfn{font-style:italic;}h1{font-size:2em;margin:0.67em 0;}mark{background:#ff0;color:#000;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{border:0;}svg:not(:root){overflow:hidden;}figure{margin:1em 40px;}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}pre{overflow:auto;}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em;}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0;}button{overflow:visible;}button,select{text-transform:none;}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}button[disabled],html input[disabled]{cursor:default;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}input{line-height:normal;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;padding:0;}textarea{overflow:auto;}optgroup{font-weight:bold;}table{border-collapse:collapse;border-spacing:0;}td,th{padding:0;}</style>
        <script type="text/javascript">
        (function(d){
            var c=d.className,reJS=new RegExp('(^|\\s)no-js(\\s|$)');
            c=c.replace(reJS, '$1js$2');
            d.className=c;
        }(document.documentElement));
        </script>
    </head>
    <body>
        <!--[if lt IE 8]>
            <p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
        <![endif]-->

        <!-- Add your site or application content here -->
        <p>Hello world! This is HTML5 Boilerplate.</p>
        <script>
        (function(w, d, c){
            var delta = Date.now() - w.timeStart;

            // Creates script element and injects it into DOM
            w.loadRemoteScript = function(url) {
                var e = d.createElement("script");
                e.src = url;
                document.body.appendChild(e);
            };

            c.log(delta); // Remove in production

            // Fast internet, takes less than 1s to load
            if (delta <= 1000) {
                // lazy-load any concatenated script, even Angular/Ember/other app script, etc.
            }

            loadRemoteScript('/js/modernizr.min.js');
        }(window, document, console));
        </script>
    </body>
</html>

Known issue: Using .no-js snippet together with Modernizr results in doubled .js class: <html class=" js js ...">

Let me know what you think.