Where the files go
Vite serves everything in public/ at the site root without processing it. That is exactly what favicons need, because /favicon.ico has to answer at that literal path.
public/
favicon.ico
icon.svg
favicon-96x96.png
apple-touch-icon.png
icon-192.png
icon-512.png
icon-mask.png
manifest.webmanifestThe index.html snippet
Replace the starter's vite.svg link in the <head> of index.html with:
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="icon" href="/favicon-96x96.png" type="image/png" sizes="96x96">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="manifest" href="/manifest.webmanifest">Do not import favicons through src/
Anything imported from src/ gets a content hash in its filename at build time, such as favicon-3f9a1c.ico. That is right for app assets, but crawlers and feed readers request /favicon.ico directly and would get a 404. Keep favicons in public/ and reference them with root-relative paths.
Any framework on Vite
The setup is the same for React, Vue, Svelte, Solid and Preact apps built with Vite, because the favicon lives in index.html, not in components. For a React-specific overview, including Next.js and React Router, see React.
Questions
Why does /favicon.ico 404 in my Vite build?
Usually because it was placed in src/ or assets/ and got a hashed filename. Move it to public/.
Do I need to restart the dev server?
Files in public/ are served as they are, but browsers cache favicons hard. Hard-reload, or add ?v=2 to the link while testing.
My app is deployed under a sub-path. Does that change anything?
The tab icons can follow your base path, but /favicon.ico at the domain root is still what crawlers request. If you control the root, put a copy there.