Code前端首页关于Code前端联系我们

HTTP重定向到HTTPS

terry 1周前 (08-14) 阅读数 921 #Vue
文章标签 HTTP重定向HTTPS

Vue3开发部署到Nginx会遇到哪些常见又容易踩的坑?怎么解决这些问题?

最近刚把公司内部用Vite+Vue3做的项目从开发机搬到云服务器,折腾了整整三个晚上才搞定——一开始history模式刷新404,接着Vite打包出来的assets文件夹里的图片、字体全找不到,后来加了微前端子应用后跨域又冒出来一堆奇奇怪怪的问题,查了很多零散的资料,踩过不少重复的坑,今天就把整理出来的实用问答分享给大家,不管你是第一次部署Vue3还是微前端子应用,应该都能用上。

基础问题1:Vue3 SPA用history模式,为什么刷新浏览器就报404?怎么彻底解决?

这个坑可以说是Vue部署的入门级拦路虎了,但很多人第一次遇到还是会慌,甚至随便改回hash模式就完事——但hash模式的URL带#号太丑了,要是做SEO友好点的项目(虽然SPA本身SEO弱,但静态预渲染或轻量SSR还是能用history模式的),或者需要分享给客户看,#号真的掉分。

首先得搞懂为什么会404:SPA是单页面应用,整个应用只有一个index.html入口文件,所有的页面跳转其实都是JS在浏览器端操控DOM完成的,没有真正向服务器发送“请求不同HTML文件”的指令,但当你刷新浏览器时,相当于强行让浏览器访问当前URL对应的服务器路径——比如你的项目路由是https://example.com/dashboard/user/1,服务器上根本没有dashboard/user/1这个文件夹或HTML文件,Nginx找不到东西就直接返回404了。

彻底解决的办法只有一个:让Nginx把所有找不到对应静态资源的请求,都强行重定向回根目录的index.html,交给Vue3的路由去处理

那具体怎么配置?很多教程会直接给一行try_files,但要是项目没部署在根目录,或者有个别真实的静态文件(比如服务器根目录下有个单独的download文件夹放PDF),直接照搬就会把真实文件也给重定向了,反而出问题,正确的完整配置应该是这样的:

server {
    listen 80;
    server_name example.com; # 换成你的域名或服务器IP
    root /var/www/vue3-project/dist; # 换成你Vite/npm run build生成的dist文件夹的绝对路径
    index index.html index.htm;
    # 核心配置:处理SPA history路由刷新404
    location / {
        try_files $uri $uri/ /index.html;
        # 解释一下这三个参数:
        # $uri:先找当前URL对应的真实文件(比如图片、CSS)
        # $uri/:找不到文件的话,找对应的文件夹下的index.html(比如单独的download文件夹里的index.html)
        # /index.html:前两个都找不到,就重定向到根目录的index.html
    }
    # 可以加一个单独的location块,处理真实存在的其他静态资源,比如刚才说的download文件夹
    location /download/ {
        alias /var/www/downloads/; # 注意这里用alias不是root,alias会把/download/替换成/var/www/downloads/
        autoindex on; # 要不要显示文件列表?看需求,内部项目可以开,对外的记得关
    }
}

这里要特别提醒两个容易踩的小细节:一个是root和alias的区别——root会把URL路径拼在root路径后面,alias会替换location匹配的前缀;另一个是如果你的Vue3项目在Vite.config.js里设置了base路径(比如部署在二级目录https://example.com/my-vue3-app/),那try_files的最后一个参数也要改成/my-vue3-app/index.html,而且Vite.config.js里的base一定要和Nginx里的root/alias配置对应上,不然静态资源也会找不到。

基础问题2:Vite打包Vue3后,assets里的图片、字体全404,要么就是显示成空白?

这个问题是Vite用户专属的坑(Webpack用户一般不会遇到这么直接的),核心原因还是Vite的构建机制和base路径的配合问题——Vite默认会把assets里的静态资源文件名加上哈希值,而且引用路径是相对的,但要是base路径没配对,或者Nginx的静态资源缓存头没加好,就很容易出问题。

首先排查第一个原因:Vite.config.js里的base路径配置,如果你的项目部署在根目录,base可以留空或者设为'/';如果部署在二级目录,必须设为'/你的二级目录名/',比如刚才说的/my-vue3-app/,注意前后都要有斜杠,举个例子,正确的Vite.config.js(JS版本)配置片段:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
  plugins: [vue()],
  // 部署在根目录
  // base: '/', 
  // 部署在二级目录
  base: '/my-vue3-app/',
  // 有时候还需要设置assetsDir,但Vite默认是'assets',不用改,除非你有特殊需求
  build: {
    // assetsDir: 'static', 
  }
})

