Web Optimisation — Front End Development

Mohamed Sharbudeen
8 min readJun 26, 2022

--

Hello there! This post is about some of the important web optimization techniques and nuances, that someone who is aspiring to become a front-end developer should be aware of. I have compiled this long page with the knowledge that I have gathered by going through various articles and videos.

Let’s get started!

Module-wise file/ code-splitting, CDN, Client-side caching and compression, and optimized server are some of the big picture optimization techniques. But where most dynamic websites focus less is on the low-level details such as memory leaks, layout thrashing, and ignoring contentful painting methodologies. In this article, we will emphasize more on the low-level details more.

Memory Leaks

A perfectly working website in a desktop browser might struggle to load pages or interactions can become slower over time on handheld devices. Mobile devices usually have a constricted memory space (proportionately less JS heap). So it is important to not consume too much memory and let GC do its work.

function getRunner(a) {
var largeData;
function runIntensiveAlgorithm() {
if(largeData){
return largeData
}
for (var i = 0; i < 1000; i++) {
largeData += run(result);
}
return largeData;
}
temp = largeData
return runIntensiveAlgorithm;
}

  1. Unwanted closures — Closure variables won’t be GC’ed at the end of the execution context. largeData is the closure variable for runAlgorithm function. Closures can be avoided in places where it is not absolutely needed. One perfect scenario to use closure is Memoization — if function A purely outputs result B consistently, then the function can be memoized. It is important to write pure function in many places as possible to favor unit testing capabilities.
    For more info about closures
  2. Incorrect scoping — As you know, let|var|const have different scoping mechanisms and serve different purposes. Block scoped variables can be declared with let, const access modifiers accordingly. eg: i in for loop can be block scoped to restrict visibility. When variables are declared without any access modifiers eg: temp, it is stored in the window object (not in strict mode) due to delegation.
  3. Detached DOM references — Elements not present in the DOM won't be GC’ed as it holds reference with a JS variable.

Ways to identify

Open timeline panel in dev tools. Record the actions performed. In the heap graph, make sure that memory consumption is not gradually increasing over time or bloated suddenly, and GC is called in regular intervals. Use heap snapshots to identify detached DOM nodes.
For more read on memory problems

Layout Thrashing

There is a sequence of actions that takes place between a JS code to render something on the screen to the pixels being painted. They are
Javascript > Styling > Layout > Painting > Compositing.
One must be aware of what set of actions triggers this cycle in order to write performant and efficient code.

  • JavaScript. JS code that triggers the visual change. eg: changing the style of an element. Adding or removing elements from the DOM.
  • Style calculations. CSS selectors are mapped to the elements to which the style should be applied. From there based on CSS specificity, once rules are known, they are applied and the final styles for each element are calculated.
  • Layout. Calculate the position and dimension of elements that should appear on the page. eg: Changing the width of a container element, affects all its children’s widths.
  • Paint. Painting is the process of filling in pixels. It involves drawing out text, colors, images, borders, and shadows, essentially every visual part of the elements. The drawing is typically done onto multiple surfaces, often called layers.
  • Compositing. Two elements can have different stacking contexts based on the z-index. The order of layers is determined in this phase.

Change to layout information triggers the pipeline from layout (Reflow). similarly, if painting is triggered, it starts from painting (Re-paint). Imagine when this change is triggered at a very high frequency. That is when things get worse.

Reflow (a.k.a Layout) is a process of calculating DOM element’s size and position on a web page. Reflows are expensive, so browsers try to queue the changes and apply them in batches to minimize the required reflows.

function expandTable() {
for (let i = 0; i < 1000; i++) {
let width = container.offsetWidth;
cell.style.width = width + 10 + “px”;
}
}
function expandTableImprov() {
let width = container.offsetWidth;
for (let i = 0; i < 1000; i++) {
cell.style.width = width + 10 + “px”;
}
}

Note that the width calculation is done prior as it is not dependent on the write calls. Reads and writes should be batched wherever possible.

The former triggers reflow where the latter only repaints. Refer
For more read
layout thrashing

Contentful Painting Methodologies

Time to First Byte

TTFB is the duration it takes for the first data byte to arrive from a server to the requesting browser. The most straightforward front-end performance hiccup is the network lag and TLS delays. Not all customers are equipped with high-end devices and network bandwidth which most developers use during the development and testing phases.

