Vite 中 SVG 404 的幕后黑手:你真的懂静态资源处理吗?

开发 React 项目的时候,遇到个挺典型的问题:页面上的 SVG 图标全部显示不出来,控制台刷刷刷报一堆 404 错误。

看了下错误信息,浏览器在请求这些路径:

  • foundry-cost-icon.svg:1 Failed to load resource: the server responded with a status of 404 ()
  • foundry-roi-icon.svg:1 Failed to load resource: the server responded with a status of 404 ()
  • foundry-deploy-icon.svg:1 Failed to load resource: the server responded with a status of 404 ()
  • foundry-update-icon.svg:1 Failed to load resource: the server responded with a status of 404 ()

页面上"为何选择 Foundry"那个区块的 4 个图标,全都加载失败了。

问题根源:误用源码路径作为资源 URL

翻了下代码,发现问题在这里:

tsx 复制代码
// FoundryPage.tsx (错误写法)
<img
  src="/src/assets/icons/foundry/foundry-roi-icon.svg"
  alt="ROI icon"
  className="w-full h-full"
/>

乍一看没问题啊,文件确实在 src/assets/icons/foundry/ 目录下:

bash 复制代码
$ ls -la src/assets/icons/foundry/
total 32
-rw-r--r--@ 1 tsk  staff   373 Nov  4 18:28 foundry-cost-icon.svg
-rw-r--r--@ 1 tsk  staff  1870 Nov  4 18:28 foundry-deploy-icon.svg
-rw-r--r--@ 1 tsk  staff   955 Nov  4 18:28 foundry-roi-icon.svg
-rw-r--r--@ 1 tsk  staff   812 Nov  4 18:28 foundry-update-icon.svg

但这就是问题所在------我把源码路径当成了浏览器访问的 URL 路径

Vite 的资源处理机制

想明白这个问题,得先理解 Vite 怎么处理静态资源的:

方式 1:import 导入(推荐用于组件内)

tsx 复制代码
import logoSvg from '@/assets/logo.svg';

function Header() {
  return <img src={logoSvg} alt="Logo" />;
}

