The Secret Formula: Lesser-known Tips For Improved Web Performance


Contributor: Bhavya Saggi

There are plenty of articles around on how to set up a robust infrastructure, and even more articles on how to make it resilient and faster. But other than optimising the infrastructure and web-services,  individual web pages that are served to users must also be optimised to gain the attention of your consumers. This calls for the need for smooth content delivery and an efficient On-Page SEO. 

Following the premise, similar common optimisations were in place at my workplace at Makaan.com but I figured I could shelve a few extra seconds off the web-page and deliver more content (to Search-Engine Crawlers and Users). Following is a summary of actions, optimisations and experiments that were performed to this effect.

Minify HTML

A build automation tool like grunt, gulp or webpack is often used to create minified-uglified-hashed static resources, but the HTML document (or HTML template) slip in between. Since the HTML document is an accumulation of all HTML, inline-CSS (style tags) and inline-JS (script tags) resources, it may be tricky, but the presence of the ‘white-space’ is common. 

The white-spaces from <script> & <style>tags can be eliminated by using as sparingly as possible in these tags and moving other content to an external file, which later can be minimised and uglified by utilising already available tasks under major build automation tools like grunt-usemin for grunt.

For the HTML templates or static HTML files, whitespace elimination between HTML-tags should be done either via regex-replacement or innate template optimisation.

In our case, we had many inline<script> tags containing relevant information in JSON-format. Therefore, we simply overrode our <script> tag in template-renderer to wrap contents said script-tags in JSON.stringify(JSON.parse()) and for other script tags, simply made a regex replacement replace(/[\s]*[\n\r][\s]*/gi,'') over the HTML document.

Use of Link tags to hint future resources

After setting up the HTML, its time we checked the external resources. Resources are requested by browser via two ways:

  1. Explicit inclusion by <script> , <style>, or <link> tag in HTML document.
  2. Resources needed by external resources, eg. a background-image in a CSS style or dependency for a JS files.

The first priority should be to minimise resource-chaining e.g. an external Javascript file requesting a CSS file which later on requests an image. And to this effect modern browsers allow us to declare future resources beforehand for apriori management, some are discussed as follows:

  1. <link rel="dns-prefetch" href="resource">
    This directive mentions to the browser to prematurely resolve the DNS query to a domain, so that the future resources from the domain can be resolved.
    dns-prefetch is most effective when used for the CDN domain, or declaring alternate domains if domain-sharding is employed.
  2. <link rel="preconnect" href="resource" crossorigin>
    Moving further ahead from dnspreconnect, the preconnect directs the browser to actually setup the connection to the domain.
    This is most effective when used in conjunction with a domain that utilises HTTP/2, as HTTP/2 keeps a connection opened till it is explicitly closed.
  3. <link rel="preload" href="resource" as="type">
    If there is a resource that is sure to be used on a webpage but is declared deep in a chain, it can indicate to the browser beforehand using this directive. preload allows browser to fetch the resource and keep it in its cache, and serve it from cache itself when resource is requested.
    This can be effectively used for mentioning the font/image files mentioned further in the CSS stylesheet.
  4. <link rel="prefetch" href="resource">
    Similar to preload, the prefetch directive hints browser to request the resource, but in the background in idle-time.
    Link prefetch is supported by most modern browsers with the exception of Safari, IOS Safari, and Opera Mini.
  5. <link rel="prerender" href="resource">
    Prerendering is very similar to prefetching in that it gathers resources that the user may navigate to next. The difference is that prerendering actually renders the entire page in the background.
    Hence, it is most effective to indicate the next likely HTML navigation target.

Ergo, we added all the CDNs under the ‘preconnect’ link-tag, the image-sprites and font-woff files under ‘prefetch’ link-tag, and future-needed lazy-loaded javascript files under ‘preload’ link-tag. You may even go further ahead and add the next page resources under ‘prerender’ link-tag if your web-application is like a SPA (Single Page Application).


Chunking, Segmenting & Deferring Page Resources

Payload Size of a TCP packet is ~63Kb which means that we can transfer 63Kb of data in a single RTT (Round-Trip Time) before the network waits for the next packet. Hence, it means that:

  1. Very large files would take many packets (which arrive out-of-order), and network waits till all are received so that they can be processed.
  2. Very small files would be resolved in a single RTT, but multiple requests pollute the network with copious amount of packets.

Therefore, the experience can be improved by chunking the external resource files into independent modules of size 40–50Kb. But this comes with a problem that multiple independent modules, all requested and executing in parallel (thanks to http/2), chokes the network and main thread of the browser. To solve this, manual intervention is needed to segment resource requests/execution in three phases:

  1. First-Fold Resources (inline resources)
    As per Google’s Pagespeed & Lighthouse tool guidelines, the CSS for content in first-fold should be inlined, so as to maintain user-engagement and avoid jerky/clunky-loading on the webpage. Other than CSS, a few essential/declarative JavaScript could be inlined e.g. infrastructure related JS, on which future JS execution is dependent. It is done so as to avoid any race conditions that may happen, that is, if the definition of a function has not arrived yet and it is used.
  2. Page-Essential Resources (linked resources)
    Only resources which provide basic functionality for your webpage should be ‘linked’ in the <head> of the HTML document, to reduce the number of requests the browser sends and avoid network choking. This means, adding only the CSS for basic and static components, and only the JS for essential functionalities (e.g. event-binding or tracking).
  3. Lazy-loaded resources
    Heavy and dynamic resources should be lazyloaded and their resources should be fetched only when the domInteractive event for browser has fired. You may even go further and delay a few resources after the pageLoad event. This allows us to manually defer resources and in a hacky-manner define the sequential order of resources.

