Last weekend we had a chance to fine-tune the performance of SwissDev Jobs.
We run a job board for Software Developers who are looking for work opportunities in Switzerland. Performance of SwissDevJobs.ch matters for 2 reasons:
If you search for "website performance basics" you will get many actionable points, like:
We implemented most of those low-hanging fruits.
Additionally, as our main page is basically a filterable list (written in React) we introduced react-window to render only 10 list items at a time, instead of 250.
All of this helped us to improve the performance heavily but looking at the speed reports it felt like we can do better.
So we started digging into the more unusual ways in which we can make it faster and... we have been quite successful! Here is report from this week:
This report shows that the fully loaded time decreased by 24%!
What did we do to achieve it?
This simple line in the index.html file indicates to the browser that it should fetch it before it's actually requested by an AJAX/fetch call from JavaScript.
When it comes to the point when the data is needed, it will be read from the browser cache instead of fetching again. It helped us to shave of ~0,5s of loading time
We wanted to implement this one earlier but there used to be some problems in the Chrome browser that caused double download. Now it seems to work.
After implementing JSON preloading we found that downloading the job list is still the bottleneck (takes around 0,8s wait time to get the response from the server). Therefore, we decided to look into server-side cache.
First, we tried node-cache but, surprisingly, it did not improve the fetch time.
It is worth to mention that the /api/jobs endpoint is a simple getAll endpoint so there is not too much room for improvement.
However, we decided to go deeper and built our own simple cache with... a single JS variable. It looks the following:
The only thing not visible here is the POST /jobs endpoint which deletes the cache (cachedJobs = undefined)
As simple as it is! Another 0,4s of load time reduced!
The last thing we looked at is the size of CSS and JS bundles that we load. We noticed that the unsuspected font-awesome bundle weighted over 70kb.
In the same time, we used maybe 20% of the icons.
How did we approach it? We used icomoon.io to select the icons we used and created our own self-hosted lean icon package.
The result? 50kb size saved!
Those 3 unusual changes helped us speed up the website loading time by 24%.Or, as some other reports show, by 43% (to 1,2s).
We are happy with this changed. However, we believe that there is still room for improvement.
If you have your own, unusual techniques that could help - please let us know!
After applying the improvements mentioned above we noticed that the bottleneck is still the fetching time of our /api/jobs endpoint and downloading of the initial HTML.
The reason for that is that both the initial HTML and the API are served from a single server based in Europe.
We looked into possible solutions and decided to take the radical step of caching everything at the server level.
With Cloudflare, by default, only static assets are cached. But, by adding a simple Page Rule, you can enable caching for every page or resource:
This enabled our website to be served directly by the Cloudflare's CDN without even hitting the server.
The results?
Impressive - over 50% load time reduction across the world!
Now, you might be skeptical about the idea of caching the initial HTML and dynamic APIs, but consider:
First, that our endpoint does not change that often - new jobs are added in batches once per day.
Second, we purge the cache programmatically when it happens.
This allows us to server SwissDev Jobs in different locations across the world in less than 1 second!