You dropped favicon.svg into Vite's public folder, hit save, and the browser tab still shows a blank white square. The build works, the file is right there, yet the browser can't find it. This happens when developers treat Vite's public directory like any other static folder — and miss one critical rule about how Vite serves those files.

This is a focused walkthrough of the public/ folder. For the complete picture, including dark mode, Apple touch icons and common pitfalls, start with our Vite favicon guide.

The public folder is Vite's escape hatch for assets that need stable, unhashed URLs. Favicons are the textbook use case — and the most common source of confusion for Vite beginners.

Why Favicons Belong in Vite's Public Folder

Vite hashes imported assets in src/ for cache-busting (think assets/logo-a3f9c2.svg). Great for JS and CSS, terrible for favicons. Browsers cache favicons aggressively, and search engines need a predictable URL to show your icon in results.

Files in public/ skip the bundler entirely. Per Vite's official docs, anything in this directory is copied to the build output root and served at the root path. So public/favicon.svg becomes /favicon.svg — not /public/favicon.svg. Just the file name, prefixed with a slash.

Prerequisites

Step 1: Drop Favicon Files into public/

Place your icon files directly into the public/ directory at your project root. Most Vite scaffolds create this folder automatically.

my-vite-app/
├── public/
│   ├── favicon.svg
│   ├── favicon.ico
│   └── apple-touch-icon.png
├── src/
├── index.html
└── package.json

Use simple, conventional filenames. Browsers find favicon.svg, favicon.ico, and apple-touch-icon.png at the root without extra config.

Step 2: Reference Files with Root-Relative Paths

Open index.html in your project root (not public/index.html — that file doesn't exist in Vite). Add link tags inside the <head> using absolute paths starting with /. This is the only place in a Vite project where you hard-code asset paths, because the public folder intentionally skips the import resolver.

<head>
  <link rel='icon' type='image/svg+xml' href='/favicon.svg'>
  <link rel='icon' type='image/x-icon' href='/favicon.ico'>
  <link rel='apple-touch-icon' href='/apple-touch-icon.png'>
</head>

The HTML spec defines rel='icon' as the standard way to declare favicons (WHATWG HTML Living Standard). Browsers pick the best match based on type and sizes attributes.

Step 3: Add a Dark Mode Variant

Modern browsers support a second SVG that activates automatically in dark themes. Add another link tag with a media query so users see the right icon when their system switches to dark mode.

<link rel='icon' type='image/svg+xml' href='/favicon.svg' media='(prefers-color-scheme: light)'>
<link rel='icon' type='image/svg+xml' href='/favicon-dark.svg' media='(prefers-color-scheme: dark)'>

For deeper coverage on dark mode SVG favicons, see our favicon dark mode guide.

Step 4: Verify the Build Output

Run npm run build and inspect dist/. Your favicon files should appear at the root, untouched by any bundler.

dist/
├── assets/
├── favicon.svg
├── favicon.ico
└── index.html

You can also spin up npm run preview and open the Network tab in DevTools. You should see requests for favicon.svg, favicon.ico, and apple-touch-icon.png returning 200, not 404.

Common Pitfalls

Most Vite favicon bugs come from one of these mistakes. If your tab is blank or shows a generic icon, run through this list first.

When the Public Folder Isn't Enough

The public folder is the right home for static, unchanging favicons. It gives you stable URLs that browsers cache reliably and search engines can index. If you later need dynamic favicons — notification badges, user avatars, live status indicators — you'll need JavaScript to swap the href attribute at runtime. Our guide on dynamic favicon JavaScript covers that pattern in detail.

Need help generating the right icon files first? Mzu favicondl produces SVG, PNG, ICO, and Apple Touch variants from a single source image. Drop them into public/, add your link tags, and your Vite app has a professional tab presence in minutes.