首页 / 实战手册 / 工程化
打包上线后,JS 和 CSS 全 404
先看结论
本地 npm run build 一切正常,传到服务器后页面白屏,HTML 里的资源地址全部 404。
现象
HTML 能打开,但样式和脚本都加载失败:
GET https://gis.example.cn/assets/index-8f3a2b1c.js 404 (Not Found)
GET https://gis.example.cn/assets/index-2d7e9a44.css 404 (Not Found)
Uncaught TypeError: Failed to fetch dynamically imported module:
https://gis.example.cn/assets/map-4c1b8e2f.js
原因
| 原因 | 表现 |
|---|---|
只上传了 index.html | 所有 assets 404 |
publicPath / base 与部署目录不一致 | 资源请求到错误前缀 |
| 服务端缓存了旧 HTML,引用了已删除的 hash 文件 | 旧文件名 404,新文件名正常 |
| Nginx 没配置静态目录 | 所有静态请求落到应用路由 |
| CDN 未回源或回源路径错 | 部分节点 404 |
构建产物的文件名带内容 hash,每次发布都会变。旧 HTML 指向旧文件, 而旧文件已被新版本覆盖删除,就会出现“页面能开、资源全 404”。
排查过程
- 在服务器上
ls dist/assets,确认文件真实存在。 - 把 HTML 里的资源地址与服务器目录逐段对照。
- 看响应头是否命中 CDN 缓存,
Age很大说明拿到的是旧 HTML。
curl -I https://gis.example.cn/assets/index-8f3a2b1c.js
curl -I https://gis.example.cn/ | grep -i "cache-control\|age\|etag"
解决方案
Nginx 指向构建产物目录,并给带 hash 的资源长缓存、给 HTML 不缓存。 这是避免“新旧混用”的关键:
server {
listen 443 ssl http2;
server_name gis.example.cn;
root /var/www/gis/dist;
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
location / {
add_header Cache-Control "no-cache";
try_files $uri $uri/ /index.html;
}
}
部署时整目录替换,不要只传入口文件。用软链接切换版本,出问题可以回滚:
# 服务器上
rsync -av --delete ./dist/ /var/www/gis/releases/20261029/
ln -sfn /var/www/gis/releases/20261029 /var/www/gis/dist
nginx -s reload
前端构建基路径必须与部署路径一致。部署在 /gis/ 下时:
// vite.config.js
export default {
base: '/gis/',
build: {
outDir: 'dist',
assetsDir: 'assets'
}
};
验证
打开首页,网络面板中 JS、CSS、字体全部 200,状态列没有 404。
重复发布两次,旧浏览器刷新后仍能正常加载。
index.html 的 Cache-Control 为 no-cache,
assets 下为长缓存。
先确认文件在不在:404 有两种,文件不存在与路径不对。
在服务器上直接 ls 一眼就能分开,比反复改配置快。
经纬志(2026)。《打包上线后,JS 和 CSS 全 404》。https://www.jingweizhi.com/cookbook/build-static-404.html(访问日期 2026-11-03)。