配置完base路径后,重新npm run build,然后看一下dist/index.html里的资源引用路径——比如是不是变成了/my-vue3-app/assets/logo.abc123.png,如果是的话就对了,接下来只要把整个dist文件夹的内容上传到服务器对应的二级目录就行。

然后排查第二个原因:Nginx里的静态资源缓存时间太短,或者Content-Type设置错了,有时候虽然路径对了,但浏览器加载图片时服务器返回了错误的Content-Type(比如把.png返回成text/plain),或者浏览器缓存了旧的、失效的资源,也会导致空白或404,可以加一个单独的location块专门处理静态资源:

location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
    root /var/www/vue3-project/dist;
    # 缓存时间设为30天,因为Vite打包的文件名带哈希值,修改后哈希值会变,不会有缓存过期的问题
    expires 30d;
    # 强制浏览器验证资源是否过期,不过因为哈希值变了,其实这个可以不加
    add_header Cache-Control "public, immutable";
    # 确保Content-Type正确,一般Nginx默认的mime.types里有,但可以加上以防万一
    include /etc/nginx/mime.types;
}

这里的Cache-Control加了immutable属性,这个是HTTP/1.1的一个新属性,告诉浏览器这个资源的哈希值不会变,只要缓存了就不用再验证,直接用就行,能大大减少服务器的请求压力——这个算个小的新兴优化点吧,最近两年的主流浏览器(Chrome、Firefox、Safari 14+)都支持了。

进阶问题3:Vue3做了微前端(比如用qiankun),主应用部署在根目录,子应用部署在二级目录,Nginx怎么配置才不会跨域、路由不会乱?

这个问题是最近两三年很多公司项目重构时遇到的——主应用可能是Vue2或者React,子应用用Vue3,分成多个团队开发,然后部署到同一个域名下,很多人照搬主应用的配置把子应用部署上去,要么就是跨域(qiankun会请求子应用的index.html和JS/CSS,跨域的话直接拦截),要么就是子应用的路由刷新后404,要么就是主应用和子应用的路由冲突。

首先得明确几个前提:第一,主应用和子应用最好部署在同一个域名下的不同二级目录,这样跨域问题会少很多(如果必须部署在不同域名,要加CORS配置,但不同域名的qiankun微前端可能会有其他问题,比如样式隔离、cookie共享,所以优先推荐同域名二级目录);第二,子应用在Vite.config.js里的base路径必须是子应用的二级目录名,而且qiankun的注册配置里的entry也要对应;第三,子应用的路由必须是相对路径的history模式,或者用hash模式,但推荐history模式。

那具体怎么配置Nginx?假设主应用部署在https://example.com/,子应用A部署在https://example.com/sub-app-a/,子应用B部署在https://example.com/sub-app-b/,完整的配置应该是这样的:

server {
    listen 80;
    server_name example.com;
    # 处理主应用
    root /var/www/main-app/dist;
    index index.html index.htm;
    location / {
        try_files $uri $uri/ /index.html;
        # 主应用可能也需要请求子应用的资源,所以这里可以加CORS,但同域名的话其实不用
    }
    # 处理子应用A,二级目录/sub-app-a/
    location /sub-app-a/ {
        alias /var/www/sub-app-a/dist/; # 注意alias的最后也要有斜杠,不然路径会错
        index index.html index.htm;
        try_files $uri $uri/ /sub-app-a/index.html; # 子应用的index.html在二级目录下
        # 同域名的话CORS其实不用,但qiankun有时候会加一些特殊的请求头,所以可以加上以防万一
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Custom-Header';
        # 子应用的静态资源缓存,和主应用一样
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
            expires 30d;
            add_header Cache-Control "public, immutable";
            include /etc/nginx/mime.types;
        }
    }
    # 处理子应用B,二级目录/sub-app-b/,配置和子应用A一样,只是alias和try_files的路径改一下
    location /sub-app-b/ {
        alias /var/www/sub-app-b/dist/;
        index index.html index.htm;
        try_files $uri $uri/ /sub-app-b/index.html;
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Custom-Header';
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
            expires 30d;
            add_header Cache-Control "public, immutable";
            include /etc/nginx/mime.types;
        }
    }
    # 刚才说的真实静态资源文件夹
    location /download/ {
        alias /var/www/downloads/;
        autoindex off; # 对外项目记得关
    }
}