rel=”preload” is a declarative attribute set in links to fetch requests that tell the browser to request a resource as soon as possible. Requests can be prefetched based on various mechanisms to avoid TLS delays and receive content fast. They are...

  • dns-prefetch: indicates to the browser that it should perform the resolution of a given domain name (determining the IP to contact) before that domain is used to download resources.
  • preconnect: indicates to the browser that it should connect to a given origin before that domain is used to download resources. Pre-connecting involves, like – dns-prefetch, the DNS resolution, but also the TCP handshake and TLS negotiation (if the page is secure)
  • prefetch: indicates to the browser that it can download a given resource, even if it is not detected on the page. The resource is downloaded with a low priority. <link rel="prefetch"> asks the browser to download and cache a resource (like a script or a stylesheet) in the background. The download happens with a low priority, so it doesn’t interfere with more important resources. It’s helpful when you know you’ll need that resource on a subsequent page, and you want to cache it ahead of time. The browser doesn’t do anything with the resource after downloading it. Scripts aren’t executed, and stylesheets aren’t applied. It’s just cached — so that when something else needs it, it’s available immediately.
  • preload: tells the browser that it must download a given resource as soon as possible, with high priority.<link rel="preload"> tells the browser to download and cache a resource (like a script or a stylesheet) as soon as possible. It’s helpful when you need that resource a few seconds after loading the page, and you want to speed it up. The browser doesn’t do anything with the resource after downloading it. Scripts aren’t executed, and stylesheets aren’t applied. It’s just cached — so that when something else needs it, it’s available immediately.

For more read on preloading files

First Contentful Paint

FCP is the initial piece of content that the user sees during the initial load of the webpage. It should represent the contextual info of the webpage and not a white blank space. eg. Facebook newsfeed container holder with title and basic meta, without the actual content in it.

The following aspects ensure a good FCP score

  1. Keep initial content load less than 2s
  2. Render Navbar and Main contextual pieces of information first
  3. Implement loading placeholders for data-dependent contents
  4. Default font should render. Always provide fallback sans serif.
  5. Load initial JS and CSS files alone.
  6. Reduce initial JS modules using file-split. Don’t overdo it. Hit other main modules asynchronously
  7. Minify it. Closure compiler for more effective minification
  8. If images are served, compress them. gzip is the most popular file compression choice.
  9. Implement cache-control headers
  10. Lazy load-dependent files based on modules

Time To Interact

The problem is that there is a limit to the number of concurrent connections a browser can open to a single host. This limit exists to protect a server from being overloaded with a high number of HTTP requests. However, it also serves as a potential bottleneck, often forcing the browser to start queuing connection requests. which could make interactions slow. In near future, multiplexing using http2 can resolve this.

A visitor using the Google Chrome browser can only open six TCP connections to your server at once

Following measures ensure a good TTI score

  1. Keep it less than 4s.
  2. Load scripts through async and defer, based on the use case.
  3. Without async and defer — HTML parsing is halted during the download and execution phase of the script file
  4. async — HTML parsing is not halted during the download phase. Once the file completes download, HTML parsing stops and js executes (usually HTML parses within download time). The first downloaded script will execute without maintaining order.
  5. defer — same as async but once HTML parsing completes, the file starts executing. The execution order is maintained as per download request.

Cumulative layout shift

Consider that you are about to check out your online shopping items and before you click the button layout shifts and you accidentally click on delete cart. Weird right?

layout shift score = impact fraction * distance fraction

Try to specify pre-defined height and width to HTML elements at possible places, so that reflow wouldn’t have to explicitly find it and shift the layout.

A few other techniques that you should be aware of...

Critical Rendering Path — Optimising for performance is all about understanding what happens in these intermediate steps between receiving the HTML, CSS, and JavaScript bytes and the required processing to turn them into rendered pixels — that’s the critical rendering path.

HTML parsing can be optimized by deferring scripts, preloading files, and having minimal nodes in DOM

CSS parsing can be optimized by writing efficient selectors

  • Selectors are processed from right to left.
  • Better to write specific selectors than writing codes like,
    :nth-child(2) or children[2].parentElement
  • Avoid using negating selectors (:not())
  • Let specificity do the work. Don’t use !important.
  • Split CSS files based on demand

Render Tree construction can be optimized by writing proper CSS to HTML elements with specificity in place

CSS triggers should be known to avoid reflows.

Good read on the critical rendering path

Thanks for giving it a read. Cheers!

--

--