nginx spa index.html static files cache

本文详细介绍如何在Nginx中正确配置缓存头,以优化单页应用(SPA)的性能。文章强调了对于index.html禁用缓存的重要性,并对静态资源如CSS、JS和字体文件启用长时间缓存的方法。

 

 

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?

Primary Requirement

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
 

Status Quo

  • 2 WebApps segregated by NGINX locations paths
  • AWS ELB is sitting in front of the NGINX Web Server
  • AWS CloudFront is sitting in front of AWS ELB. (Actual caching is done here)
  • NGINX is sending out last-modified and etag headers.
  • I have some faint idea on how caching works.

 

Configure caching in NGINX

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:

  1. disable caching for index.html ( Every time browser will ask for a fresh copy of index.html)
  2. Enable caching for static assets (CSS, JS, fonts, images) and set the expiry as long as you need ( eg: 1year).

 

1. Let's disable caching for index.html 

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

  • no-store: don’t cache/store any of the response in the browser.
  • no-cache: ask every-time(every request) with the server “Can I show the cached content I have to the user?”
  • must-revalidate: once the cache expires don’t serve the stale resource. ask the server and revalidate.

The combination of these three values will disable caching for the response which is received from the server.

 

2. Let's enable caching for static assets

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.
 

NGINX add-header Gotcha

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.

 

Bonus

  1. Enabling CORS for fonts when serving through a different CDN domain.

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.
 

  1. Adding vary by gzip

# 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.
 

  1. NGINX HTTP to HTTPS Redirection

# 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";
    }
} 
# 项目1配置 server { listen 8001; server_name localhost; # 前端1的dist目录 root /var/www/project1/dist; index index.html; # 静态资源 location ~* \.(?:js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; try_files $uri =404; } # API请求转发到后端1 location /api/ { proxy_pass http://backend1; # 后端1服务 # 代理设置... } # SPA路由处理 location / { try_files $uri $uri/ /index.html; } } # 项目2配置 server { listen 8002; server_name localhost; # 前端2的dist目录 root /var/www/project2/dist; index index.html; # 静态资源 location ~* \.(?:js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; try_files $uri =404; } # API请求转发到后端2 location /api/ { proxy_pass http://backend2; # 后端2服务 # 代理设置... } # SPA路由处理 location / { try_files $uri $uri/ /index.html; } } # 后端服务定义 upstream backend1 { server 127.0.0.1:3001; # 项目1的后端服务 } upstream backend2 { server 127.0.0.1:3002; # 项目2的后端服务 } 帮我按照这个把user www-data; worker_processes auto; worker_cpu_affinity auto; pid /run/nginx.pid; error_log /var/log/nginx/error.log; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 768; # multi_accept on; } http { ## # Basic Settings ## sendfile on; tcp_nopush on; types_hash_max_size 2048; server_tokens off; # Recommended practice is to turn this off # server_names_hash_bucket_size 64; # server_name_in_redirect off; include /etc/nginx/mime.types; default_type application/octet-stream; ## # SSL Settings ## ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3 (POODLE), TLS 1.0, 1.1 ssl_prefer_server_ciphers off; # Don't force server cipher order. ## # Logging Settings ## access_log /var/log/nginx/access.log; ## # Gzip Settings ## gzip on; # gzip_vary on; # gzip_proxied any; # gzip_comp_level 6; # gzip_buffers 16 8k; # gzip_http_version 1.1; # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; ## # Virtual Host Configs ## include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } #mail { # # See sample authentication script at: # # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript # # # auth_http localhost/auth.php; # # pop3_capabilities "TOP" "USER"; # # imap_capabilities "IMAP4rev1" "UIDPLUS"; # # server { # listen localhost:110; # protocol pop3; # proxy on; # } # # server { # listen localhost:143; # protocol imap; # proxy on; # } #} 改一下,最终出一个完整的
08-28
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 80; server_name localhost; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } server { # 监听443端口并启用SSL listen 443 ssl http2; server_name your-domain.com; # SSL证书配置(根据你的实际情况调整路径) ssl_certificate C:/Users/123/Desktop/nginx-1.29.2/html/certs/server.crt; ssl_certificate_key C:/Users/123/Desktop/nginx-1.29.2/html/certs/server.key; # 客户端证书验证(根据vue.config.js中的配置) ssl_client_certificate C:/Users/123/Desktop/nginx-1.29.2/html/certs/ca.crt; ssl_verify_client on; # SSL安全配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; # 根路径配置 - 指向构建后的dist目录 location / { root C:/Users/123/Desktop/nginx-1.29.2/html; # 替换为你的实际部署路径 index index.html; try_files $uri $uri/ /index.html; # 支持Vue Router history模式 } # API代理配置 - 根据vue.config.js中的proxy配置 location /api { # 注意:这里需要替换为实际的VUE_APP_BASE_API值 proxy_pass http://127.0.0.1:8081; # 对应后端服务地址 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # API文档代理配置 location ~ ^/v3/api-docs/(.*) { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 静态资源缓存优化 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } # gzip压缩 gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; }有什么错误
最新发布
10-24
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值