Techblog

All about web

Optimising website above the fold content

1 year ago · 6 MIN READ
#frontend 

Above the fold content is how you make your first impression with your users. Make sure it is lasting one.

One of the most common enemies of website is slow network and it is always a challenge to avoid long loading time and all users can see is a blank screen for several seconds, remember every extra second of wait is an opportunity of conversion wasted. However, if above-the-fold content (critical path) is properly prioritized and all the unnecessary content is loaded without blocking the rendering it cannot only provide great speed but subtle UX; as users will not see the blank page for long and in best cases will not see it at all.

Before continuing must read basic frontend optimization guide.

Background

Before the browser can render a page it has to build the DOM tree by parsing the HTML markup. During this process, whenever the parser encounters a script it has to stop and execute it before it can continue parsing the HTML. In the case of an external script the parser is also forced to wait for the resource to download, which may incur one or more network roundtrips and delay the time to first render of the page. The above the fold content of your website plays a pivotal role in driving your user traffic. With the need for speed in page loading its also important to know what to load and when to load it. The general thumb rule is to load above the fold content in a single request. There are various ways to do that the most elementary one is to inject all the resources required to render the above the fold content in the HTML itself, as easy it may sound it will require quite some work to achieve that, especially if you already have an existing website which loads multiple resources from external servers like fonts, css, jquery, and other javascript libraries. Let us discuss some of the best practices to rock your website's above the fold.

Identifying render blocking content

This is the most important part of the study, it is important to know what is the essential part of above the fold content, it will help us to efficiently improve the overall speed of the website. Once the critical path is identified the rest of the resources should be deferred to load after the page load is completed. You can use google pagespeed insight tool to check what are the resources that are causing render blocking.

Critical and non-critical CSS

first the critical path css should be separated and inlined into the HTML itself so that it becomes part of a single request. You can either manually identify and separate them or use some packages that will do it automatically, like:

Online tool criticalpathcssgenerator Using this tool you can build the critical path from the complete css file. The generated critical path can then be inlined into the html <style> block and rest of the complete css file can be loaded asynchronously.

Node module Critical Critical node module setups end to end and fully automated solution for generating critical path css. It can be implemented easily.

Install

$ npm install --save critical

Initialize

var critical = require('critical');

Usage

critical.generate({
    // Inline the generated critical-path CSS
    // - true generates HTML
    // - false generates CSS
    inline: true,

    // Your base directory
    base: 'dist/',

    // HTML source
    html: '<html>...</html>',

    // HTML source file
    src: 'index.html',

    // Your CSS Files (optional)
    css: ['dist/styles/main.css'],

    // Viewport width
    width: 1300,

    // Viewport height
    height: 900,

    // Target for final HTML output.
    // use some CSS file when the inline option is not set
    dest: 'index-critical.html',

    // Minify critical-path CSS when inlining
    minify: true,

    // Extract inlined styles from referenced stylesheets
    extract: true,

    // Complete Timeout for Operation
    timeout: 30000,

    // Prefix for asset directory
    pathPrefix: '/MySubfolderDocrot',

    // ignore CSS rules
    ignore: ['font-face',/some-regexp/],

    // overwrite default options
    ignoreOptions: {}
});

more explanation here.

Laravel Package critical-css For more sophisticated websites built with Laravel, this package provides an end to end solution from building critical-path css to inlining it to the view. It's built on top of Laravel as a wrapper to Critical package. Here are the basic steps you can follow to get started with criticalcss in a Laravel Project:

Install npm package:

$ npm install critical --save

Install Composer package (composer.json)

$ composer require krisawzm/critical-css
composer install

Setup Service Provider

Add the following to the providers key in config/app.php:

'providers' => [
    Alfheim\CriticalCss\CriticalCssServiceProvider::class,
];

To get access to the criticalcss:clear and criticalcss:make commands, add the following to the $commands property in app/Console/Kernel.php:

protected $commands = [
    \Alfheim\CriticalCss\Console\CriticalCssMake::class,
    \Alfheim\CriticalCss\Console\CriticalCssClear::class,
];

Prepare config file Generate a template for the config/criticalcss.php file by running:

$ php artisan vendor:publish

To generate criticalcss use command php artisan criticalcss:make and to inline the generated style in a view specify the @criticalcss directive provided by the CriticalCssServiceProvider as below:

<html>
<head>
  ...
  @criticalCss('pages/route')
</head>
</html>

