React
Favicons in React — Vite, Create React App, and everything else
React itself has no favicon convention. Your bundler does. This guide covers the modern setup for Vite-powered React, Create React App, and framework-less setups.
React is a library, not a framework. It has no favicon convention. Your bundler does. This guide covers the three setups in use today: Vite (the modern default), Create React App (legacy but still alive), and framework-less or other bundlers.
Vite (recommended)
Vite projects have a /public folder at the project root. Drop favicons there:
public/
├── favicon.svg
├── favicon.ico
├── favicon-96x96.png
├── apple-touch-icon.png
├── web-app-manifest-192x192.png
├── web-app-manifest-512x512.png
└── site.webmanifest
Link them from index.html (also at the project root):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#0a0a0a" />
<title>My App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Create React App (legacy)
CRA’s /public folder behaves the same as Vite’s. Same file layout, same link tags, same paths. Edit /public/index.html:
<link rel="icon" type="image/svg+xml" href="%PUBLIC_URL%/favicon.svg" />
<link rel="icon" type="image/png" sizes="96x96" href="%PUBLIC_URL%/favicon-96x96.png" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/apple-touch-icon.png" />
<link rel="manifest" href="%PUBLIC_URL%/site.webmanifest" />
%PUBLIC_URL% is CRA’s placeholder for the public base path. It gets replaced at build time. Absolute /favicon.svg works too if your app is always deployed at the domain root.
CRA is on maintenance. New React projects should use Vite, Next.js, Remix, or TanStack Start.
Deploying under a subpath
If your app is served from a subpath like https://example.com/app/, favicon paths need the subpath prefix. Vite:
// vite.config.js
export default defineConfig({
base: "/app/",
});
With base set, Vite rewrites absolute paths in index.html to include the prefix. Your <link rel="icon" href="/favicon.svg" /> becomes /app/favicon.svg in the built HTML.
For client-side references inside components, use import.meta.env.BASE_URL:
<img src={`${import.meta.env.BASE_URL}favicon.svg`} alt="" />
Favicons themselves do not need this because they are referenced from index.html, which Vite processes.
Why not import the favicon in a component
Do not write:
import faviconUrl from "./assets/favicon.svg";
<link rel="icon" href={faviconUrl} />
This bundles the favicon with a content hash (favicon.abc123.svg). The browser caches favicons per-hostname, so a hash-rotating URL defeats caching and changes on every build. Favicons need stable paths. Put them in /public and reference by absolute path.
The exception is if your app is entirely client-rendered and you want favicon hash-busting on deploy. Even then, query strings on the <link> tag work better (/favicon.svg?v=2) than import-based hashing.
Common pitfalls
- Broken paths after subpath deploy. Forgot
basein vite.config.js. - Missing apple-touch-icon. CRA and Vite ship with only
favicon.icoby default. The full modern set has five files plus a manifest. - Raw
<head>access in React. React itself does not manage the document head. Usereact-helmet-asynconly for per-page titles and metadata, not for favicons. Favicons belong inindex.html.
Generate the set
Drop your logo SVG into Faviconry, unzip the download into /public, paste the five <link> tags into index.html. Works identically in Vite, CRA, and any bundler-less setup.
Frequently asked questions
Why does my favicon path break in production but work in dev?
Vite and CRA serve the /public folder at the root in dev and at the base URL in production. If you deploy under a subpath like /app/ without setting the base option, absolute paths like /favicon.svg break. Use the BASE_URL variable or set base in vite.config.js.
Where do favicons go in a Vite React project?
The /public folder. Files there are served at the root in dev and copied verbatim into /dist on build. Reference them with absolute paths in your index.html. No imports, no hashing.
Can I import favicons as ES modules?
You can but should not. Importing a favicon into a component bundles it with a content hash, so the path changes on every build. Favicons need stable root paths. Keep them in /public and link them from index.html.
Other frameworks
- Next.js How to ship a complete modern favicon set in a Next.js App Router project. Where files go, which metadata API to use, and the specific gotchas of the /app directory.
- Vue / Nuxt Vue with Vite uses /public for favicons and index.html for link tags. Nuxt 3 adds app.head config for declarative setup. This guide covers both.