经纬志
首页 / 实战手册 / 工程化

打包上线后,JS 和 CSS 全 404

先看结论

本地 npm run build 一切正常,传到服务器后页面白屏,HTML 里的资源地址全部 404。

最后验证 2026-11-03 · 多技术栈 · Nginx 1.24 · 更新 2026-10-29 · 引用本文

现象

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”。

排查过程

  1. 在服务器上 ls dist/assets,确认文件真实存在。
  2. 把 HTML 里的资源地址与服务器目录逐段对照。
  3. 看响应头是否命中 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)。