工作原理

  • Vite 会把 SVG 文件当作模块处理
  • 构建时自动生成带 hash 的文件名(如 logo-a3f2b9c1.svg
  • logoSvg 变量保存的是构建后的实际路径

优点

  • 自动 hash,利于缓存
  • TypeScript 类型支持
  • 构建时优化(压缩、转换)

缺点

  • 每个图标都要写一行 import
  • 动态路径不好处理

方式 2:放在 public 目录(推荐用于公共资源)

tsx 复制代码
// 文件位置:public/images/icons/arrow.svg
function Icon() {
  return <img src="/images/icons/arrow.svg" alt="Arrow" />;
}

工作原理

  • public/ 目录下的文件会被原样复制到构建输出目录
  • URL 路径直接对应文件路径
  • 没有任何处理和转换

优点

  • 不需要 import
  • 路径可以动态拼接
  • 适合大量图标/静态文件

缺点

  • 没有文件 hash(缓存管理靠手动版本号)
  • 不会被构建优化
  • 文件引用错误在构建时检测不到(只能运行时 404)

方式 3:错误示范(就是我踩的坑)

tsx 复制代码
// ❌ 错误:把源码路径当 URL
<img src="/src/assets/icons/foundry/foundry-roi-icon.svg" />

为什么会 404

  • 浏览器会向服务器请求 http://localhost:5173/src/assets/icons/foundry/foundry-roi-icon.svg
  • 但这个路径在开发服务器的静态资源映射中不存在
  • src/ 目录下的文件必须通过 import 引入,不能直接作为 URL 访问

解决方案:迁移到 public 目录

既然项目中有很多图标,而且路径需要动态拼接(比如循环渲染),最适合的方式是用 public/ 目录。

步骤 1:移动文件

bash 复制代码
# 创建目标目录
mkdir -p public/images/icons/foundry

# 复制图标文件
cp src/assets/icons/foundry/*.svg public/images/icons/foundry/

# 验证文件已复制
ls -la public/images/icons/foundry/

输出:

diff 复制代码
total 32
-rw-r--r--@ 1 tsk  staff   373B Nov  5 09:44 foundry-cost-icon.svg
-rw-r--r--@ 1 tsk  staff   1.8K Nov  5 09:44 foundry-deploy-icon.svg
-rw-r--r--@ 1 tsk  staff   955B Nov  5 09:44 foundry-roi-icon.svg
-rw-r--r--@ 1 tsk  staff   812B Nov  5 09:44 foundry-update-icon.svg

步骤 2:更新代码中的路径

修改 FoundryPage.tsx,把所有图标路径从 /src/assets/... 改为 /images/...

tsx 复制代码
// Before
<img
  src="/src/assets/icons/foundry/foundry-roi-icon.svg"
  alt="ROI icon"
  className="w-full h-full"
/>

// After ✅
<img
  src="/images/icons/foundry/foundry-roi-icon.svg"
  alt="ROI icon"
  className="w-full h-full"
/>

步骤 3:清理旧文件

确认没有其他地方引用这些文件后,删除旧目录:

bash 复制代码
# 检查是否还有其他引用
grep -r "src/assets/icons/foundry" src/ --include="*.tsx" --include="*.ts"
# (没有输出,说明安全)

# 删除旧目录
rm -rf src/assets/icons/foundry

验证修复

刷新浏览器,打开开发者工具:

修复前

ruby 复制代码
❌ GET http://localhost:5173/src/assets/icons/foundry/foundry-roi-icon.svg 404 (Not Found)
❌ GET http://localhost:5173/src/assets/icons/foundry/foundry-cost-icon.svg 404 (Not Found)
...

修复后

ruby 复制代码
✅ GET http://localhost:5173/images/icons/foundry/foundry-roi-icon.svg 200 (OK)
✅ GET http://localhost:5173/images/icons/foundry/foundry-cost-icon.svg 200 (OK)
...

页面上的 4 个图标全部正常显示,404 错误消失。


最佳实践建议

根据这次踩坑经验,总结几个建议:

1. 静态资源目录规划

建议项目初期就规划好目录结构:

csharp 复制代码
项目根目录/
├── public/              # 不需要构建处理的静态资源
│   ├── images/          # 图片资源
│   │   ├── icons/       # 图标(SVG/PNG)
│   │   ├── backgrounds/ # 背景图
│   │   └── logos/       # Logo
│   ├── fonts/           # 字体文件
│   └── data/            # 静态 JSON 数据
│
├── src/
│   ├── assets/          # 需要构建处理的资源
│   │   ├── styles/      # 样式文件
│   │   └── images/      # 组件专用图片(会被 import)
│   └── components/

决策规则

  • public/:大量通用资源(图标库、公共图片)、动态路径拼接的资源
  • src/assets/:组件紧密相关的资源、需要构建优化的资源

2. 图标引用方式选择

场景 推荐方式 示例
单个图标,固定引用 import + src/assets import logo from '@/assets/logo.svg'
多个图标,循环渲染 public 目录 + 字符串路径 <img src="/images/icons/${name}.svg" />
Icon 组件库 import + 动态导入 const Icon = lazy(() => import(...))
需要 SVG 内联(修改颜色等) import + SVGR 插件 import { ReactComponent as Logo } from './logo.svg'

3. 避免常见错误

错误 1:混淆源码路径和 URL 路径

tsx 复制代码
❌ <img src="/src/components/Icon.svg" />
✅ <img src="/images/icons/icon.svg" />  // public 目录
✅ import icon from '@/components/Icon.svg'  // src 目录

错误 2:public 文件路径写错

tsx 复制代码
// 文件位置:public/images/logo.png

❌ <img src="images/logo.png" />        // 缺少开头的 /
❌ <img src="/public/images/logo.png" />  // 多了 /public
✅ <img src="/images/logo.png" />

记住 :public 目录下的文件,URL 路径要去掉 public/ 前缀 ,并且开头加 /


错误 3:构建后路径失效

tsx 复制代码
// 开发环境正常,生产环境 404
<img src="./images/logo.png" />  // ❌ 相对路径在路由变化时会出问题

✅ <img src="/images/logo.png" />  // 绝对路径

项目目录结构(修复后)

css 复制代码
zhiman-fe/
├── public/
│   └── images/
│       └── icons/
│           ├── foundry/              # 新增:Foundry 图标
│           │   ├── foundry-cost-icon.svg
│           │   ├── foundry-deploy-icon.svg
│           │   ├── foundry-roi-icon.svg
│           │   └── foundry-update-icon.svg
│           └── arrow-right.svg       # 已有的其他图标
│
├── src/
│   ├── assets/
│   │   └── icons/
│   │       ├── direction-top.svg     # 组件专用图标(保留)
│   │       └── foundry/              # 已删除(迁移到 public)
│   └── pages/
│       └── FoundryPage.tsx           # 已更新图标路径

总结

这次踩坑让我重新理解了 Vite 的资源处理机制:

核心原则

  • src/ 目录 = 需要构建处理的源码,必须通过 import 引入
  • public/ 目录 = 静态资源,直接通过 URL 访问(路径去掉 public/ 前缀)
  • 永远不要 把源码路径(/src/...)当作浏览器访问的 URL

参考资料

  1. Vite 官方文档 - 静态资源处理 - 详细说明了 public 目录和 import 的区别
  2. Vite 官方文档 - public 目录 - public 目录的使用规则
  3. React 官方文档 - Adding Images, Fonts, and Files - React 项目中静态资源的处理方式

写在最后

研究完这个问题,我的理解是:Vite 的资源处理分两套系统------构建系统 (src/ 目录)和静态服务(public/ 目录),用错了系统就会 404。

下次添加新图标时,记住这个决策树:

  • 图标数量多、需要动态路径 → public/images/icons/
  • 单个图标、紧密耦合组件 → src/assets/ + import
  • 需要修改 SVG 属性(颜色、大小) → import + SVGR 插件

如果你还没规划项目的静态资源目录结构,建议现在就去整理一下。清晰的目录结构能避免 90% 的资源 404 问题------别成为那个被 404 困扰一下午的倒霉蛋。

相关推荐
未来之窗软件服务2 小时前
幽冥大陆(三十五)S18酒店门锁SDK go语言——东方仙盟筑基期
java·前端·golang·智能门锁·仙盟创梦ide·东方仙盟·东方仙盟sdk
卸任2 小时前
解密Flex布局:为什么flex:1仍会导致内容溢出
前端·css·flexbox
前端一课2 小时前
第 28 题:async / await 的原理是什么?为什么说它是 Promise 的语法糖?(详细版)
前端·面试
前端一课2 小时前
第 28 题:手写 async/await(Generator 自动执行器原理)
前端·面试
前端一课2 小时前
第 33 题:浏览器渲染流程(Reflow 重排、Repaint 重绘、Composite 合成)*
前端·面试
前端一课2 小时前
前端面试第 34 题:事件循环(Event Loop)—— 必考高频题
前端·面试
前端一课2 小时前
第 26 题:Vue2 和 Vue3 的响应式原理有什么区别?为什么 Vue3 要用 Proxy 替代 defineProperty?
前端·面试
前端一课2 小时前
第 30 题:模块化原理(CommonJS vs ESModule)
前端·面试
前端一课2 小时前
第 31 题:Tree Shaking 原理与常见失效原因(高频 + 难点 + 面试必考)
前端·面试