你刚上线了一个 React + Vite 项目,把 favicon.ico 丢进 public/,重启了 dev server,浏览器标签页却依然显示 Vite 的闪电图标。更糟的情况是,图标彻底消失。这就是典型的 React Vite favicon 不显示问题,几乎总能追溯到几类具体的配置错误。

下面先给两分钟的快速修复方案,再解释背后的根本原因。

两分钟快速修复

打开项目根目录下的 index.html,确认 link 标签使用的是以 / 开头的绝对路径,而不是 /public/。在 Vite 中,public/ 目录下的文件会被原样拷贝到构建产物,并以根路径提供服务(参见 Vite public 目录文档)。所以放在 public/favicon.ico 的文件,引用时应该写成 /favicon.ico。

<link rel='icon' type='image/x-icon' href='/favicon.ico' />
<link rel='icon' type='image/svg+xml' href='/icon.svg' />
<link rel='apple-touch-icon' href='/apple-touch-icon.png' />

保存文件。如果 Vite 正在运行,HMR 会自动接管。如果图标还是没有出现,试试硬刷新(Ctrl+Shift+R 或 Cmd+Shift+R),然后在 Network 面板里看看 favicon URL 是否返回 200。

根本原因详解

1. 文件放错了目录:放在 src/ 而不是 public/

这是最常见的错误。src/ 下的文件会经过 Vite 的打包流程——它们需要被作为模块导入,最终文件名还会带上 hash。public/ 下的文件则会以原始形态、从根路径提供服务。如果你把 favicon 放在 src/assets/,然后期望它在 /favicon.ico 这个位置可用,那它根本不会出现在那里。把文件挪到 public/ 即可。

2. link 标签里的路径写错了

从 Create React App 迁移过来的开发者有时会写成 href='%PUBLIC_URL%/favicon.ico' 或者 href='/public/favicon.ico'。Vite 没有 %PUBLIC_URL% 这个变量,/public/ 是一个运行时根本不存在的字面目录名。请直接用 /favicon.ico。

3. base 路径不匹配

如果你在 vite.config.js 中设置了 base: '/my-app/',那么所有资源路径都必须带上这个前缀。当应用部署在 https://example.com/my-app/ 时,link 标签里写 href='/favicon.ico' 就会 404。可以改成相对引用(href='./favicon.ico'),或者把所有路径都更新为带上配置好的 base 前缀。

4. 缓存在骗你

浏览器对 favicon 的缓存非常激进。Chrome 把它们存在一个独立的 SQLite 数据库里,普通清缓存操作根本清不掉(这一点我们在 favicon 缓存清理指南 中讲过)。哪怕你修好了路径,旧的破损图标也可能赖着不走长达几个小时。开发阶段可以在 URL 上加一个版本号查询参数,强制刷新请求:

<link rel='icon' href='/favicon.ico?v=2' />

5. SPA 路由拦截了 /favicon.ico

如果你的项目用了 React Router,并且配了 catch-all 路由,那么对 /favicon.ico 的请求可能会被 React 应用接住,返回 HTML 而不是图标。Vite 的 dev server 会优先处理 public/ 下的文件,所以这个问题大多出现在生产构建阶段。可以用 curl 请求部署后的 URL,检查响应头里的 Content-Type 来验证。

6. 把 link 标签写在 React 组件里

放在 JSX 组件里的 <link> 标签,并不总是按你期望的方式出现在 <head> 中。React 不会把它们提升到 head 节点——它们只会在组件挂载到 DOM 时才生效。如果要动态切换图标,用 react-helmet-async;静态的 favicon 标签直接写在 index.html 里就行。

7. SVG favicon 的语法错误

Chrome 严格按照 HTML 规范中对 rel='icon' 的定义来解析 SVG favicon。缺了 namespace 声明、viewBox 属性不合法,或者引用了外部资源,都会导致解析静默失败。在引用之前,可以先把 SVG 直接拖到浏览器标签页里打开测试一下。

预防清单

想更系统地了解 Vite 基于文件系统的元数据处理方式,可以看 这份 Vite public 目录 favicon 完整指南。如果你的问题跟 React 强相关(Helmet、动态图标),React favicon 配置指南 也覆盖了相关模式。

最后的话

React Vite favicon 不显示这个问题,几乎总能归到三类原因:文件位置、路径引用、缓存。这三点搞对了,90% 的问题就解决了。剩下的 10% 通常是 base 路径不匹配,或者某个 React 组件把 link 标签吞掉了——这两种情况在 DevTools 里都能找到明显的痕迹。

用 Mzu favicondl 生成一整套图标,把文件扔进 public/,把 link 标签粘到 index.html 里,然后就可以上线了。