Skip to content

Dynamic Favicon

document.querySelector("link[rel~='icon']")

Strategy Best For Notes

  1. Hostname/Subdomain One app deployed for all clients Dynamic, one build
  2. Env Variable per Build Separate builds per client Static favicon per client build
  3. API-based User-based or late client detection Set favicon after API response
Strategy Best For Notes
Hostname/Subdomain One app deployed for all clients Dynamic, one build
Env Variable per Build Separate builds per client Static favicon per client build
API-based User-based or late client detection User-based or late client detection

Env Variable METHOD

config.js
export const DOMAIN_LISTS = {
  INFOCOM: "info",
  PRABHU: "bank",
  NIMB: "nimb",
  ADBL: "adbl",
};

export const PATH = {
  ASSETS: "/assets/",
};

export const FAVICONS = {
  [DOMAIN_LISTS.INFOCOM]: `${PATH.ASSETS}infocom.ico`,
  [DOMAIN_LISTS.PRABHU]: `${PATH.ASSETS}prabhu.ico`,
  [DOMAIN_LISTS.NIMB]: `${PATH.ASSETS}nimb.ico`,
  [DOMAIN_LISTS.ADBL]: `${PATH.ASSETS}adbl.ico`,
};
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- REMOVE THIS FROM HERE  -->
    <!-- <link rel="icon" type="image/svg+xml" href="/assets/favicon.ico" /> -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Bank App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>
App.jsx
import { DOMAIN_LISTS, FAVICONS, PATH } from "@shared/utils/config"; // Adjust the import path as needed
const buildMode = import.meta.env.VITE_REACT_APP_BUILD_MODE; // e.g. "adbl", "info", etc.

import { useEffect } from "react";
import { DOMAIN_LISTS, FAVICONS, PATH } from "@shared/utils/config";

function App() {
  const buildMode = import.meta.env.VITE_REACT_APP_BUILD_MODE;

  useEffect(() => {
    let favicon = document.querySelector("link[rel~='icon']");
    if (!favicon) {
      favicon = document.createElement("link");
      favicon.rel = "icon";
      favicon.type = "image/x-icon";
      document.head.appendChild(favicon);
    }

    // Set favicon based on buildMode, fallback to default.ico if not found
    favicon.href = FAVICONS[buildMode] || `${PATH.ASSETS}default.ico`;
  }, [buildMode]);

  return (
    // Your component JSX here
  );
}

export default App;
What this does:

On component mount or whenever buildMode changes, it:

  • Looks for existing <link rel="icon"> tag or creates one.
  • Sets its href to the correct favicon path from your FAVICONS map.
  • Falls back to /assets/default.ico if buildMode is not mapped.

Bonus tips:

  • Make sure default.ico exists at /assets/default.ico.
  • Ensure your favicon files exist at the correct locations.
  • Clear browser cache or test in incognito to see changes immediately.

Add a small cache-busting query param to avoid favicon being stuck in browser cache (optional):

favicon.href = `${
  FAVICONS[CONFIG.BUILD_MODE] || `${PATH.ASSETS}default.ico`
}?v=${Date.now()}`;

Only necessary during dev or frequent icon switching, though.

This prevents the browser from defaulting to /favicon.ico before your React app mounts.

In public/index.html (or whatever your entry HTML is):

<link rel="icon" href="/assets/default.ico" type="image/x-icon" />

Then your dynamic logic will override this as soon as React loads.


EXTRA

If i add the the favicon.ico in the index.html

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- THIS  -->
    <link rel="icon" type="image/svg+xml" href="/assets/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Bank App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>
useEffect(() => {
  // Remove any existing favicon tags
  const existingIcons = document.querySelectorAll("link[rel*='icon']");
  existingIcons.forEach((el) => el.parentNode.removeChild(el));

  const favicon = document.createElement("link");
  favicon.rel = "icon";
  favicon.type = "image/x-icon";
  favicon.href = `${
    FAVICONS[CONFIG.BUILD_MODE] || `${PATH.ASSETS}default.ico`
  }?v=${Date.now()}`;
  document.head.appendChild(favicon);
}, [CONFIG.BUILD_MODE]);

Antd

import React from "react";
import { Image } from "antd";
const App = () => (
  <Image
    width={200}
    height={200}
    src="error"
    fallback="data:image/png;base64,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"
  />
);
export default App;

Reference