Skip to main content

Favicon in Vite und React SPA (2026)

8 min read

Vite- und React-SPAs liefern Favicons aus dem public/-Ordner und deklarieren sie in index.html. Anders als Next.js gibt es keine Metadata API. Du schreibst link-Tags von Hand oder nutzt ein PWA-Plugin. SPAs liefern für jede Route dieselbe index.html aus, root-relative Pfade funktionieren meist, aber Base-URL und Deploy-Pfad brauchen Aufmerksamkeit.

Dieser Leitfaden deckt Standard-Vite-Setup, PWA-Manifest-Icons, React-Router-Sonderfälle und die Prüfung auf gebauter und deployed App ab.

Kurzantwort: public-Ordner + index.html

Standard-Vite-Struktur:

my-app/
  public/
    favicon.ico
    favicon-32x32.png
    favicon-16x16.png
    apple-touch-icon.png
    site.webmanifest
  index.html
  src/
    main.tsx

Dateien in public/ landen unverändert im Build-Root. In index.html referenzieren:

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<title>Meine App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

vite build ausführen. Icons liegen unter /favicon-32x32.png auf der deployed Origin.

Warum index.html bei SPAs zählt

Crawler und Browser lesen index.html, bevor JavaScript läuft. Favicon-Logik nur in React (react-helmet, useEffect-DOM-Writes) bedeutet:

  • Tags fehlen in der initialen HTML-Antwort
  • Manche Tools führen kein JS aus und verpassen Icons

Regel: Favicons in index.html deklarieren. React-Head-Libraries nur für pro-Route Title oder OG-Tags, die wirklich wechseln, nicht für das site-weite Favicon.

Vite-base-Option und Subdirectory-Deploys

Setzt vite.config.ts eine Base ungleich Root:

export default defineConfig({
base: '/my-app/',
})

Icon-Pfade brauchen die Base:

<link rel="icon" href="/my-app/favicon.ico" />

Oder Vites %BASE_URL%-Platzhalter in HTML:

<link rel="icon" href="%BASE_URL%favicon.ico" />

Nach Build Pfade in dist/index.html prüfen. Falsche Base bedeutet 404 auf allen Icons. Deployed URL mit Favicon Check scannen.

PWA-Manifest mit vite-plugin-pwa

Für installierbare Apps @vitejs/plugin-pwa:

// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
VitePWA({
registerType: 'autoUpdate',
manifest: {
name: 'Meine App',
icons: [
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
},
}),
],
})

pwa-192x192.png und pwa-512x512.png in public/. Plugin erzeugt Manifest- und Service-Worker-Referenzen beim Build.

Tab-Favicon und PWA-Icons sind getrennte Oberflächen. Größen: Favicon-Größen-Leitfaden.

React Router und Client-Routing

React Router ändert Favicon-Pfade nicht bei root-relativem /favicon.ico. Icons laden von der Origin, nicht von der Route.

