I was fortunate enough to participate in a short term project to develop a new product within 2 weeks. Normally, once we identify user’s top problems we go for a minimum viable product over several weeks. However, this time it was more of a minimum viable experiment.
Due to some security policies of the client, we were told that we could only provide the app in a static
manner to public. Initially, we had the ambitions to set up headless CMS with database and cloud storage but we couldn’t get by the security policies in 2 weeks. Therefore the structure of the product became very simple: no database, no cloud storage, no fetching data over network, etc.
Now the product (React app) is basically serving static contents over Nginx, we focused on feature stories and design stories and we did not expect any performance issues.
We released the product. At the end of the first day, we heard from users that the initial render was taking a long time. It sounded like the network that users were using was a lot slower than what we expected. It was too bad that we couldn’t be there to see and feel the environment they were in.
Creating Nginx Container
In order to test on my local machine, I tried to mimic path to production with Docker using Nginx container.
First, a multi-stage build Dockerfile was created:
Here is a minimum Nginx configuration that we started with:
Docker compose file:
Lighthouse Analysis
First we analyzed the page load using lighthouse
in Chrome DevTools.
Under opportunities, first thing lighthouse shows is Enable text compression
. We were able to get hints from lighthouse that maybe it is better to add some compression configurations to nginx.conf
.
Compression
Before starting, network speed was set to Slow 3G
because in a fast network environment everything seems good. This might be a little bit more extreme than the users’ environment but it was done as an experiment.
Before making any changes network tab was captured:
We decided to add some gzip
directives to nginx.conf
in order to see if it made any changes or improvements:
It is possible to check if compression is applied properly or not by checking the Content-Encoding
HTTP header under Response Headers
. You could also check by using cURL command: curl -I --compressed http://your-content-path
.
This was the network tab after applying compression:
It can be observed that for Javascript file:
- Loading time decreased from 11.65s to 6.28s
- Size decreased from 370kB to 115kB
Also, for CSS file:
- Loading time decreased from 3.19s to 2.32s
- Size decreased from 20.9kB to 6.3kB
Lastly, DOMContentLoaded and Loaded time displayed at the bottom of the Chrome DevTools:
- DOMContentLoaded: Decreased from 13.69s to 8.34s
- Loaded: Decreased from 42.94 to 37.57s
lighthouse
page load analysis was performed again after applying compression. This time the result looked like this and there is no Enable text compression
anymore under opportunities.
Caching
Looking at the lighthouse
result, it says Serve static assets with an efficient cache policy
under Diagnostics
.
When users visit this website again, browser cache can help load files much faster. Because this product had many assets such as images, videos, icons, etc, caching would be beneficial to user experience. Basically, the idea is to configure Nginx to return the Cache-Control
HTTP response header.
With this setup, for the files matching the criteria for caching, it will show Cache-Control
HTTP header with max-age=2592000
which is one month.
$ curl -I http://localhost:3000/assets/P17.jpg
HTTP/1.1 200 OK
Server: nginx/1.23.3
Date: Fri, 03 Mar 2023 01:56:18 GMT
Content-Type: image/jpeg
Content-Length: 398807
Last-Modified: Thu, 02 Mar 2023 13:04:05 GMT
Connection: keep-alive
ETag: "64009ec5-615d7"
Expires: Sun, 02 Apr 2023 01:56:18 GMT
Cache-Control: max-age=2592000
Accept-Ranges: bytes
Looking at the lighthouse
, there are more hints on optimizing the performance. This article covered how to set up compression and caching using Nginx. One thing I learned is that no matter how static a product can be, it is important to allocate some time before release to tweak configurations in order to maximize the user experience.