Performance optimizations on

Table Of Contents

  1. Low hanging performance speed ups
  2. How I measured the performance
  3. Conclusion is a website that distributes exam papers and schemes to students and teachers of Sri Lanka.

As I always tested the site on my home Wi-fi, I never really noticed the performance issues. 😬

Here is a look at the network tab of Developer Tools:

Network requests of, loading total of 14 MB images

#Low hanging performance speed ups

#Vercel's Functions configuration

The website is hosted on Vercel. By default, Serverless Functions on Vercel are run in Washington D.C., USA (East). It's too far away from both our users (Sri Lanka) and our server (Singapore). Because of this the initial (HTML) response time was too long (~2s).

After, I switched to Singapore region. This change alone reduced the initial response time to about 0.8s. About 2x faster.

#Cache-Control header

Until about 4 months ago (or so), precisely 0 responses from our service had Cache-Control header. The reason is, to be honest, I forgot about it. 🫣

Here is a sad effect that:

Logo of contributed to 2.68 GB of Network Transfer in 30 days

After seeing this, I started adding Cache-Control headers to static files. Now a fair amount of responses are served with a Cache-Control. I am still figuring out perfect caching techniques for non-static assets.

There are only two hard things in Computer Science: cache invalidation and naming things.
Phil Karton

#Switch to JPEG

Our thumbnail images are stored in our server in PNG format. The reason behind this is ease of use. PNG images usually have huge file sizes (compared to other formats). Currently, we have stored 486 MB of thumbnail images on our server. While this is not a huge impact for our server's storage, we had a bigger issue. All of these images were sent AS IS for all of our users. Poor me did not think it through. On top of that, users were served full sized. 🥲

I got curious and measured how much time it takes to load our homepage. The page loads about 20 thumbnail images. I ran a WebPageTest.

  • It took 78.664s to load!
  • 97.9% of all bytes loaded are images!
  • 14 MB of images were transferred

To optimize this, I started serving thumbnail images in JPEG format instead. I created a middleware using sharp library to convert and downscale images.

After doing these, I ran another WebPageTest. Here are the new numbers.

  • It took 10.120s to load (~8x improvement)
  • 54.3% of all bytes loaded are for images (~2x improvement)
  • 387.17KB of transferred images (~36x improvement)

Network requests of, loading total of 387 KB images

There are more optimized image formats in the wild such as WEBP and AVIF. And they are supported on all major browsers. I could have gone for WEBP or AVIF and achieved even more optimizations. However, converting to these formats took more time.

#Preconnect & DNS-prefetch's server and website of are ran on different machines and different domains. The web browsers must create a new connection to load resources from the server. But our server is a critical resource. We would like to establish the HTTPS connection as early as possible.

We can hint the browsers to do so, by using these tags:

<!-- is not our server's domain -->
<link rel="preconnect" href="" />
<link rel="dns-prefetch" href="" />

#How I measured the performance

I measured loading performance with Firefox on my laptop connected to my home wifi. All screenshots are taken from here.

I used WebPageTest to measure real-life performance. The configuration was:

  • Browser: Chrome
  • Device: Emulated Motorola G (gen 4)
  • Location: Mumbai, India
  • Connection: 1.6 Mbps 3G with 300ms of latency

Note that I did not run extensive performance measurements after each of the steps. I should have but did not.

#Conclusion has about 45,000 users. Every small performance improvement are important for each of them. I am kind of sad that I did not notice these performance issues early on.