nginx配置(以单页面应用为例)

1,066 阅读6分钟

这篇文章解决如下问题:

  1. nginx中的location块匹配规则是什么?
  2. nginx中的alais,root try_files指令理解
  3. 对应部署单页面经常遇到的配置(跨域,子路径,history路由模式的部署)
  4. 静态文件缓存的处理
  5. 什么时候会导致301,重定向到末尾加了/的路径

nginx需要知道的前置知识

一份前端部署最基本的Nginx配置文件,理解如下:

# 一个http中可以有多个server
http {
   # 配置虚拟主机
  server { 
    listen      80;  # 端口
    server_name your-domain.com; # 域名
    
    #access_log   /path/log/access.log;  #成功日志
    #error_log    /path/log/error.log;  #错误日志

    # (root指令)指定服务器块的根目录 也可放在location块中
    root /opt/front/dist;

    # location块 处理请求路径
    location / {
        # (index指令)指定默认的索引文件,Nginx将尝试按照指定的顺序查找并返回符合条件的索引文件
        # 当用户访问 http://your-domain.com/时
        # Nginx会查找/opt/front/index.html是否存在,存在返回,不存在继续查找/opt/front/index.htm
        index  index.html index.htm; 
    }
    
    location /api/ {
        # 将/api前缀的请求转发到localhost:6000可以解决前后端跨域问题
        proxy_pass http://localhost:6000;
    }

    # 404的时候(比如访问http://your-domain.com/aaa找不到指定的文件或者目录)
    # 直接重定向到/opt/front/404.html页面
    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 {
        # 精准匹配 只有访问http://your-domain.com/50x.html进入
        # 重新指定了目录,执行了nginx默认的放置文件的目录
        root   html;
    }
  }
}

理解location匹配规则

nginx location module Doc