这里还要特别提醒三个微前端专属的细节:第一个是子应用在qiankun的注册配置里,entry要写成完整的二级目录路径,/sub-app-a/'或者'//example.com/sub-app-a/';第二个是子应用的路由必须是相对路径的history模式,比如用Vue Router4的createWebHistory('/sub-app-a/'),不能留空或者设为'/';第三个是子应用在Vite.config.js里还要加一些qiankun专属的插件配置,比如vite-plugin-qiankun,不然子应用无法在qiankun环境下正常加载——这个虽然不是Nginx的问题,但很多人遇到跨域或白屏时会误以为是Nginx的错,所以顺便提一下。

高阶问题4:Vue3 + Vite做了轻量SSR(比如用Vite SSR Plugin或者Nuxt3的静态预渲染+动态路由?不对,静态预渲染不算纯SSR,纯SSR的话比如用Vite的官方SSR模板),Nginx怎么配置反代SSR服务器?

纯SSR的Vue3项目,部署时不能只放dist文件夹了,需要启动一个Node.js的SSR服务器(比如用Express或者Koa),然后用Nginx做反向代理——这样可以让Nginx处理静态资源(JS、CSS、图片这些,Node.js处理静态资源效率低),剩下的动态HTML渲染请求交给Node.js的SSR服务器。

假设你的SSR服务器监听在本地的3000端口,静态资源文件夹在/var/www/vue3-ssr-project/client-dist/,完整的Nginx配置应该是这样的:

server {
    listen 80;
    server_name example.com;
    # 先处理静态资源,直接从client-dist里拿,不用经过SSR服务器,效率更高
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        root /var/www/vue3-ssr-project/client-dist;
        expires 30d;
        add_header Cache-Control "public, immutable";
        include /etc/nginx/mime.types;
    }
    # 剩下的所有请求(包括动态HTML渲染)都反代到本地的3000端口
    location / {
        proxy_pass http://127.0.0.1:3000;
        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; # 告诉SSR服务器当前是HTTP还是HTTPS,很重要,不然可能会有重定向循环或者cookie丢失的问题
        # 超时时间设长一点,SSR渲染有时候会比较慢
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

这里的X-Forwarded-Proto非常重要——如果你的SSR服务器检测不到HTTPS,可能会把HTTPS的请求重定向到HTTP,或者生成的HTML里的静态资源路径是HTTP的,导致浏览器报“混合内容”的错误,如果你的服务器已经配置了HTTPS(强烈建议配置),那可以把listen 80改成listen 443 ssl,然后加上SSL证书的配置,再把listen 80的server块改成重定向到HTTPS的:

    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}
# HTTPS server块
server {
    listen 443 ssl;
    server_name example.com;
    # SSL证书配置,换成你自己的证书路径
    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1.2 TLSv1.3; # 只支持安全的TLS版本
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    # 剩下的配置和上面的纯HTTP SSR配置一样
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        root /var/www/vue3-ssr-project/client-dist;
        expires 30d;
        add_header Cache-Control "public, immutable";
        include /etc/nginx/mime.types;
    }
    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

SSR服务器建议用PM2来管理,这样可以保证服务器崩溃后自动重启,而且可以设置集群模式,利用多核CPU的优势——这个虽然不是Nginx的问题,但也是部署SSR项目的必要步骤。

避坑小技巧总结

最后给大家总结几个Vue3部署到Nginx的避坑小技巧,都是我自己踩出来的血泪教训:

  1. 每次修改Nginx配置后,一定要先运行nginx -t检查语法是否正确,然后再运行nginx -s reload重载配置,不然可能会导致整个Nginx服务器挂掉。
  2. 如果遇到路径问题,可以在浏览器的开发者工具里的Network面板看请求的URL是什么,然后对比服务器上的文件路径,这样很容易找到问题所在。
  3. Vite打包的文件名带哈希值,修改代码后哈希值会变,所以不用手动清理浏览器缓存,直接重载页面就行,但要确保Nginx的静态资源缓存时间设得足够长,避免频繁请求服务器
  4. 同域名的微前端项目优先推荐二级目录部署,跨域问题会少很多;如果必须跨域,要仔细配置CORS,包括预检请求(OPTIONS请求)的处理
  5. 纯SSR项目一定要用Nginx做反向代理处理静态资源,Node.js处理静态资源的效率太低了,而且容易被攻击

好了,以上就是我整理的Vue3开发部署到Nginx的常见问题和解决办法,希望能帮到大家,如果还有其他问题,欢迎在评论区留言讨论。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门