1. 项目概述
最近在部署一个基于Vue.js和Vite的前端项目yudao-ui-go-view时,遇到了需要将应用发布到Nginx二级目录的需求。具体来说,就是要把整个前端应用部署到Nginx服务器的/big目录下,而不是常见的根目录。这种部署方式在实际项目中很常见,特别是当我们需要在同一域名下部署多个独立应用时。
这个需求看似简单,但在实际操作中却遇到了几个关键问题:Vite打包时的静态资源路径问题、路由配置问题、以及Nginx的代理配置问题。经过一番摸索和实践,我总结出了一套完整的解决方案,现在分享给大家。
2. 环境准备与配置修改
2.1 定义发布目录路径
首先,我们需要在项目中明确指定发布目录。在yudao-ui-go-view/types/vite-env.d.ts文件中,我们增加了一个环境变量VITE_PUBLISH_PATH:
typescript复制/// <reference types="vite/client" />
interface ImportMetaEnv {
// 端口
VITE_DEV_PORT: string;
// 开发地址
VITE_DEV_PATH: string
// 后端请求地址
VITE_PRO_PATH: string
// 发布目录
VITE_PUBLISH_PATH: string
}
这里有个重要细节:环境变量名最好以VITE_开头。这是因为Vite出于安全考虑,默认只会暴露以VITE_开头的环境变量给客户端代码。如果使用其他前缀,变量可能无法在客户端访问。
2.2 创建二级目录生产环境配置
接下来,我们创建一个专门用于二级目录部署的环境配置文件.env.prod2:
env复制# port
VITE_DEV_PORT = '3000'
# development 后端地址
VITE_DEV_PATH = 'http://127.0.0.1:48080'
# 后端地址,根据实际情况修改
VITE_PRO_PATH = '/big'
# 应用发布目录
VITE_PUBLISH_PATH = '/big'
# 租户开关
VITE_APP_TENANT_ENABLE=true
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true
这个配置文件中,VITE_PUBLISH_PATH指定了我们的应用将部署在/big目录下。VITE_PRO_PATH则指定了后端API的基础路径,这里同样设置为/big,意味着我们的API请求也会加上/big前缀。
提示:在实际项目中,你可能还需要创建标准的.prod(根目录部署)和.dev(开发环境)配置文件,以便在不同环境下灵活切换。
2.3 修改打包命令配置
为了支持不同的构建环境,我们在package.json的scripts节点下新增了build:prod2命令:
json复制"scripts": {
"dev": "vite --host --mode dev",
"build": "vue-tsc --noEmit && vite build --mode prod",
"build:prod2": "vue-tsc --noEmit && vite build --mode prod2",
"preview": "vite preview",
"new": "plop --plopfile ./plop/plopfile.js",
"postinstall": "husky install",
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.vue src",
"lint:fix": "eslint --ext .js,.jsx,.ts,.tsx,.vue src --fix"
}
这样,我们就可以通过npm run build:prod2命令来构建专门用于二级目录部署的版本。
3. Vite打包配置调整
3.1 修改base和outDir配置
在vite.config.ts文件中,我们需要根据环境变量动态设置base和build.outDir:
typescript复制export default ({ mode }) => defineConfig({
base: loadEnv(mode, process.cwd()).VITE_PUBLISH_PATH,
// 路径重定向
resolve: {
....
},
...
build: {
target: 'es2015',
outDir: OUTPUT_DIR + loadEnv(mode, process.cwd()).VITE_PUBLISH_PATH,
rollupOptions: rollupOptions,
brotliSize: brotliSize,
chunkSizeWarningLimit: chunkSizeWarningLimit
}
})
这里有几个关键点:
-
base配置:决定了静态资源的基础路径。设置为/big后,所有静态资源请求都会自动加上/big前缀。
-
outDir配置:指定了构建输出目录。我们将其设置为dist目录下的big子目录,这样构建结果会自动组织到正确的目录结构中。
3.2 处理路由和静态资源
在Vue Router的配置中,我们需要确保路由能正确处理二级目录。如果你的项目使用的是history模式,基本配置如下:
javascript复制const router = createRouter({
history: createWebHistory(import.meta.env.VITE_PUBLISH_PATH),
routes
})
这样,所有路由都会自动基于VITE_PUBLISH_PATH环境变量生成正确的URL。
对于静态资源引用,Vite会自动处理base路径,所以项目中直接引用静态资源即可:
html复制<img src="/assets/logo.png" />
在构建后,这会自动转换为/big/assets/logo.png。
4. 编译打包与部署
4.1 执行构建命令
在终端中执行以下命令进行构建:
bash复制npm run build:prod2
构建完成后,你会在dist目录下看到一个big子目录,里面包含了所有构建产物。你可以直接将这个big目录压缩为big.zip,方便后续部署。
4.2 Nginx配置
Nginx的配置是确保二级目录部署成功的关键。我们需要配置两个主要的location块:
nginx复制location /big/admin-api/ { ## 后端项目 - 管理后台
proxy_pass http://127.0.0.1:48080/admin-api/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /big/ {
try_files $uri $uri/ $uri/index.html /big/index.html; #router = history
}
第一个location块处理API请求,将所有/big/admin-api/开头的请求代理到后端服务。
第二个location块处理前端路由,关键点在于try_files指令。当请求的路径不存在时,它会回退到/big/index.html,这是Vue Router的history模式正常工作所必需的。
4.3 上传部署
将构建好的big.zip上传到Nginx服务器的/var/www/html目录,然后执行以下命令:
bash复制cd /var/www/html
unzip big.zip
service nginx restart
部署完成后,你的应用就可以通过http://your-domain.com/big/访问了。
5. 常见问题与解决方案
5.1 静态资源404错误
问题现象:页面能打开,但CSS、JS等静态资源加载失败,返回404。
原因分析:这通常是因为base配置不正确,或者Nginx没有正确配置静态资源路径。
解决方案:
- 检查vite.config.ts中的base配置是否与VITE_PUBLISH_PATH一致
- 确保Nginx配置中包含对静态资源的正确处理:
nginx复制location /big/assets/ {
alias /var/www/html/big/assets/;
expires 1y;
add_header Cache-Control "public";
}
5.2 页面刷新后404
问题现象:直接访问首页正常,但刷新子页面或直接访问子页面URL时返回404。
原因分析:这是Vue Router的history模式常见问题,Nginx没有正确回退到index.html。
解决方案:
确保Nginx配置中包含正确的try_files指令:
nginx复制location /big/ {
try_files $uri $uri/ /big/index.html;
}
5.3 API请求路径错误
问题现象:前端发起的API请求路径不正确,没有包含/big前缀。
原因分析:可能是VITE_PRO_PATH环境变量没有正确设置,或者前端代码中硬编码了API路径。
解决方案:
- 检查.env.prod2文件中的VITE_PRO_PATH设置
- 确保前端代码中使用环境变量构建API请求路径:
javascript复制const apiClient = axios.create({
baseURL: import.meta.env.VITE_PRO_PATH + '/admin-api'
})
5.4 开发环境与生产环境路径不一致
问题现象:开发环境工作正常,但生产环境出现路径问题。
解决方案:
可以在开发环境也使用二级目录进行测试,修改.env.dev文件:
env复制VITE_PUBLISH_PATH = '/big'
然后启动开发服务器:
bash复制npm run dev
这样可以在开发阶段就发现潜在的路径问题。
6. 高级技巧与优化建议
6.1 自动化部署脚本
为了简化部署流程,可以创建一个简单的部署脚本deploy.sh:
bash复制#!/bin/bash
# 构建项目
echo "Building project..."
npm run build:prod2
# 压缩构建结果
echo "Compressing build output..."
cd dist
zip -r big.zip big/
# 上传到服务器
echo "Uploading to server..."
scp big.zip user@yourserver:/var/www/html/
# 执行远程部署命令
echo "Deploying on server..."
ssh user@yourserver << EOF
cd /var/www/html
unzip -o big.zip
sudo service nginx reload
EOF
echo "Deployment completed!"
6.2 多环境配置管理
对于更复杂的项目,可以考虑使用更灵活的环境配置管理方案:
- 创建一个config目录,存放不同环境的配置文件
- 使用vite-plugin-environment插件动态加载配置
- 通过命令行参数指定环境类型
示例vite.config.ts修改:
typescript复制import environment from 'vite-plugin-environment'
export default defineConfig({
plugins: [
environment({
NODE_ENV: 'development',
VITE_PUBLISH_PATH: '/big',
// 其他环境变量
})
]
})
6.3 CDN集成
如果项目使用了CDN,还需要额外处理:
- 在vite.config.ts中配置CDN域名:
typescript复制base: 'https://cdn.yourdomain.com/big/'
- 修改Nginx配置,将静态资源请求重定向到CDN:
nginx复制location /big/assets/ {
return 301 https://cdn.yourdomain.com/big/assets/$request_uri;
}
6.4 性能优化建议
- 开启Gzip压缩:
nginx复制gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
- 配置长期缓存策略:
nginx复制location /big/assets/ {
expires 1y;
add_header Cache-Control "public";
}
- 启用Brotli压缩(需要Nginx支持):
nginx复制brotli on;
brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
7. 本地开发环境调试技巧
在本地开发时,你可能需要模拟生产环境的二级目录结构。以下是几个实用技巧:
- 修改本地开发服务器配置:
在vite.config.ts中,可以配置开发服务器的base:
typescript复制server: {
base: '/big/'
}
- 使用不同的环境变量启动开发服务器:
bash复制npm run dev -- --mode prod2
- 配置本地hosts文件,使用自定义域名测试:
code复制127.0.0.1 dev.yourdomain.com
然后访问http://dev.yourdomain.com:3000/big/
- 使用Chrome开发者工具的"Network conditions"功能,模拟不同的基础路径。
8. 项目结构最佳实践
为了更好的维护性,建议采用以下项目结构:
code复制yudao-ui-go-view/
├── config/
│ ├── env.dev
│ ├── env.prod
│ └── env.prod2
├── dist/
├── public/
├── src/
│ ├── assets/
│ ├── components/
│ ├── router/
│ ├── stores/
│ ├── utils/
│ └── views/
├── types/
├── vite.config.ts
└── package.json
关键点:
- 将环境配置集中放在config目录
- 按功能模块组织src目录
- 使用types目录存放类型定义
- 保持public目录干净,只放必须的静态资源
9. 版本控制与协作建议
- 在.gitignore中添加:
code复制# 环境文件
.env.*
!.env.example
# 构建输出
dist/
- 创建一个.env.example文件作为模板:
env复制# port
VITE_DEV_PORT=3000
# development path
VITE_DEV_PATH=http://127.0.0.1:48080
# production path
VITE_PRO_PATH=/big
# publish path
VITE_PUBLISH_PATH=/big
# tenant enable
VITE_APP_TENANT_ENABLE=true
# captcha enable
VITE_APP_CAPTCHA_ENABLE=true
- 在README.md中明确说明不同环境的构建命令:
markdown复制## 构建命令
- 开发环境: `npm run dev`
- 生产环境(根目录): `npm run build`
- 生产环境(二级目录): `npm run build:prod2`
10. 监控与维护
部署完成后,还需要考虑以下维护事项:
- 配置日志监控:
nginx复制access_log /var/log/nginx/big-access.log;
error_log /var/log/nginx/big-error.log;
- 设置日志轮转:
创建/etc/logrotate.d/nginx-big文件:
code复制/var/log/nginx/big-*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
/usr/sbin/nginx -s reload
endscript
}
- 配置健康检查接口:
在前端项目中添加一个简单的健康检查接口:
javascript复制// src/utils/healthCheck.js
export const healthCheck = () => {
return fetch(`${import.meta.env.VITE_PRO_PATH}/health`, {
method: 'GET'
})
}
然后在Nginx中配置:
nginx复制location /big/health {
add_header Content-Type text/plain;
return 200 'OK';
}
11. 安全加固建议
- 禁用目录列表:
nginx复制location /big/ {
autoindex off;
try_files $uri $uri/ /big/index.html;
}
- 添加安全头:
nginx复制location /big/ {
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
try_files $uri $uri/ /big/index.html;
}
- 限制HTTP方法:
nginx复制location /big/admin-api/ {
limit_except GET POST PUT DELETE {
deny all;
}
proxy_pass http://127.0.0.1:48080/admin-api/;
# 其他proxy设置...
}
12. 性能监控与分析
- 集成Web Vitals监控:
javascript复制// src/main.js
import { getCLS, getFID, getLCP } from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
- 配置Nginx访问日志格式:
nginx复制log_format big_format '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/big-access.log big_format;
- 使用Lighthouse进行性能评估:
bash复制npm install -g lighthouse
lighthouse http://yourdomain.com/big/ --view
13. 多应用共存部署
如果你需要在同一Nginx服务器上部署多个Vue应用,可以扩展这个方案:
- 为每个应用创建独立的环境文件(.env.app1, .env.app2等)
- 在package.json中添加对应的构建命令
- 配置Nginx为每个应用设置独立的location块:
nginx复制location /app1/ {
try_files $uri $uri/ /app1/index.html;
alias /var/www/html/app1/;
}
location /app2/ {
try_files $uri $uri/ /app2/index.html;
alias /var/www/html/app2/;
}
- 确保每个应用的base和VITE_PUBLISH_PATH配置正确
14. 容器化部署方案
对于更现代的部署方式,可以考虑使用Docker容器:
- 创建Dockerfile:
dockerfile复制FROM node:16 as builder
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build:prod2
FROM nginx:alpine
COPY --from=builder /app/dist/big /usr/share/nginx/html/big
COPY nginx.conf /etc/nginx/conf.d/default.conf
- 创建nginx.conf:
nginx复制server {
listen 80;
location /big/ {
try_files $uri $uri/ /big/index.html;
alias /usr/share/nginx/html/big/;
}
}
- 构建并运行容器:
bash复制docker build -t my-vue-app .
docker run -p 8080:80 my-vue-app
15. 持续集成与部署
最后,我们可以将整个流程自动化:
- 创建GitHub Actions工作流文件.github/workflows/deploy.yml:
yaml复制name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm install
- run: npm run build:prod2
- name: Deploy to Server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
source: "dist/big/"
target: "/var/www/html/"
- name: Restart Nginx
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
sudo service nginx restart
- 在GitHub仓库设置中添加必要的secrets:
- SERVER_HOST: 服务器IP
- SERVER_USER: 服务器用户名
- SSH_KEY: SSH私钥
这样,每次推送到main分支时,都会自动构建并部署最新代码。