nginx匹配由两种类型组成:

  • a prefix string: 前缀匹配 可以添加= and ^~两个修饰符

    • = 精确匹配
    • ^~ 优先于其他正则表达式匹配的 location 块,匹配到后不再进行正则匹配查找
  • a regular expression 正则匹配,可以添加~ ,~*两个修饰符

    • ~:表示正则表达式匹配,区分大小写。
    • ~*:表示正则表达式匹配,不区分大小写。
  1. 前缀匹配(prefix locations),最长位置匹配被记住(没有遇到修饰符,走2
    • 如果最长位置匹配遇到 = 修饰符,搜索停止
    • 如果最长位置匹配具有 ^~,搜索停止
  2. 正则匹配,按配置文件中定义的顺序进行匹配。
  3. 如果第2条规则产生匹配的话,结果被使用。否则,使用第1条规则的结果。
location = / {
  [ configuration A ]
}

location / {
  [ configuration B ]
}

location /documents/ {
  [ configuration C ]
}

location ^~ /images/ {
  [ configuration D ]
}

location ~* .(gif|jpg|jpeg)$ {
  [ configuration E ]
}

请求 / 将会精确匹配 A,请求 /index.html 将会匹配 B,请求 /documents/document.html 将会匹配 C,请求 /images/1.gif 将会匹配 D,请求 /documents/1.jpg 将会匹配 E。

下面的所有例子都以上面Nginx配置文件作为基础

(history|hash)router

hash模式

当用户访问 http://your-domain.com#subpath,由于 #后的这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理,上面的Nginx的基础配置文件中实际上匹配到的就是Nginx的location / 块,访问的就是根目录的index.html

history

当用户访问 http://your-domain.com/subpath,Nginx的基础配置文件中实际上匹配到的就是Nginx的location / 块,这时候nginx默认会去找/opt/front/dist下是否有subpath文件或者subpath目录下的index.html index.htm。而我们是单页应用,只有根目录一个index.html。

解决:使用Nginx的try_files指令添加一个简单的回退路由, 按照一定的顺序处理请求,将匹配不到的都指向根index.html

location / {
  try_files $uri $uri/ /index.html;
}

如上的配置当你访问http://your-domain.com/subpath,进入这个location块,$uri为nginx的变量,表示请求的URI,这里就是Nginx在寻找subpath文件在不,不在继续,$uri/ 表示请求的 URI 对应的目录,Nginx寻找subpath目录在不,不在寻找第三个到/index.html,这里使用的是绝对路径,代表根目录下的index.html

跨域处理

proxy_pass

前端web使用nginx服务部署在your-domain.com,后端服务localhost:6000

当你在浏览器访问 http://your-domain.com里面请求的API地址是http://your-domain.com/api/getUsers实际上是访问http://localhost:6000/api/getUsers

 location /api/ {
      # 将/api前缀的请求转发到localhost:6000可以解决前后端跨域问题
      proxy_pass http://localhost:6000;
  }

本地开发以vite为例,配置

server: {
    proxy: {
      "/api": {
        target: "http://localhost:6000",
      },
    },
  }

实现CROS

后端服务localhost:6000不支持CROS,前端服务http://your-domain.com

当你在浏览器访问 http://your-domain.com里面请求API的地址是 localhost:9001/api/getUsers=》实际访问 http://localhost:3000/api/getUsers以下配置是将不支持CROS的后台服务支持CROS (对于跨域的访问,浏览器每次都会发送一次OPTIONS预检请求,去询问后台是否支持跨域)

server {
    listen       9001;
    server_name  localhost;
    location / {
        proxy_pass  http://localhost:6000;
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' 'http://your-domain.com';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain; charset=utf-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        add_header 'Access-Control-Allow-Origin' 'http://your-domain.com';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
    }
}

实际中后台服务部署会加入Access-Control-Allow-Origin等从而实现CROS,上面只是举例后台服务没有实现CROS而已

子路径部署

假设要把开发的前端项目部署在http://your-domain.com/web/h5,首先前端的打包需要支持/web/h5,以vite作为例子,如下

import { defineConfig } from "vite";
export default defineConfig({
  base: "/web/h5/"
});
location /web/h5 {
    try_files $uri $uri/ /index.html;
}

遇到一个问题,请求的静态文件会都返回index.html。比如请求/web/h5/assets/index-B7RyahvK.js文件,$uri $uri/找不到,因为我们打包是直接将静态文件放在根目录下的rootDirectory/assets/index-B7RyahvK.js,所以选择最后的回退路由rootDirectory/index.html

这就要将location加入alias指令了,当请求/web/h5/assets/index-B7RyahvK.js时,nginx查找的是/opt/front/dist/assets/index-B7RyahvK.js就可以正确返回了

root /opt/front/dist;
location /web/h5 {
    alias /opt/front/dist;
    try_files $uri $uri/ /index.html;
}

root vs alias 指令

  • alias 只能配置在location 中, 而root 可以配置在 server, http 和 location 中
  • 使用 root 时, 服务器里真实的资源路径是 root 的路径拼接上 location 指定的路径,alias是不包括,举例如下:
location /i/test/ {
    root /data/w3;
}

如上配置,当请求/i/test/top.gif进入这个location后,Nginx查找的地址是/data/w3/i/test/top.gif

location /i/test/ {
    alias /data/w3/;
}

如上配置,当请求/i/test/top.gif进入这个location后,Nginx查找的地址是/data/w3/top.gif

no-cache index.html

http caching Doc

  1. HTTP协议被设计尽可能多地缓存,从而提高效率
  2. http caching 建议都显式指定Cache-Control,不指定会进入Heuristic caching启发式缓存机制,条件满足的情况下也会被缓存和重用,不可控,由客户端的实现会有差异
  3. no-cache不是不缓存,而是每次强制发送一个条件请求(conditional request 含有 If-Modified-Since``If-None-Match请求头)去确认资源是否已更改,返回304不会带有响应体,no-store才是不缓存

对于采用Nginx部署我们的单页面应用时,最有效的是搭配index.html 为no-cache,其他的静态资源搭配现有hash打包,可以设置为Cache-Control: public, max-age=31536000, immutable配置适当的缓存失效时间,添加 immutable可以防止重新加载时进行发送请求进行验证。配置如下:

location / {
    if ($request_filename ~* ^.*[.](html|htm)$) {
        add_header Cache-Control "no-cache";
    }
    try_files $uri $uri/ /index.html;
}

location ~ ^/assets/.*.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}

扩展

关于URL末尾的 /

浏览器访问的URL中加入 / vs 不加 /

  1. 对于访问域名http://your-domain.com,如果末尾没有加斜杠,浏览器会自动在请求中添加斜杠,以确保请求的是域名下的默认资源或默认页面
  2. 直接访问子路径http://your-domain.com/subpath/ Nginx会去查找/opt/front/dist/subpath文件夹下有没有指定的索引文件index.html
  3. 对于子路径http://your-domain.com/subpath浏览器不会自动添加/,nginx会去查找/opt/front/dist下有没有subpath文件,没有就查找有没有subpath这个目录,(两者都没有返回404)有这个目录的情况下返回301,redirect到subpath/去查找目录下有没有指定的默认索引文件 index.html

location块末尾的 /

root /opt/front/dist;
# (1)
location /user/ {
    proxy_pass http://user.example.com;
}
# (2)
location = /user {
    proxy_pass http://login.example.com;
}

采用(1)location块,用户访问http://your-domain.com/user,nginx服务器接收到 /user的URI,发现没有user文件,user目录也没有。会返回404 not found。发现有user这个目录,301重定向/user/。如果不希望重定向,直接采用(2)的精确匹配

总结: Nginx对于没有加/的子路径,如/subpath,找寻不到subpath文件,却有这个subpath文件夹,会返回一个301给浏览器,重定向到含有/subpath/的地址

PS: 上面的所有配置都是最基本的配置,可以让你了解基本的单页面部署涉及到的一些知识,基本是Nginx的,实际业务中更为复杂