🚀 第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 的匹配规则,把"哪些请求走哪套处理"彻底讲透。