Achtung bei:

  • Hash-Routern (#/about) - Favicons weiter von Origin-Root, keine Extra-Config
  • Browser-Router auf Static Hosts - Server muss alle Pfade auf index.html mappen (Netlify _redirects, Vercel Rewrites, nginx try_files)
  • Falschen relativen Pfaden - href="favicon.ico" bricht auf /dashboard/settings, Browser fragt /dashboard/favicon.ico ab

Immer root-relative Pfade mit / am Anfang.

Dynamische Favicons in React (selten)

Unread-Badge, Nutzer-Avatar als Icon. Runtime-Wechsel:

useEffect(() => {
const link = document.querySelector("link[rel~='icon']") as HTMLLinkElement
if (link) {
link.href = '/notification-favicon.png'
}
}, [hasUnread])

Default-Favicon in index.html als Fallback vor JS-Load. Nicht nur Runtime-Tags für Launch-Tests verlassen.

Create React App vs. Vite

CRA nutzt dasselbe public/ + index.html-Muster. Migration zu Vite:

  1. public/-Assets unverändert verschieben
  2. link-Tags aus CRA-index.html nach Vite-index.html
  3. %PUBLIC_URL%-Prefixe entfernen (/ oder %BASE_URL%)
  4. Production-Build neu testen

Typische Vite/React-Favicon-Fehler

Icons in src/assets/ ohne Import

Dateien in src/ bekommen gehashte Namen ohne Import. Favicons gehören in public/ für stabile URLs wie /favicon.ico.

favicon.ico fehlt

Browser rufen /favicon.ico automatisch ab. In public/ legen, auch mit PNG-Tags.

Nur 32×32 PNG

16×16, 180×180 apple-touch-icon und Manifest-Größen für Mobile ergänzen. Eine Datei reicht nicht für Homescreen-Install.

Rebuild nach Icon-Tausch vergessen

Vite kopiert public/ beim Build. Icon-Wechsel ohne vite build und Redeploy lässt Production veraltet.

Helmet-Duplikate

react-helmet-async mit Favicon auf jeder Route erzeugt Duplikate. Favicon einmal in index.html.

Vite/React-Favicons testen

Lokale Preview

npm run build
npm run preview

http://localhost:4173 öffnen (Default-Preview-Port). Tab-Icon und Seitenquelltext prüfen.

Production-Scan

dist/ deployen. Favicon Check auf Live-URL.

Prüfen:

  • Jede Icon-URL liefert 200
  • /favicon.ico erreichbar
  • Manifest gültig bei PWA-Plugin

Voller Workflow: Favicons testen vor Launch.

Open Graph separat

SPAs injizieren OG-Tags oft client-seitig, Crawler verpassen sie. Favicon in index.html ist ok; OG-Tags sollten servergerendert oder prerendered sein. Shares mit Open Graph Check testen.

Production-Datei-Checkliste

public/
  favicon.ico
  favicon-16x16.png
  favicon-32x32.png
  apple-touch-icon.png
  android-chrome-192x192.png   # bei Manifest
  android-chrome-512x512.png
  site.webmanifest
index.html                     # alle link-Tags
vite.config.ts                 # base + PWA-Plugin bei Bedarf

HTML-Basis: Favicon per HTML einbinden.

Netlify, Cloudflare Pages und AWS S3

Static Hosts liefern dist/ unverändert aus. Typische Probleme:

Netlify: _redirects darf Icon-Pfade nicht abfangen und HTML zurückgeben. Bei breitem SPA-Fallback explizite Regeln:

/favicon.ico 200
/favicon-32x32.png 200

Cloudflare Pages: Gleiches SPA-Fallback-Risiko. Icon-Dateien müssen im Build-Output liegen, nicht nur im Repo-Root.

S3 + CloudFront: public/-Assets mit korrektem Content-Type hochladen. ICO als application/octet-stream verwirrt manche Clients. image/x-icon als S3-Metadaten setzen.

Nach Static-Host-Deploy Live-URL scannen. Build-Logs bestätigen nicht, dass Icons öffentlich erreichbar sind.

Umgebungsvariablen und Multi-Brand-SPAs

White-Label-React-Apps tauschen Favicons pro Mandant zur Laufzeit. Für Launch-Tests:

  1. Jede Mandanten-Subdomain separat testen
  2. Default-Favicon in index.html vor Mandanten-JS behalten
  3. Dokumentieren, welche Mandanten Custom-Icons nutzen

Runtime-Swaps sind valide, schwerer zu QAen. Automatische Scans pro Mandanten-URL, nicht nur Hauptdomain.

Vite + TypeScript + React 19

Die Favicon-Regeln ändern sich mit React-Versionen nicht. Was sich ändert: Strict Mode doppeltes Mounting in Dev kann Favicon-useEffect-Swaps flackern lassen. In Production tritt das nicht auf. Teste Icon-Wechsel mit npm run preview, nicht nur npm run dev.

Turborepo und Shared-Asset-Packages

In Monorepos liegen Favicon-Dateien manchmal in @brand/assets. Vite muss sie beim Build nach public/ kopieren, nicht nur aus src/ importieren.

Funktionierendes Muster:

packages/assets/icons/   # Source of Truth
apps/web/public/         # per Build-Script kopiert

Prebuild-Schritt:

"scripts": {
"prebuild": "cp -r ../packages/assets/icons/* public/"
}

Vergessene Copy-Scripts: CI-Builds ohne Icons, obwohl lokale Symlinks funktionieren.

Vitest und E2E-Smoke-Tests

Minimale E2E-Assertion nach Deploy-Preview:

test('favicon.ico returns 200', async () => {
const res = await fetch(`${baseUrl}/favicon.ico`)
expect(res.status).toBe(200)
})

Mit HTML-Parse für link[rel="icon"] kombinieren. Visuelle Regression auf 16×16 optional; HTTP-Abdeckung fängt die meisten Deploy-Fehler.

React Router 7 und Data Routers

Auch mit React Router 7 und Data APIs ändert sich nichts an der Regel: Favicon-Tags gehören in index.html. Loader-Daten und Route-Module rendern den Head nicht für First Paint, solange kein SSR aktiv ist.

SSR und Meta-Frameworks (Remix, TanStack Start)

Wenn du von reiner SPA zu SSR wechselst, können Favicons in index.html bleiben. Der Server liefert dieselbe statische Shell. Prüfe nach SSR-Migration erneut den Seitenquelltext: manche Adapter verschieben Assets oder ändern base.

Nach Framework-Wechsel immer Favicon Check auf Staging und Production laufen lassen.

Performance-Hinweis

Favicon-Requests sind klein und selten relevant für LCP. Trotzdem keine Multi-MB-Dateien in public/ legen. Korrekte Content-Type-Header setzen, damit Browser nicht retryen.

Schnellreferenz: index.html Head-Block

Starter-Block für Vite-index.html, Dateinamen anpassen:

<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">

Nach erstem Deploy mit Favicon Check validieren und nur erweitern, wenn der Scan Lücken meldet.

Häufige Vite-Config-Fallen

// vite.config.ts - base vergessen bei GitHub Pages
export default defineConfig({
base: process.env.GITHUB_ACTIONS ? '/repo-name/' : '/',
})

Hardcodierte /favicon.ico-Pfade in index.html brechen ohne %BASE_URL%. Immer Build-Output in dist/index.html prüfen, nicht nur Dev-Server.

Design-Handoff für React-Teams

Bitte Design um diese Lieferung:

  • favicon.ico mit 16, 32, 48
  • PNG 16, 32, 180, 192, 512
  • Quadratischer Master ab 512 px

Alles nach public/ kopieren, in index.html referenzieren, Build laufen lassen. Kein Import aus src/assets für stabile Favicon-URLs.

Nach jedem Rebrand den alten public/-Ordner komplett ersetzen statt Dateien überschreiben. Browser und CDN erkennen Namensänderungen zuverlässiger als byte-identische Überschreibungen.

CI-Beispiel für GitHub Actions

Nach Deploy kann ein Workflow die Production-URL prüfen:

- name: Check favicon.ico
run: |
code=$(curl -s -o /dev/null -w "%{http_code}" https://example.com/favicon.ico)
test "$code" = "200"

Ersetze die URL durch Preview oder Production. Ergänze manuelle Favicon Check-Scans für HTML-Tags und Manifest, die curl allein nicht validiert.

Wer Vite mit npm, pnpm und bun parallel testet, sollte nach dem Build immer dist/ inspizieren. Package Manager ändern nichts an Favicon-Pfaden, aber unterschiedliche CI-Images vergessen manchmal den prebuild-Copy-Schritt aus Monorepos.

Lege in package.json ein Script verify:icons an, das nach vite build die wichtigsten Dateien in dist/ auflistet. Schon das verhindert leere Deploys ohne sichtbaren Fehler im Tab.

FAQ

Wo lege ich das Favicon in Vite React ab?

In public/ im Projekt-Root. In index.html mit root-relativem Pfad wie /favicon.ico referenzieren.

Verarbeitet Vite Favicon-Dateien?

public/-Dateien kopieren unverändert ins dist/-Root. Kein Bundling oder Hashing. Stabile URLs.

Favicon zur Vite PWA hinzufügen?

@vitejs/plugin-pwa Manifest-Icons plus Standard-link-Tags in index.html für Browser-Tabs.

Warum Favicon-404 auf GitHub Pages?

GitHub-Pages-Project-Sites nutzen Subdirectory-Base (/repo-name/). base in vite.config.ts setzen und Icon-hrefs anpassen.

SVG-Favicon in Vite?

Ja. favicon.svg in public/ und <link rel="icon" type="image/svg+xml" href="/favicon.svg">. PNG-Fallback für Safari.

React Helmet für Favicon?

Möglich, für site-weite Icons nicht empfohlen. index.html für stabile servergelieferte Tags.

Warum unscharfes Favicon in React-App?

Falsche Quell-Dimensionen oder hochskaliertes kleines PNG. Richtige Größen exportieren. Siehe Favicon unscharf.

Favicon vs. OG-Bild in React SPA?

Verschiedene Tags, verschiedene Dateien. Favicon in index.html. OG braucht SSR/Prerender für zuverlässige Shares. Siehe Favicon vs. Open-Graph-Bild.

Fazit

Vite-React-Favicons gehören in public/ mit link-Tags in index.html. base für Subdirectory-Deploys setzen, Manifest-Icons für PWA ergänzen, kein reines Client-Favicon.

Bauen, deployen, mit Favicon Check scannen. Share-Metadata separat fixen und mit Open Graph Check validieren.