In some of the special cases you might notices the criticalcss package may generate duplicate styles, if the input array for $cssPaths contains files with duplicate styles. Which is common when you compile css for production with some task runner like gulp. If you have 5 compiles css files which contains same styles in each file and it is part of above the fold content, the critical path generated will have these styles duplicate 5 times. In order to resolve this issue I created a fork to the above repository here: https://github.com/rd4704/critical-css. This allows users to specify a single css file that you would like to use for a particular laravel route. On top of removing duplicates it also helps to build critical path faster as it has to build css from much smaller stylesheet. To use this fork you can update your composer.json file as following:

composer require: "krisawzm/critical-css": "dev-master"

and specify the VCS to my repository like this:

"repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/rd4704/critical-css"
        }
    ],

What to do with non-critical css?

The non criticalcss should be loaded asynchronously to trigger download only after the DOM processing is complete. The google's recommended way to do so is discussed here. It can be implemented as follows in a Laravel Project as suggested here: https://github.com/filamentgroup/loadCSS.

Asynchronously loading Javascript and jQuery

Javascript undoubtedly creates the most overhead when it comes to rendering the page, especially when the website is built with jQuery. If you have built your javascript code based on jQuery it becomes difficult to asynchronously load the jQuery as the scripts depending on it will break while rendering the page. But there is a work around for that, this article explains neatly different ways to asynchronously loading jQuery. My preferred way is to asynchrobously load external jQuery resource and check if it's loaded and only then execute my jquery dependent code as follows:

<script async src="https://goo.gl/6IkjYe"></script>

// loaded anywhere on the page asynchronously.
(function jqIsReady() {
    if (typeof $ === "undefined"){
        setTimeout(jqIsReady,10);
        return ;
    }
    var async = async || [];
    while(async.length) { // there is some syncing to be done
        var obj = async.shift();
        if (obj[0] =="ready") {
            $(obj[1]);
        }else if (obj[0] =="load"){
            $(window).load(obj[1]);
        }
    }

    async = {
        push: function(param){
            if (param[0] =="ready") {
                $(param[1]);
            }else if (param[0] =="load"){
                $(window).load(param[1]);
            }
        }
    };
        /* your jquery dependent code here */
})();

This can easily be added to your gulp task using gulp-wrap package which automatically takes care of any new code you may add in future. You can pipe gulp-wrap with this code as below:

return gulp
.pipe(wrap('(function jqIsReady(){if(typeof $==="undefined" || typeof jQuery==="undefined")' +
            '{setTimeout(jqIsReady,10);return;} var async=async || []; while(async.length){var obj=async.shift(); ' +
            'if (obj[0]=="ready") { $(obj[1]); } else if (obj[0] == "load") { $(window).load(obj[1]); } } ' +
            'async = { push: function (param) { if (param[0] == "ready") { $(param[1]); } ' +
            'else if (param[0] == "load") { $(window).load(param[1]); } } }; \n<%= contents %>\n })()'))

read more about gulp-wrap here.

Rest of the non critical javascript can be async or deferred as following: Async: <script async src="script.js">Defer <script defer src="script.js">

Read more about async and defer here.

Loading iframes

Iframes are render blocking resources, if they are not handled well it can make the page keep loading for forever, as the browser will keep waiting until the iframe is completely loaded. So, best practice will be to hack the way and tell browser to load it in background and do not wait for the iframe unless they are needed to be loaded with page load. It can be done by loading the iframe after page load completes, as below:

<script>
    (function (d) {
        var iframe = d.body.appendChild(d.createElement('iframe')),
            doc = iframe.contentWindow.document;
        doc.open().write('<body onload="' +
            'var d = document;d.getElementsByTagName(\'head\')[0].' +
            'appendChild(d.createElement(\'script\')).src' +
            '=\'http://someurl.com' +
        '">');
        doc.close();
    })(document);
</script>

Results

With all the optimizations we discussed I could boost the google pagespeed score from 70 to 89 on mobile and 77 to 94 on desktop. GooglePageSpeed-criticalpath.png

Other than that the DomContentLoadTime became 727ms as compared to 2.41s while page load time was improved by almost 1 second under regular 3G network simulated on Apple iPhone 6.

···

Rahul Sharma

Product Enthusiast & a clean coder. Strong believer of attention to detail, hungry for optimisations and making things better.
comments powered by Disqus


Copyright © Rahul Sharma, 2017 · Sign In