When your frontend app is a SPA, all the assets get loaded into the browser and routing happens within the browser unlike a SSR app or conventional/legacy web apps where every page is spat out by the server. If caching is misconfigured or not configured, you will have a horrifying time during deployments.
Muscle memories of your developers will make them hit hard refresh when they hear the word “Code Deployed” but your customers will rant and rave when their web page gets mangled in the middle of something important because of your deployment.
Having read on the internet before “Browsers and Web servers have been configured by default handle basic caching” made me procrastinate my learning on caching until one day. It started annoying QA and started killing developer’s productivity. That day I told myself “You are not gonna sleep tonight!”
Here is a guide to caching headers for SPA in Nginx.
How to Cache headers for SPA on Nginx?
The configuration which I’m going to explain wil+l work only if your SPA uses webpack or any other bundler which can be configured to append random characters to file names in the final distribution folder on every build (revving). This is quite a standard practice in modern web development. I’m pretty sure it will be happening in your system without your knowledge.
Checkout Revved resources section at
https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
The headers which we are going to need are cache-control, etag and vary. vary is added by NGINX by default, so we don’t have to worry about it. We need to add the other two headers in our configs at the right place to get caching working.
We have to configure the following things:
My current config.
location /app1 {
alias /home/ubuntu/app1/;
try_files $uri $uri/ /index.html;
}
After disabling caching.
location /app1{
alias /home/ubuntu/app1/;
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
How this will work?
When I hit /app1 from my browser NGINX will serve the index.html from /home/ubuntu/app1 directory to my browser, at the same time it will also execute the add_header directive which will add the Cache-Control "no-store, no-cache, must-revalidate"; to the response header. The header conveys the following instructions to my browser
The combination of these three values will disable caching for the response which is received from the server.
My current config.
#for app1 static files
location /app1/static {
alias /home/ubuntu/app1/static/;
}
After enabling caching.
#for app1 static files
location /app1/static {
alias /home/ubuntu/app1/static/;
expires 1y;
add_header Cache-Control "public";
access_log off;
}
How to implement cache headers with Nginx?
We enable aggressive caching for static files by setting Cache-Control to "public" and set expires header to 1y. We do this because our frontend build system generates new file names (revving) for the static assets every time we build and new file names invalidate the cache when browsers request it. These static files are referred in index.html which we have disabled caching completely. I disable access logs for static assets as it adds noise to my logs.
That's it! This must set up the Nginx caching headers for SPA to create a beautiful app.
We usually add the headers which we want to be common for all the location blocks in the server block of the config. But beware that these headers will not get applied when you add any header inside a location block.
http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header
There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.
server {
# X-Frame-Options is to prevent from clickJacking attack
add_header X-Frame-Options SAMEORIGIN;
# disable content-type sniffing on some browsers.
add_header X-Content-Type-Options nosniff;
# This header enables the Cross-site scripting (XSS) filter
add_header X-XSS-Protection "1; mode=block";
# This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
add_header Referrer-Policy "no-referrer-when-downgrade";
location /app1 {
alias /home/ubuntu/app1/;
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
In the above example the security headers in the beginning will not be applied to /app1 block. Make sure you either duplicate it or have it written in a separate .conf file and import it in every location block.
location /app1/static/fonts {
alias /home/ubuntu/app1/static/fonts/;
add_header “Access-Control-Allow-Origin” *;
expires 1y;
add_header Cache-Control “public”;
}
Adding the Access-Control-Allow-Origin header will instruct the browsers to allow loading fonts from a different sub-domain. Note that I also enabled aggressive caching for fonts too.
# Enables response header of "Vary: Accept-Encoding"
gzip_vary on;
This will add Vary: Accept-Encoding header to the publicly cacheable, compressible resources and makes sure that the browser will get the correct encoded cached response.
# Get the actual IP of the client through load balancer in the logs
real_ip_header X-Forwarded-For;
set_real_ip_from 0.0.0.0/0;
if ($http_x_forwarded_proto = 'http') {
return 301 https://$host$request_uri;
}
Add the above in your server block and open port 80 along with 443 in your AWS ELB. This redirect http to https and also log the actual client IP n your logs.
Putting all the above things together, this how the final config would look like.
server {
server_name www.my-site.com
listen 80;
# Get the actual IP of the client through load balancer in the logs
real_ip_header X-Forwarded-For;
set_real_ip_from 0.0.0.0/0;
# redirect if someone tries to open in http
if ($http_x_forwarded_proto = 'http') {
return 301 https://$host$request_uri;
}
# X-Frame-Options is to prevent from clickJacking attack
add_header X-Frame-Options SAMEORIGIN;
# disable content-type sniffing on some browsers.
add_header X-Content-Type-Options nosniff;
# This header enables the Cross-site scripting (XSS) filter
add_header X-XSS-Protection "1; mode=block";
# This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
add_header Referrer-Policy "no-referrer-when-downgrade";
# Enables response header of "Vary: Accept-Encoding"
gzip_vary on;
location /app1 {
alias /home/ubuntu/app1/;
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
#for app1 static files
location /app1/static {
alias /home/ubuntu/app1/static/;
expires 1y;
add_header Cache-Control "public";
access_log off;
}
#for app1 fonts
location /app1/static/fonts {
alias /home/ubuntu/app1/static/fonts/;
add_header "Access-Control-Allow-Origin" *;
expires 1y;
add_header Cache-Control "public";
}
}
Final config snippet.
Leave a Reply
Your email address will not be published. Required fields are marked *