打包后地图资源 404?publicPath 与静态路径排查

1 阅读5分钟

TL;DR:Vue3 + Vite 项目本地 npm run dev 一切正常、build 后部署到 GitHub Pages / Gitee Pages / Nginx 子路径就地图白屏、资源 404——先看 Network 里 404 的 URL 缺没缺子路径前缀,缺了就是 base 与静态路径问题。排查顺序和正确写法见下,文末 Demo 可一键切换错误 / 正确两种状态。

上个月交付一个水利一张图,本地跑得好好的,推到客户内网 Nginx 的 /gis/ 子路径下,打开直接白屏。F12 一看,/assets/index-xxx.js 全部 404—— 资源引用写死在根路径,部署到子路径当然找不到。

这种坑的典型特征就是本地测不出来、一部署就炸,而且 404 的不是业务代码,是打包产物的资源引用。这篇把排查顺序和根治写法一次讲清,本机 Chrome 实测,2026-10。

先看 404 的 URL 长什么样

打开 F12 Network,过滤 404,盯着失败的请求看两秒:

  • https://host/gis/assets/index-xxx.js → 带上了子路径,问题在资源本身不存在或路径写错;

  • https://host/assets/index-xxx.js → 缺了 /gis/ 前缀,就是 base / 静态路径问题,直接往下走。

这一步决定了排查方向,别一上来就翻配置文件。90% 的 "部署后白屏" 是第二种形态。

Vite 默认 base: '/',产物引用写死根路径

Vite 默认把资源引用写成绝对根路径,build 后打开 dist/index.html 能看到:

<script type="module" src="/assets/index-BCH-ek3h.js"></script>

部署到 https://host/gis/ 后,浏览器按规则把 /assets/... 拼成 https://host/assets/...—— 子路径 gis 被丢掉了,404。这不是你代码的问题,是构建配置没适配部署位置。

正确做法,二选一:

`vite.config.ts`
export default defineConfig({
  // 方案一:相对路径,任何子路径都能部署
  base: './',
})
`vite.config.ts`
// 方案二:明确指定部署子路径(部署位置固定时更稳)
export default defineConfig({
  base: '/gis/',
})

base: './' 会把产物引用改成相对路径(./assets/index-xxx.js),相对 index.html 所在目录解析,部署到哪层子路径都不会丢。这个 demo 工程就是用 hash 路由 + 相对 base 部署的,Gitee Pages 的 /仓库名/ 子路径下直接能跑。

代码里写死的根绝对路径,grep 一下就有数

base 改完了,再 grep 一下源码里有没有手写根路径:

grep -rn "src=\"/" src/        # 模板里的图片资源
grep -rn "new URL('/" src/     # 动态 URL
grep -rn "fetch('/" src/       # 接口 / 静态文件请求

常见三处:<img src="/assets/xxx">、fetch('/api/...')、new URL('/xxx', import.meta.url)。这些路径不受 base 影响,部署到子路径后照样 404。地图相关的静态资源(瓦片、GeoJSON、模型)要用构建期变量拼:

<script setup lang="ts">
// BASE_URL 由 base 配置注入,部署到 /gis/ 时它是 '/gis/'
const tileUrl = import.meta.env.BASE_URL + 'data/tiles/{z}/{x}/{y}.png'
</script>

<template>
  <img :src="import.meta.env.BASE_URL + 'img/底图示意.png'" alt="底图资源" />
</template>

BASE_URL 是 Vite 构建时注入的部署根路径变量,跟着 base 走 ——base 是 /gis/ 它就是 /gis/,base 是 ./ 它就是相对值。所有 "会变成 URL 的静态资源" 统一走它,不再手写。

public 目录资源没拼前缀

public/ 下的文件会原样拷进 dist/ 根目录。很多人写 <img src="/logo.png"> 本地没事,部署到子路径一样 404。public 资源的引用规则是:

<!-- 错误:手写根路径 -->
<img src="/img/logo.png" />

<!-- 正确:BASE_URL + public 相对路径 -->
<img :src="import.meta.env.BASE_URL + 'img/logo.png'" />

demo 里我模拟了完整场景:资源放在 public/docs/assets/(相当于部署在 /docs/ 子路径),错误写法请求 /assets/w5-tile.png(缺前缀,加载失败),正确写法请求 /docs/assets/w5-tile.png(命中,正常显示)—— 按钮一键切换,打开就能对比。

001.png

002.png

隐藏坑:本地 dev 的 SPA fallback 会掩盖 404

这是最容易误判的一环。本地 npm run dev 时,Vite dev server 对未命中的路径会返回 index.html(SPA fallback)—— 你写错路径,浏览器拿到的不是 404,而是一段 HTML。图片拿到 HTML 会解码失败照样裂开,JS 引用拿到 HTML 照样白屏,但 Network 面板里看不到 404,看着像 "资源在但没用上",把排查方向带偏。

所以验证要绕开 dev:

  1. npm run build 后,直接在 dist/ 里双击 index.html 看效果(相对 base 时本地文件协议也能跑);

  2. 或者 npm run preview,它就是模拟生产环境的产物服务;

  3. 部署后再看一次 Network,这时候的 404 才是真的。

另外提醒一句:只改 base 不够。base 只管构建产物的引用,你代码里手写的绝对路径(原因二)和 public 资源引用(原因三)都得跟着改,缺一个都会在部署环境露出 404。改完用上面的 grep 三连过一遍最省事。

小结

按这个清单过一遍,部署后资源 404 基本能一次修完:

  1. Network 里看 404 URL 缺没缺子路径前缀 → 判定是不是 base 问题;

  2. vite.config.ts 配 base: './' 或具体子路径;

  3. grep 源码里写死的 /assets、/img、fetch('/,改用 import.meta.env.BASE_URL 拼接;

  4. public 资源引用同样走 BASE_URL;

  5. 别在 dev 里验证,用 dist 双击或 preview 复现真实部署行为。

路径这关过了,再遇到白屏就回到渲染本身去查 —— 容器、初始化时机、底图源,那是另一条线。

📦 Demo 见 jgwx-notes-code 工程 /vite-asset-404 路由(npm run dev 后浏览器直接打开,错误 / 正确一键切换即可复现)。