Complete React Favicon Implementation Guide
Master favicon implementation in React applications. Learn setup for Vite, Create React App, Next.js, and Remix with modern best practices.
React Favicon Quick Facts
- Location: /public folder (all tools)
- Auto-included: Most React tools
- Hot Reload: May need browser refresh
- Build Process: Copied automatically
- Dynamic Icons: Possible with hooks
- PWA Support: Via manifest.json
Setup by React Tool
Vite (Recommended Modern Tool)
1. File Structure:
my-vite-app/
??? public/
? ??? favicon.ico
? ??? favicon-16x16.png
? ??? favicon-32x32.png
? ??? favicon-96x96.png
? ??? favicon-512x512.png
? ??? apple-touch-icon.png
? ??? android-chrome-192x192.png
? ??? android-chrome-512x512.png
? ??? site.webmanifest
??? src/
??? index.html2. Update index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Favicons -->
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png" />
<link rel="icon" type="image/png" sizes="512x512" href="/favicon-512x512.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#000000" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>Vite Note: Files in /public are served at root. No import needed. Changes require page refresh during development.
Create React App (CRA)
1. File Structure:
my-cra-app/
??? public/
? ??? favicon.ico (default, replace this)
? ??? logo192.png (rename to android-chrome-192x192.png)
? ??? logo512.png (rename to android-chrome-512x512.png)
? ??? manifest.json
? ??? index.html
??? src/2. Update public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<!-- Replace default favicons -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>3. Update public/manifest.json:
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "android-chrome-192x192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "android-chrome-512x512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}CRA Note: Use %PUBLIC_URL% for paths in HTML. Files copy automatically during build.
Next.js (App Router & Pages Router)
App Router (Next.js 13+):
my-next-app/
??? app/
? ??? favicon.ico (automatic)
? ??? icon.png (automatic, multiple sizes)
? ??? apple-icon.png (automatic)
? ??? layout.tsx
??? public/
??? site.webmanifestNext.js 13+ automatically handles favicons in /app directory:
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Next.js App',
description: 'Description',
icons: {
icon: [
{ url: '/favicon.ico' },
{ url: '/icon.png', sizes: '32x32', type: 'image/png' },
],
apple: '/apple-icon.png',
},
manifest: '/site.webmanifest',
}Pages Router (Next.js 12 and below):
// pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head>
<link rel="icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png" />
<link rel="icon" type="image/png" sizes="512x512" href="/favicon-512x512.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}Next.js Magic: Automatic optimization for favicons. Place files in /app (13+) or /public (12-).
Remix
File Structure:
my-remix-app/
??? public/
? ??? favicon.ico
? ??? favicon-32x32.png
? ??? apple-touch-icon.png
??? app/
??? root.tsxUpdate app/root.tsx:
import { Links, LiveReload, Meta, Outlet, Scripts } from "@remix-run/react";
export const links = () => [
{ rel: "icon", href: "/favicon.ico" },
{ rel: "icon", type: "image/png", sizes: "32x32", href: "/favicon-32x32.png" },
{ rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" },
{ rel: "manifest", href: "/site.webmanifest" },
];
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
);
}Remix: Use links() export in root.tsx for favicon links.
Dynamic Favicon Changes in React
Change favicons dynamically based on app state (e.g., notifications, theme):
Custom Hook: useFavicon
// hooks/useFavicon.js
import { useEffect } from 'react';
export const useFavicon = (href) => {
useEffect(() => {
const link = document.querySelector("link[rel~='icon']") ||
document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'icon';
link.href = href;
if (!document.querySelector("link[rel~='icon']")) {
document.head.appendChild(link);
}
}, [href]);
};
// Usage in component:
import { useFavicon } from './hooks/useFavicon';
function App() {
const [hasNotifications, setHasNotifications] = useState(false);
useFavicon(hasNotifications ? '/favicon-notification.ico' : '/favicon.ico');
return <div>App Content</div>;
}Badge on Favicon (Notification Count):
// utils/faviconBadge.js
export const drawFaviconBadge = (count) => {
const favicon = document.querySelector("link[rel~='icon']");
const img = document.createElement('img');
img.src = favicon.href;
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext('2d');
// Draw original favicon
ctx.drawImage(img, 0, 0, 32, 32);
// Draw badge
if (count > 0) {
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(24, 8, 8, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 10px Arial';
ctx.textAlign = 'center';
ctx.fillText(count > 9 ? '9+' : count, 24, 11);
}
favicon.href = canvas.toDataURL('image/png');
};
};
// Usage:
useEffect(() => {
drawFaviconBadge(unreadCount);
}, [unreadCount]);React PWA Manifest Setup
Complete manifest.json for React PWA:
{
"short_name": "React App",
"name": "My React Application",
"description": "A Progressive Web App built with React",
"icons": [
{
"src": "/favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "/android-chrome-192x192.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "any"
},
{
"src": "/android-chrome-512x512.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "any"
},
{
"src": "/android-chrome-maskable-192x192.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "maskable"
},
{
"src": "/android-chrome-maskable-512x512.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
}
],
"start_url": "/",
"scope": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff",
"orientation": "portrait-primary"
}Common React Favicon Issues
Problem: Changed favicon but still see old one.
Solutions:
Solutions:
- Hard refresh: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac)
- Clear browser cache
- Open in incognito/private window
- Vite: Stop dev server, clear cache, restart
- Add version query:
favicon.ico?v=2
Causes:
- File not in /public folder
- Incorrect path in link tags
- Build process didn't copy files
- Verify files exist in /public
- Check build output (dist/ or build/)
- Use correct paths:
/favicon.iconot./favicon.ico - CRA: Use
%PUBLIC_URL%/favicon.ico
Check:
- useEffect dependencies correct
- querySelector finding the right element
- New href actually different
- Browser cache cleared
- Canvas toDataURL supported
React Favicon Best Practices
? Do's
- Always place in /public folder
- Use absolute paths (/favicon.ico)
- Include manifest.json for PWA
- Provide multiple sizes
- Test in production build
- Use SVG for scalability
- Implement proper caching
- Consider dark mode variants
? Don'ts
- Don't import favicons in components
- Don't use relative paths
- Don't put in /src folder
- Don't forget production testing
- Don't skip manifest.json
- Don't use only ICO format
- Don't ignore mobile sizes
- Don't assume dev = production
Generate React-Optimized Favicons
Create all required sizes and formats for your React app
Generate for React