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.tsimport { 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.htmlmappen (Netlify_redirects, Vercel Rewrites, nginxtry_files) - Falschen relativen Pfaden -
href="favicon.ico"bricht auf/dashboard/settings, Browser fragt/dashboard/favicon.icoab
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:
public/-Assets unverändert verschiebenlink-Tags aus CRA-index.htmlnach Vite-index.html%PUBLIC_URL%-Prefixe entfernen (/oder%BASE_URL%)- 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 buildnpm run previewhttp://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.icoerreichbar- 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:
- Jede Mandanten-Subdomain separat testen
- Default-Favicon in
index.htmlvor Mandanten-JS behalten - 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 Pagesexport 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.icomit 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.