React Vite Favicon Not Showing? Quick Fixes That Work
You shipped a new React + Vite app, dropped your favicon.ico into public/, restarted the dev server — and the browser tab still shows the Vite lightning bolt. Or worse, nothing at all. This is the classic React Vite favicon not showing problem, and it almost always traces back to one of a handful of specific misconfigurations.
If you are still setting up icons rather than troubleshooting, our Vite favicon guide covers the full setup.
Here's the two-minute fix first, then the root causes explained.
The 2-Minute Fix
Open index.html in your project root and verify the link tags use absolute paths starting with /, not /public/. In Vite, files in the public/ directory are copied to the build output and served at the root path (see the Vite public directory docs). So favicon.ico in public/favicon.ico is referenced as /favicon.ico.
Save the file. If Vite is running, HMR picks it up automatically. If the icon still doesn't appear, hard refresh (Ctrl+Shift+R or Cmd+Shift+R) and check the Network tab for a 200 response on the favicon URL.
Root Causes Explained
1. The file is in src/, not public/
This is the number one mistake. Files in src/ go through Vite's bundler — they need to be imported as modules and end up with hashed filenames. Files in public/ are served as-is at the root path. If you put your favicon in src/assets/ and expect it to appear at /favicon.ico, it won't exist there. Move it to public/.
2. Wrong path in the link tag
Developers coming from Create React App sometimes write href='%PUBLIC_URL%/favicon.ico' or href='/public/favicon.ico'. Vite has no %PUBLIC_URL% variable, and /public/ is a literal directory name that doesn't exist at runtime. Use plain /favicon.ico.
3. Base path mismatch
If you set base: '/my-app/' in vite.config.js, every asset path must include that prefix. A link tag with href='/favicon.ico' will 404 when the app is served from https://example.com/my-app/. Fix it with a relative reference (href='./favicon.ico') or update all paths to include the configured base.
4. Cache is lying to you
Browsers aggressively cache favicons. Chrome stores them in a separate SQLite database that survives normal cache clears (we covered this in our favicon cache clear guide). Even after you fix the path, the old broken icon can stick around for hours. A query-string version bump forces a fresh request during development:
<link rel='icon' href='/favicon.ico?v=2' />
5. SPA routing intercepts /favicon.ico
If you're using React Router with a catch-all route, a request for /favicon.ico might be handled by your React app and return HTML instead of the icon. Vite's dev server prioritizes public/ files, so this is mostly a production-build issue. Verify by curling the deployed URL and checking the Content-Type header.
6. Link tags inside React components
<link> tags placed inside JSX components don't always render in <head> the way you'd expect. React doesn't hoist them — they only attach when the component mounts in the DOM. Use react-helmet-async for dynamic icons, or place static favicon tags directly in index.html.
7. SVG favicon syntax errors
Chrome parses SVG favicons strictly per the HTML spec for rel='icon'. Missing namespace declarations, invalid viewBox attributes, or external resource references will silently fail. Test the SVG by opening it directly in a browser tab before referencing it.
Prevention Checklist
Keep favicon files in public/ only — never src/
Reference them with absolute paths starting at the root (/favicon.ico)
Place all <link rel='icon'> tags in index.html, not inside JSX
Add a version query string for cache busting during development
Verify the deployed build with curl -I https://yoursite.com/favicon.ico
Test in an incognito window before assuming the icon is broken
The React Vite favicon not showing problem almost always comes down to file location, path reference, or caching. Get those three right and you're 90% there. The remaining 10% is usually a base-path mismatch or a React component swallowing the link tag — both of which leave clear fingerprints in DevTools.
Generate the full icon set with Mzu favicondl, drop the files into public/, paste the link tags into index.html, and ship it.
你刚上线了一个 React + Vite 项目,把 favicon.ico 丢进 public/,重启了 dev server,浏览器标签页却依然显示 Vite 的闪电图标。更糟的情况是,图标彻底消失。这就是典型的 React Vite favicon 不显示问题,几乎总能追溯到几类具体的配置错误。
下面先给两分钟的快速修复方案,再解释背后的根本原因。
两分钟快速修复
打开项目根目录下的 index.html,确认 link 标签使用的是以 / 开头的绝对路径,而不是 /public/。在 Vite 中,public/ 目录下的文件会被原样拷贝到构建产物,并以根路径提供服务(参见 Vite public 目录文档)。所以放在 public/favicon.ico 的文件,引用时应该写成 /favicon.ico。
새로 만든 React + Vite 앱을 배포하고 favicon.ico를 public/에 넣은 뒤 dev 서버를 재시작했는데, 브라우저 탭에는 여전히 Vite 번개 아이콘이 보입니다. 심지어 아무것도 표시되지 않는 경우도 있죠. 이는 React Vite favicon이 표시되지 않는 고전적인 문제로, 거의 항상 몇 가지 특정 설정 오류 중 하나로 귀결됩니다.
먼저 2분 만에 해결하는 방법을 알려드리고, 이어서 근본 원인들을 자세히 설명하겠습니다.
2분 만에 해결하기
프로젝트 루트의 index.html을 열고 link 태그의 경로가 /public/이 아닌 루트 /로 시작하는 절대 경로인지 확인합니다. Vite에서 public/ 디렉터리의 파일은 빌드 출력으로 복사되어 루트 경로에서 제공됩니다(Vite public 디렉터리 문서 참조). 따라서 public/favicon.ico의 favicon은 /favicon.ico로 참조해야 합니다.
파일을 저장합니다. Vite가 실행 중이라면 HMR이 자동으로 반영합니다. 그래도 아이콘이 표시되지 않으면 강제 새로 고침(Ctrl+Shift+R 또는 Cmd+Shift+R)을 수행하고 Network 탭에서 favicon URL의 응답 코드가 200인지 확인합니다.
근본 원인 분석
1. 파일이 src/에 있고 public/에 없는 경우
가장 흔한 실수입니다. src/의 파일은 Vite의 번들러를 거치며 모듈로 import되어 파일명에 해시가 붙습니다. 반면 public/의 파일은 그대로 루트 경로에서 제공됩니다. src/assets/에 favicon을 두고 /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/'를 설정했다면, 모든 asset 경로에 해당 prefix를 포함해야 합니다. href='/favicon.ico'로 작성한 link 태그는 앱이 https://example.com/my-app/에서 제공될 때 404를 반환합니다. 상대 경로(href='./favicon.ico')를 사용하거나, 설정한 base를 포함하도록 모든 경로를 수정하면 됩니다.
4. 캐시가 잘못된 정보를 보여주는 경우
브라우저는 favicon을 매우 공격적으로 캐시합니다. Chrome은 favicon을 일반 캐시 삭제 후에도 살아남는 별도의 SQLite 데이터베이스에 저장합니다(이 부분은 favicon 캐시 삭제 가이드에서 다룬 바 있습니다). 경로를 수정한 뒤에도 이전 깨진 아이콘이 몇 시간 동안 그대로 남아 있을 수 있습니다. 개발 중에 쿼리 스트링 버전을 변경하면 새로운 요청을 강제로 만들 수 있습니다.
<link rel='icon' href='/favicon.ico?v=2' />
5. SPA 라우팅이 /favicon.ico를 가로채는 경우
React Router에서 catch-all 라우트를 사용 중이라면, /favicon.ico 요청이 React 앱에 의해 처리되어 아이콘 대신 HTML을 반환할 수 있습니다. Vite의 dev 서버는 public/ 파일에 우선순위를 두므로 이 문제는 주로 프로덕션 빌드에서 발생합니다. 배포된 URL을 curl로 요청해 Content-Type 헤더를 확인하면 검증할 수 있습니다.
6. React 컴포넌트 내부의 link 태그
JSX 컴포넌트 안에 배치한 <link> 태그가 항상 예상대로 <head>에 렌더링되지는 않습니다. React는 link 태그를 자동으로 끌어올리지 않으며, 컴포넌트가 DOM에 마운트될 때에만 적용됩니다. 동적 아이콘이 필요하다면 react-helmet-async를 사용하고, 정적인 favicon 태그는 index.html에 직접 작성해야 합니다.
7. SVG favicon 구문 오류
Chrome은 rel='icon'에 대한 HTML 명세를 엄격하게 따라 SVG favicon을 파싱합니다. namespace 선언 누락, 잘못된 viewBox 속성, 외부 리소스 참조는 조용히 실패합니다. 참조하기 전에 브라우저 탭에서 SVG를 직접 열어 테스트해 보시기 바랍니다.
예방 체크리스트
favicon 파일은 public/에만 둡니다. src/에는 절대 두지 않습니다.
루트에서 시작하는 절대 경로(/favicon.ico)로 참조합니다.
모든 <link rel='icon'> 태그는 JSX가 아닌 index.html에 작성합니다.
개발 중에는 버전 쿼리 스트링을 추가해 캐시를 무효화합니다.
curl -I https://yoursite.com/favicon.ico 명령으로 배포된 빌드를 검증합니다.
React Vite favicon 미표시 문제는 거의 항상 파일 위치, 경로 참조, 캐시 문제로归结됩니다. 이 세 가지만 제대로 처리하면 90%는 해결됩니다. 나머지 10%는 보통 base 경로 불일치이거나 React 컴포넌트가 link 태그를 흡수하는 경우인데, 이 두 가지는 DevTools에서 명확한 흔적을 남깁니다.
Mzu favicondl로 전체 아이콘 세트를 생성하고, 파일들을 public/에 넣은 뒤, link 태그를 index.html에 붙여 넣은 다음 배포하면 됩니다.
Acabas de desplegar una nueva app con React + Vite, metiste tu favicon.ico en public/, reiniciaste el servidor de desarrollo… y la pestaña del navegador sigue mostrando el rayo de Vite. O peor, no muestra nada. Este es el clásico problema del favicon que no aparece en React con Vite, y casi siempre se reduce a uno de unos pocos errores concretos de configuración.
Primero te dejo la solución exprés en dos minutos y después te explico las causas de fondo.
La solución exprés
Abre el archivo index.html en la raíz de tu proyecto y comprueba que las etiquetas link usen rutas absolutas que empiecen con /, y no con /public/. En Vite, los archivos del directorio public/ se copian al build y se sirven en la raíz (mira la documentación del directorio public de Vite). Así que favicon.ico en public/favicon.ico se referencia como /favicon.ico.
Guarda el archivo. Si Vite está en ejecución, el HMR recoge el cambio automáticamente. Si el icono sigue sin salir, haz un hard refresh (Ctrl+Shift+R o Cmd+Shift+R) y revisa en la pestaña Network que la URL del favicon devuelva un 200.
Causas de fondo
1. El archivo está en src/, no en public/
Este es el error número uno. Los archivos en src/ pasan por el bundler de Vite — hay que importarlos como módulos y terminan con nombres hasheados. Los archivos en public/ se sirven tal cual en la raíz. Si metes tu favicon en src/assets/ y esperas que aparezca en /favicon.ico, ahí no va a existir. Muévelo a public/.
2. Ruta mal escrita en la etiqueta link
Quien viene de Create React App a veces escribe href='%PUBLIC_URL%/favicon.ico' o href='/public/favicon.ico'. Vite no tiene la variable %PUBLIC_URL%, y /public/ es un nombre de directorio literal que no existe en tiempo de ejecución. Usa directamente /favicon.ico.
3. Desajuste del base path
Si defines base: '/my-app/' en vite.config.js, toda ruta de asset debe incluir ese prefijo. Un link con href='/favicon.ico' devolverá 404 cuando la app se sirva desde https://example.com/my-app/. Arregla la ruta con una referencia relativa (href='./favicon.ico') o actualiza todas las rutas para incluir el base configurado.
4. La caché te está mintiendo
Los navegadores cachean los favicons de forma muy agresiva. Chrome los guarda en una base de datos SQLite aparte que sobrevive a limpiezas de caché normales (lo vimos en nuestra guía para limpiar la caché de favicons). Incluso después de arreglar la ruta, el icono roto anterior puede seguir apareciendo durante horas. Un versionado con query string fuerza una petición nueva durante el desarrollo:
<link rel='icon' href='/favicon.ico?v=2' />
5. El routing de la SPA intercepta /favicon.ico
Si usas React Router con una ruta catch-all, una petición a /favicon.ico puede ser atendida por tu app de React y devolver HTML en vez del icono. El dev server de Vite prioriza los archivos de public/, así que esto suele pasar en el build de producción. Compruébalo haciendo curl a la URL desplegada y revisando la cabecera Content-Type.
6. Etiquetas link dentro de componentes React
Las etiquetas <link> dentro de JSX no siempre se renderizan en <head> como esperarías. React no las eleva al head — solo se adjuntan cuando el componente se monta en el DOM. Usa react-helmet-async para iconos dinámicos, o coloca las etiquetas de favicon estáticas directamente en index.html.
7. Errores de sintaxis en el favicon SVG
Chrome interpreta los SVG de favicon de forma estricta según la spec de HTML para rel='icon'. Declaraciones de namespace que faltan, atributos viewBox inválidos o referencias a recursos externos provocan un fallo silencioso. Prueba el SVG abriéndolo directamente en una pestaña del navegador antes de referenciarlo.
Checklist de prevención
Mantén los archivos de favicon solo en public/ — nunca en src/
Referéncialos con rutas absolutas desde la raíz (/favicon.ico)
Coloca todas las etiquetas <link rel='icon'> en index.html, no dentro de JSX
Añade un query string de versión para romper la caché durante el desarrollo
Verifica el build desplegado con curl -I https://tudominio.com/favicon.ico
Prueba en una ventana de incógnito antes de asumir que el icono está roto
El problema del favicon que no aparece en React con Vite casi siempre se reduce a tres cosas: ubicación del archivo, ruta de referencia o caché. Si aciertas en esas tres, llevas el 90% resuelto. El 10% restante suele ser un desajuste del base path o un componente de React que se traga la etiqueta link — ambos dejan huellas claras en DevTools.
Genera el set completo de iconos con Mzu favicondl, suelta los archivos en public/, pega las etiquetas link en index.html y a desplegar.
Generate Your React Vite Favicon Set
Use Mzu favicondl to generate every favicon size from a single source — SVG, PNG, and ICO in one click.