How My Team Improved The Performance Of CarTrade's Migrated Pages

Published on :

My team was responsible for migrating the code base from PHP to .NET. Post the migration of the major, traffic heavy web pages, our next goal was to increase the performance and the traffic of the migrated pages. When I joined the team, I knew nothing about SEO, web vitals, page speed and performance. I am glad I got the chance to explore and implement the improvements with my team mates.

A web page's ranking plays a crucial role in the amount of traffic it can generate. Better rank means the website will appear higher up in the google search and it is more likely for a user to visit your website. To score a good rank, a web page must be performant and provide good content.

Content

My team had a good product manager to worry about a page's content, so let us move on and not worry about this part 😎... not really. Although my product manager used to come up with great designs and content for a web page, we still had to efficiently deliver that content on the web pages. Our main aim here is to reduce server load time. Some tips I can give are:

  • Minimize the number of api gateway calls
  • Maximize the number of parallel requests in a single api gateway call
  • Proper planning: For example, sometimes we used Elasticsearch instead of api gateway calls to fetch data. Various factors determined when to use what

HTML tags and semantics

There are more than 100 HTML tags and their proper usage will pay dividends. Obviously no need to learn every tag out there but we should understand when to use which tag. div is probably the most generic HTML tag. I have seen it being used in place of a p tag or button tag which is not correct. Even if the styling matches the design, its usage is not correct.

Proper usage of HTML semantic tags is also crucial. Semantic tags are regular HTML tags but their names have meaning which the browser can understand. This article provides various examples on how to use them.

Images

Images take up the bulk of almost every web page's content 👀. Post revamp, we used the img tag with the following attributes:

Copy code
<img class="img-class" src="img-src.png" alt="alt" loading="lazy" decoding="async" width="10" height="10" >

Image width and height attributes

Used to set the dimensions of an image. Browser allocates the space for an image based on values of width and height. Set them to reduce CLS.

Image alt attribute

An important attribute which contributes to a page's SEO and accessibility. If a browser is unable to fetch a image, it will display its alt text. It is also used by search engines to understand what the image is about.

Image decoding attribute

When a browser finds an img tag, it will try to download the image, decode the downloaded image and then show that image on the screen. Larger the image size, more time the browser will need to download and then decode it. If this is the case then the users will not be able to see the contents following the image (browsers show the content synchronously).

decoding="async" implies that browser need not wait for the downloaded image to be decoded. The content following the image is shown as decoding has been deferred. Once the decoding is complete, the image is shown. You can refer this article if you wish to read more.

Image loading attribute

A browser will start downloading all the images that it finds on a page. There is no point in downloading the images present at the end of the page if the user is at the start of the page. loading="lazy" defers the downloading of an image. Now the browser will only download an image if it at a certain distance from the viewport.

Images are also downloaded if they are present in background-image CSS property. The loading="lazy" attribute does not work on the mentioned CSS property yet. However to defer these images, we can use an intersection observer. You can also refer this YouTube video to learn about intersection observer.

index.html

Copy code
<div class="lazy-bkg" data-bkgimg="img.png"></div>

// All elements that use background-image css property will have "lazy-bkg" class
// The background image url is set in a data attribute instead of background-image property

script.js

Copy code
// Get all elements that need a background image
const lazyBkgELements = document.querySelectorAll(".lazy-bkg");

// If browser supports intersection observer
if("IntersectionObserver" in window) {
    const lazyBkgObserver = new IntersectionObserver(entries => {
        entries.forEach(entry => {
            // If element is in viewport, set its background image and unobserve the element
            if(entry.isIntersecting) {
                const bkgImg = entry.target.dataset.bkgimg;
                entry.target.style.backgroundImage = `url("${bkgImg}")`;
                lazyBkgObserver.unobserve(entry.target);
            }
        });
    });
    
    // Observe the elements
    lazyBkgELements.forEach(element => {
        lazyBkgObserver.observe(element);
    });
} else {
    // Set the background image if the browser does not support intersection observer
    lazyBkgELements.forEach(element => {
        const bkgImg = element.dataset.bkgimg;
        element.style.backgroundImage = `url("${bkgImg}")`;
    });
}

Note: No need to lazy load the images that are above-the-fold (or in viewport when the page loads). We can also skip lazy loading images that are below-the-fold but close to the viewport on load. In fact, if you lazy load an image which is above-the-fold and that image happens to the LCP element, lighthouse will report this as a failed audit.

Other tips that I can provide are:

  • Optimize the images. Instead of serving .png or .jpg images, serve .webp images. Many image optimizers can convert an image into .webp format. Be mindful of the quality and size of the served image
  • Preload the LCP image
  • Inline the critical CSS (CSS that is used by elements present above-the-fold) and defer the non critical CSS. Always defer the javascript files
  • Although we primarily used img tag at CarTrade, there are other attributes and tags available which can be used to create responsive images

Structured data

Structured data make it easier for search engines to understand what the web page is about. Browsers produce rich snippets with the help of structured data. We use schema markup to create structured data. My product owner used to convey to the team what needs to be part of the schema markup.

Core web vitals

Web vitals measure the user experience of a web page. The core web vitals consists of Largest Contentful Paint (LCP) which measures the page performance (this was the most rage inducing thing for me to optimize), First Input Delay (FID) which measures responsiveness and finally Cumulative Layout Shift (CLS) which measures unexpected content shift. These three factors contribute a lot towards SEO and improving them should help your page get a better ranking.

How to measure the web vitals

For testing in local and development environments, I used Lighthouse. Once your pages are in production, you can use PageSpeed Insights. Both provide suggestions on how to improve a web vital if needed. Also, if for any reason your web page's core web vitals worsen, then Google Search Console will report the affected the pages and which web vital has worsened.

Conclusion

The above tips are actually fundamentals of modern web development. Luckily, my team got to work on them and we learnt a lot. At the end of day, we may not have aced the PageSpeed Insights reports, but we were able to significantly improve our pages' web vitals and SEO. We saw improvement in our web pages' ranking and our traffic increased by approximately 126.15 % 😎.

Thank you for reading! 👋
One Year As A Software Engineer