Vite HMR 代理后端配置
解读
国内主流 PHP 项目(Laravel、ThinkPHP、Hyperf 等)在前后端分离后,前端团队普遍用 Vite 做构建工具。开发阶段,Vite 自带的热更新(HMR)走的是 WebSocket + ES Module,端口默认 5173,而 PHP 后端通常跑在 80/443 或 9000 端口,并借助 Nginx/Apache 做虚拟主机。此时浏览器访问 https://local.test 时,静态资源与 HMR 通道必须被“无痕”转发到 Vite 服务,否则会出现“热更新 426/502/timeout”或“页面刷新后样式丢失”等典型故障。面试官问“怎么配”,既考察候选人对 Vite 底层协议的理解,也验证其能否在真实 LEMP/LNMP 环境中把“前端端口”与“后端域名”打通,同时兼顾 HTTPS 证书、路径重写、WebSocket 升级、Cookie 域一致性等细节,属于“线上出过事故”的高频考点。
知识点
- Vite HMR 协议:WebSocket 路径固定
/vite-hmr,端口默认 5173,可配置server.hmr。 - Nginx 反向代理指令:
proxy_pass、proxy_set_header Upgrade、proxy_set_header Connection、proxy_read_timeout。 - 路径重写:用
^~ /vite-hmr或location ~* \.(js|css|svg)$把静态资源和 HMR 流量分开转发,避免把 PHP 路由污染。 - HTTPS 终端:若 Nginx 负责 SSL 终端,需在
proxy_set_header X-Forwarded-Proto $scheme保证 Vite 识别wss。 - Vite 配置项:
server.origin、server.host、server.strictPort、server.https,以及.env.development中写死VITE_HMR_PROTOCOL=wss。 - 同源策略:Cookie 的
Path=/; SameSite=Lax需与代理后域名一致,否则后端 Session 刷新失败。 - 多入口场景:微前端或 Monorepo 时,用
server.proxy把/app1、/app2分别指到不同 Vite 实例,避免端口冲突。 - 性能安全:开发阶段关闭
server.cors.origin: true,生产阶段禁止把/vite-hmr暴露到外网,防止未授权热更新脚本注入。
答案
以“Laravel + Vite + Nginx + HTTPS”为例,给出可直接落地的最小可用配置,分三步完成。
第一步:Vite 端统一域名与协议
vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: ['resources/js/app.js', 'resources/css/app.css'],
refresh: true,
}),
],
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
https: false, // 由 Nginx 统一做 SSL 终端
origin: 'https://local.test', // 告诉 Vite 生成的 URL 以域名开头
hmr: {
host: 'local.test',
protocol: 'wss',
clientPort: 443, // 浏览器只认 443,不走 5173
},
},
});
.env.development
VITE_HMR_PROTOCOL=wss
VITE_HMR_HOST=local.test
第二步:Nginx 虚拟主机把 HMR 与静态资源全部代理到 Vite
/etc/nginx/conf.d/local.test.conf
server {
listen 443 ssl http2;
server_name local.test;
index index.php;
root /data/www/laravel/public;
ssl_certificate /etc/ssl/local.test.pem;
ssl_certificate_key /etc/ssl/local.test.key;
# 1. 先处理 HMR WebSocket
location ^~ /vite-hmr {
proxy_pass http://127.0.0.1:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400;
}
# 2. 再处理 Vite 生成的静态资源(带 hash 的文件)
location ~* ^.+\.(js|css|svg|png|jpg|jpeg|gif|ico|woff2?)$ {
proxy_pass http://127.0.0.1:5173;
proxy_set_header Host $host;
expires off; # 开发阶段禁用缓存
}
# 3. 其余请求交给 PHP-FPM
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
重载 Nginx:nginx -s reload
第三步:启动开发脚本
# 终端 1
php artisan serve --host=0.0.0.0 --port=8000 # 可选,若不想用 Nginx 解析 PHP
# 终端 2
npm run dev # 启动 Vite,监听 5173
浏览器访问 https://local.test,F12 网络面板应看到 wss://local.test/vite-hmr 101 状态码,且修改 resources/js/app.js 后页面无刷新即时更新,即代理成功。
拓展思考
- Docker Compose 场景:把 Vite 容器别名设为
vite,Nginx 容器通过proxy_pass http://vite:5173通信,避免宿主机端口暴露,同时利用networks.frontend隔离开发网段。 - 多后端语言共存:同一域名下既有 PHP 接口
/api,又有 Go 微服务/svc,可在 Nginx 里用location ^~ /svc把流量转发到 Go 容器,而/vite-hmr继续指向 Vite,实现“三端一域”开发。 - 生产环境禁止代理:上线前执行
npm run build,把产物放到public/build,Nginx 直接try_files $uri $uri/ /index.php,并加Cache-Control: max-age=31536000, immutable;同时把/vite-hmr路径 return 404,防止外部探测。 - 高级调试:若 HMR 偶发断连,可在
vite.config.js加server.hmr.overlay: false关闭错误遮罩,或在 Nginx 侧把proxy_buffering off避免 WebSocket 被缓存,快速定位是网络层还是 Vite 层问题。