🚀 第05章 · 静态站点与缓存

静态文件 + 缓存策略

静态站点与缓存

不是所有请求都要落到后端。图片、CSS、JS、字体这些"静态资源"变化慢、由 Nginx 直接送出最好——快、省后端资源、支持缓存。本章讲如何让 Nginx 直接服务静态文件,以及两层缓存(浏览器缓存 + 服务端代理缓存)怎么配置。

静态与动态的分工

  • 动态请求(登录、下单、生成页面)交给后端语言处理,走 proxy_pass;
  • 静态资源(/static/app.js、/logo.png)由 Nginx 直接从磁盘发送,不走后端。

本机是纯反向代理架构,静态文件由后端(Node 等)托管、经 Nginx 转发。但只要是"Nginx 直接出文件"的站点(比如纯前端构建产物、SPA 的 dist/),就靠下面这套配置。

root 与 alias

二者都用来指定文件所在目录,最容易混淆:

指令 作用 URL 与磁盘路径对应
root /var/www/mydemo; 相对目录拼接 URI /img/a.png → /var/www/mydemo/img/a.png
alias /var/www/img; 相对 location 路径替换 location /img/ 时,/img/a.png → /var/www/img/a.png
server {
    listen 80;
    server_name mydemo.com;

    root /var/www/mydemo;   # 整体目录
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

易错 root 是把 URI 原样拼到目录后;而 alias 是"把我这个 location 匹配到的前缀整体换掉"。在 location /img/ 里用 root /var/www 会去找 /var/www/img/...,而用 alias /var/www/img; 则只替换掉 /img 这段前缀。alias 末尾常以目录结尾、不带斜杠,和 root 语义完全不同,换用前务必想清楚。

index 与 try_files

  • index index.html; —— 访问目录时的默认文件。
  • try_files $uri $uri/ =404; —— 依次尝试"真实文件 → 目录 → 兜底(404 或重写回 index.html)"。

SPA 路由(刷新 /profile 仍要回到 index.html)常用:

location / {
    root /var/www/mydemo;
    try_files $uri $uri/ /index.html;   # 找不到就交给 index.html 走前端路由
}

理解 这里兜底 /index.html 不是返回 404,而是让前端路由器接管 URL。这是 Vue/React 单页应用部署的标配,否则刷新某个前端路由页面会 404。

让 Nginx 高效送文件

磁盘会话在 http 块里开启:

sendfile on;        # 内核直接把文件搬进 socket,跳过用户态拷贝(本机已开)
tcp_nopush on;      # 攒够一个包再发,减少小包
tcp_nodelay on;     # 小包立刻发,降低延迟,常用在 keepalive

理解 sendfile 是"零拷贝"的典范:文件从磁盘到网卡,全程不经用户态缓存,CPU 占用极低。tcp_nopush 和 tcp_nodelay 看似矛盾——其实前者针对大文件批量发送、后者针对小包的实时性,现代内核能协调好。

第一层缓存:浏览器本地缓存

给静态资源设置 expires,让浏览器在一段时间内直接复用本地副本,减少重复请求:

location /static/ {
    alias /var/www/mydemo/static;
    expires 7d;
    add_header Cache-Control "public";
}
指令 作用
expires 7d; 给响应加 Expires 头,并让 Cache-Control: max-age 为 7 天
expires -1; 立即过期,强制每次再验证
expires off; 不加缓存头
add_header Cache-Control "public"; 允许共享缓存(CDN/代理)缓存

易错 HTML 页面不要长缓存——用户会看到很久都不更新的旧页面。经验做法:带 hash 的资源用 expires 1y(文件名一变就是新资源,永不冲突);HTML 本身用 expires -1 或 no-cache,保证每次重新拉取最新。

gzip 压缩

静态文本文件(HTML/CSS/JS/JSON)压缩后体积能小一半以上。本机 nginx.conf 已经 gzip on;:

gzip on;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript image/svg+xml;
gzip_min_length 1024;
gzip_vary on;
指令 作用
gzip on; 开启 gzip
gzip_comp_level 6 压缩级别 1-9,越高越省带宽越耗 CPU
gzip_types 默认只压 text/html,其它需显式列出
gzip_min_length 1024 太小的文件不压,否则得不偿失
gzip_vary on; 加 Vary: Accept-Encoding,防止缓存错配

理解 图片(JPEG/PNG/WebP)本身就是压缩格式,再 gzip 收益极小甚至变大,所以 gzip_types 里不要列图片。只压文本类资源。

第二层缓存:Nginx 代理缓存

如果后端响应较慢或较重(比如聚合接口),可以让大家一起缓存。用 proxy_cache:

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:10m inactive=60m max_size=1g;

server {
    location /api/ {
        proxy_pass http://backend;
        proxy_cache mycache;
        proxy_cache_valid 200 302 10m;
        proxy_cache_valid 404 1m;
        add_header X-Cache-Status $upstream_cache_status;  # 看命中与否
    }
}
指令 作用
proxy_cache_path 定义缓存目录、分级、内存索引区(必须放 http 层且唯一)
proxy_cache 为请求启用哪个缓存区
proxy_cache_valid 按状态码设定缓存时长
$upstream_cache_status 命中结果(HIT/MISS/BYPASS),用来调试

易错 proxy_cache_path 里的 keys_zone 名称是全配置唯一的;levels=1:2 表示缓存文件按两级目录散列存放,避免单目录文件过多。清理时用 curl -X PURGE(需 proxy_cache_purge 模块)或直接删缓存目录。

本机场景:敏感路径别缓存

本机用 location 正则把后端的 node_modules、scripts/、package.json 直接拦成 403。含义很清晰:静态/源码敏感路径宁可 403,也不允许被代理到后端,更不允许被缓存泄漏。

location ~ ^/(node_modules|scripts)/ { return 403; }
location = /package.json { return 403; }
location = /package-lock.json { return 403; }

安全 这组规则放在 server 块、location / 之前,利用"正则 location 优先于普通前缀"的规则,让 /scripts/x.js、/package.json 这类请求在代理/缓存发生之前就被拦下。生产上反代后端时,先列出"绝不能外泄的路径清单",用 location 正则封掉。

小结

Nginx 既做反向代理,也能高效服务静态文件:root/alias 定位文件、try_files 处理 SPA 路由、sendfile 零拷贝送文件;再叠加两层缓存——expires 给浏览器、proxy_cache 给 Nginx;gzip 压缩文本资源。缓存策略的关键是分清哪些能长缓存、哪些必须每次更新(HTML 短、带 hash 的资源长)。下一章深入 location 的匹配规则,把"哪些请求走哪套处理"彻底讲透。