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.
<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' />
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 ensrc/ - Referéncialos con rutas absolutas desde la raíz (
/favicon.ico) - Coloca todas las etiquetas
<link rel='icon'>enindex.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
Para un recorrido más profundo sobre el enfoque de metadatos basado en archivos de Vite, consulta nuestra guía completa del favicon en la carpeta public de Vite. Y si tu problema es específico de React (Helmet, iconos dinámicos), la guía de configuración de favicon en React cubre esos patrones.
Conclusión
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.