Generate Now

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.html

2. 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.webmanifest

Next.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.tsx

Update 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:
  1. Hard refresh: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac)
  2. Clear browser cache
  3. Open in incognito/private window
  4. Vite: Stop dev server, clear cache, restart
  5. Add version query: favicon.ico?v=2

Causes:
  • File not in /public folder
  • Incorrect path in link tags
  • Build process didn't copy files
Fixes:
  1. Verify files exist in /public
  2. Check build output (dist/ or build/)
  3. Use correct paths: /favicon.ico not ./favicon.ico
  4. 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

Related Articles

An unhandled error has occurred. Reload 🗙