Nginx Cache Config for Dockerized Angular App - NgDocker

Zaki Mohammed Zaki Mohammed
May 16, 2023 | 5 min read | 2278 Views | Comments

App running so fast, even the cops are chasing! Not breaking any law here just configuring Nginx caching for our Dockerized Angular app. In this article we will address the need for speed for our super silly NgDocker app by setting up the Nginx cache configurations for static files.

We are on a spiritual journey of performance improvement for a Dockereized Angular app. In continuation of the previous article Gzip Dockerized Angular App with Nginx - NgDocker where we have already enabled the Gzip and seen a significant performance boost for the static files; we will use the same app for this article too and add the cache control header to the static files by configuring this setting in Nginx config.

We will head in this direction:

  1. Cache Control Header
  2. Add Cache Control in Nginx Configuration
  3. Investigating the Cache Control

Cache Control Header

The Cache Control is a response header, that can be set from the server side; it tells the browser whether the file needs to be cached or not. As per the MDN documentation for the Cache-Control, we need to add the cache control header to the respective files. Once this header is present the browser then simply caches the files till the expiry provided in the header. For this we need to provide the value "max-age=31449600" to the cache control header. Here, the max-age value is set in seconds; so "31449600" is approximately 365 days, roughly a year. Through the same cache control header, we can even tell the browser to not cache some of the file by setting the value for cache control to "no-cache". We will be needing this control to decide whether to cache or not cache some of the files as per our app needs.

Add Cache Control in Nginx Configuration

The time has come to add the majestic cache configuration to the Nginx config file "nginx/nginx.conf". Consider these settings can be used for any other SPA application too like React, Vue, etc. We will add different configuration for different static files:

  1. Index File (index.html)
  2. JavaScript and CSS Files (.js/.css)
  3. Image Files (.jpg, .png, .svg, etc.)

1. Index File (index.html)

We never cache the index.html file otherwise it will never provide the updated application results when we deploy a new build. So for "index.html" it is must to not cache and always fetched from the server.

server {
    ...
    location ~ /index.html {
        add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
    }
    ...
}

This will ensure to disable the caching for index.html by the browser. Read more about these directives here: Cache-Control Directives.

2. JavaScript and CSS Files (.js/.css)

Cache JS/CSS for a year since every new build will have its own build UUID the JS/CSS files in a newer build will never be the same. So, till the time there is no new build deployed the JS/CSS files once fetched on client-side will never be fetched again until a new build is deployed.

server {
    ...
    location ~ .*\.css$|.*\.js$ {
        add_header Cache-Control 'max-age=31449600'; # one year
    }
    ...
}

3. Image Files (.jpg, .png, .svg, etc.)

The image files we are talking here are mostly from the "assets" folder. These images are mainly related to the UI and App, example: icons, background, avatar, etc. These images are static images for client-side, those can be cached for one month without any problem. This can differ application to application, set as per your app needs and requirement. For this article we are setting it up to one month.

server {
    ...
    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
        expires 1M; # one month
    }
    ...
}

We can even use "expires" value too in Nginx for location directive.

Investigating the Cache

After building the Docker image and running the Docker container let us investigate if the things are aligning or not as per our expectations. We will use Firefox as it shows the transferred file source correctly as compared to Chrome; from where it has been served, from server or browser cache. For this we will first hard-reload (Ctrl + F5) the app and observe the Developer Tool's Network tab; then we will reload (F5) the app and again observe the Network tab to see the source of the transferred file either server or cache.

Docker: Build and Run

docker build -t ng-docker:mark-3 
docker run -p 3300:80 --name ng-docker-mark-3-container-1 ng-docker:mark-3

Initial Load or Hard-Reload (Ctrl + F5)

Non-Cached | NgDocker | Mark 3 | CodeOmelet

Here, the "Transferred" column shows the size of the transferred file, indicating the file has been transferred from server and not coming from browser cache.

Reload (F5)

Cached | NgDocker | Mark 3 | CodeOmelet

Here, the "Transferred" column shows the "cached", indicating the file has been served from browser cache.

If we look it more closely, we can see that the cache control header is appearing as Response Header once we clicked on the file.

Index File

Index File | NgDocker | Mark 3 | CodeOmelet

As expected the "index.html" file is appearing as "no-cache" header value.

JavaScript Files

Cached File | NgDocker | Mark 3 | CodeOmelet

As expected the ".js" files are appearing as "max-age" header value.

Checkout the entire nginx.conf file below.

nginx/nginx.conf

server {
    gzip            on;
    gzip_static     on;
    gzip_types      text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_proxied    no-cache no-store private expired auth;
    gzip_min_length 1000;

    listen 80;

    root /usr/share/nginx/html;

    location ~ /index.html {
        add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; # no cache
    }

    location ~ .*\.css$|.*\.js$ {
        add_header Cache-Control 'max-age=31449600'; # one year
    }

    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
        expires 1M; # one month
    }

    location / {
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
    
    error_page   500 502 503 504  /50x.html;
    
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

Zaki Mohammed
Zaki Mohammed
Learner, developer, coder and an exceptional omelet lover. Knows how to flip arrays or omelet or arrays of omelet.