Adding a favicon to a React app depends on your build tool: Create React App (CRA) and Vite handle files differently. This guide covers both, plus how to swap favicons at runtime (useful for dark-mode or branded white-label apps).
Create React App (CRA)
CRA puts static files in the public/ folder.
- Place
favicon.icoinpublic/. - Open
public/index.htmland check the<link>tag (CRA includes one by default):
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" />
<link rel="manifest" href="%PUBLIC_URL%/site.webmanifest" />The %PUBLIC_URL% variable ensures the path works regardless of where the app is deployed (subdirectory or root).
Vite + React
Vite is the modern default for new React apps. Favicons go in public/, but the HTML template is index.html at the project root (not inside public/).
- Place your favicon files in
public/. - Edit the root
index.htmland add link tags in<head>:
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />Vite prefers SVG favicons — note the type="image/svg+xml" declaration. Put your favicon.svg in public/ and reference it with a leading slash.
Get every file you need (ICO, PNG sizes, SVG, Apple touch icon, manifest) from one upload at Favicon.one.
Swapping the favicon at runtime
For dark-mode favicons or white-label apps, you can change the favicon dynamically with a small hook:
import { useEffect } from 'react'
function useFavicon(href: string) {
useEffect(() => {
let link = document.querySelector("link[rel~='icon']") as HTMLLinkElement
if (!link) {
link = document.createElement('link')
link.rel = 'icon'
document.head.appendChild(link)
}
link.href = href
}, [href])
}
// Usage
function App() {
useFavicon('/favicon-dark.svg')
return <div>...</div>
}For a full dark-mode setup (automatic switching), see our Favicon Dark Mode guide.
Using react-helmet
If you already use react-helmet or react-helmet-async for managing <head>, you can declare favicons declaratively:
<Helmet>
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
</Helmet>Next.js vs React (CRA/Vite)
If you're using Next.js, the approach is different — Next.js has file-based conventions that auto-generate the tags. See our dedicated Next.js favicon guide.
Common React favicon problems
- Hard refresh. Vite's dev server caches aggressively. Stop and restart
npm run devif your icon doesn't update. - Wrong folder. CRA and Vite both want files in
public/, notsrc/. - Missing leading slash. Use
/favicon.ico, notfavicon.ico, so the path resolves from root. - Build output missing the file. Check
dist/(Vite) orbuild/(CRA) after building to confirm the favicon was copied.
Still stuck? See the full “Favicon Not Showing” checklist.