Image Optimisation

While images provide a more user-friendly and visual medium to a webpage, they are often a load on the webpage either because of their count or their size.

A solution for smooth visual experience is to defer image loading on the webpage. To achieve this, we placed a dummy-image (usually a 1px jpg) in the src attribute of <img> tag and url of original image in the data-src attribute of the tag. Upon ‘domInteractive’ or ‘pageLoad’ event, we initiate a IntersectionObserver and start replacing the src of Image-tags with the value in data-src attribute as the images come in view.

To extend user-experience and gain SEO/Accessibility brownie-points, popular tools suggest:

  1. Provide all height, width, & src attribute for the Image (<img>) tags.
  2. Serve images in webp image format, if browser supports.
  3. Resize and serve images for the same size as required on the webpage.

Use HTML5 semantic Tags & Follow W3C Validations

Because of highly interactive and content-rich webpages, crawlers and browsers often lose sight of what may be important. This is where HTML5 semantic tags come into play.

HTML5 provide numerous tags, which are similar to classical <div> but provide a universal semantic meaning. <header>, <footer>, <section>, etc. are a few to name which provide a semblance of HTML markup to crawlers and browsers.

Furthermore, modern browsers are resilient and often auto-correct incorrect HTML tags (e.g. a tag you forgot to close), but we should make sure that we provide as perfect content as possible, therefore the HTML document should pass the W3C validations. Not only does this help browsers render the document with much ease, but makes your content apprehensible to crawlers.

Update & Optimise Nginx configuration

We use Nginx as our primary front-facing load-balancer and web server. Following are the general optimisations that should be performed to get most out of it.

Mozilla SSL Configuration Generator
  1. Enable http/2
    The HTTP/2 (h2) being the successor of HTTP/1.x (h1) provides a range of optimisations, some of which includes, Header-Compression & Request-Multiplexing.
    HTTP/2 also comes with an additional feature of ‘Server Push’, which can be enabled from Nginx after v1.13.9. (which will be discussed at a later stage). One limitation before enabling http/2 is that it runs only over https, making us look for SSL related improvements.
  2. Enable OSCP stapling
    Online Certificate Status Protocol (OCSP) is used to check the revocation status of X.509 digital certificates. Nginx allows appending (“stapling”) a time-stamped OCSP response which is signed by the Certificate Authority to the initial TLS handshake (eliminating the need for clients to contact the CA) & reducing time taken to setup an SSL connection.
  3. Extend SSL cache.
    Nginx allows to sharing of the SSL-session between its workers through ngx_http_ssl_module. The cache size is specified in bytes; one megabyte can store about 4000 sessions, therefore reducing SSL resolution time for a recurring user.
    The ssl_session_ticket can also be used as an alternate to SSL-cache. In case of session tickets, information about session is given to the client. If a client has a session ticket, it can present it to the server and re-negotiation is not necessary.
  4. Enable Server-push
    When a connection is http/2 enabled, asset files can Server Push is where the server pushes a resource directly to the client without the client asking for the resource, saving 1 round-trip for assets. Since v1.13.9, Nginx natively supports Server-push, by pushing the assets defined in the “Link preload” response headers.
With HTTP/2 Push, the server can take the initiative to send content even before it is requested. In this example scenario, the server knows that anyone requesting demo.html will need styles.css and image.jpg, so it can push them to the client immediately without waiting for the client to request them.

Furthermore, do refer the [Mozilla SSL Configuration Generator] as a boilerplate to generate a secure & optimised Server configuration, as per the need.
The discussed optimisations are quite generic and can easily be extended to other web-servers (e.g. Apache, IIS).


Conclusion

The culmination of the set of aforementioned activities is evident in the following snapshot of ‘Average Page-Load Time’ for Makaan.com (taken from Google Analytics).

Not only did we manage to reduce the size of each asset (including HTML, CSS, and JS files), we chunked and linearised the delivery for the assets over http/2 protocol. This provided us  ~60KB drop in average data transmission till page-load and a full effective boost of ~2 seconds in page-load time across the entire website!

For further detailed explanation, here’s a waterfall-snapshot for a webpage on Makaan.com

Useful References

Following are popular webpage optimisation tools which should be used and referred for a better user-experience.

  1. TestMySite — Provides a comprehensive performance and User Engagement result from a suite of tests.
  2. PageSpeed Insights — A tool which scores your webpage out of 100, based upon a list of optimisations which are expected to be present.
  3. Webpagetest — A utility which allows to visualise the webpage’s performance in metrics & screenshots.
  4. Lighthouse — A client-side utility which can be run on browser itself (can be found in Google Chrome, in Developer Tools, under Audits tab, giving a broad-spectrum report.
  5. Google Structured-Data Testing Tool — Google’s Utility to verify & validate ‘Structured Data Markup’ on a webpage.
  6. Google Mobile-Friendly Test — Google’s Utility to verify & validate if a webpage performs & works as expected on Mobile Devices.
  7. W3C Validator — The Markup Validator is a free service by W3C that helps check the validity of Web documents.