X Xerobit

Favicon Caching — Force Browser Refresh After Favicon Change

Browsers aggressively cache favicons, sometimes for days or weeks. Learn how to bust the favicon cache with versioned URLs, HTTP cache headers, and the fastest way to force a...

Mian Ali Khalid · · 4 min read
Use the tool
Favicon Generator
Generate favicon package from any image or emoji. Multiple sizes (16, 32, 48, 180, 192, 512), manifest.json, and HTML snippet.
Open Favicon Generator →

Browsers cache favicons separately from regular resources — even after a hard refresh. A favicon you changed may not update for days. Cache-bust with a versioned URL in your <link> tag.

Generate your favicon with the Favicon Generator.

Why favicons cache aggressively

Most browsers request /favicon.ico once and cache it based on:

  1. Cache-Control and Expires HTTP headers from your server
  2. If no cache headers: browser defaults (often 1 week to 1 year)
  3. The cache persists across sessions and even incognito in some browsers

The browser doesn’t re-request until the cache expires or the user manually clears it.

<!-- Hard-coded version — update v=X whenever you change the favicon -->
<link rel="icon" href="/favicon.ico?v=2" type="image/x-icon">
<link rel="icon" href="/favicon.svg?v=2" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png?v=2">

<!-- Build-time hash (recommended for automated deployments) -->
<link rel="icon" href="/favicon.ico?v=a3f8c2d" type="image/x-icon">

The query string makes the browser treat it as a new URL, bypassing the cached version. The server ignores the query string and serves the same file.

Inject version in build tools

Vite:

// vite.config.js
import { createHash } from 'crypto';
import { readFileSync } from 'fs';

function faviconHash() {
  const content = readFileSync('public/favicon.ico');
  return createHash('md5').update(content).digest('hex').slice(0, 8);
}

export default {
  plugins: [{
    name: 'favicon-version',
    transformIndexHtml(html) {
      const version = faviconHash();
      return html.replace(
        /href="\/favicon\.ico"/g,
        `href="/favicon.ico?v=${version}"`
      );
    },
  }],
};

Next.js (App Router):

// app/layout.tsx
import { readFileSync } from 'fs';
import { createHash } from 'crypto';

function getFaviconVersion() {
  try {
    const content = readFileSync('public/favicon.ico');
    return createHash('md5').update(content).digest('hex').slice(0, 8);
  } catch {
    return '1';
  }
}

export default function RootLayout({ children }) {
  const v = getFaviconVersion();
  return (
    <html>
      <head>
        <link rel="icon" href={`/favicon.ico?v=${v}`} sizes="any" />
        <link rel="icon" href={`/favicon.svg?v=${v}`} type="image/svg+xml" />
      </head>
      <body>{children}</body>
    </html>
  );
}

HTTP cache headers for favicons

Set short cache lifetimes for favicons that change, or use immutable for hashed filenames:

nginx:

# For /favicon.ico — allow some caching but not too long:
location = /favicon.ico {
  expires 1d;
  add_header Cache-Control "public, max-age=86400";
  access_log off;
}

# For hashed filename (favicon.a3f8c2d.ico) — cache forever:
location ~* \.ico$ {
  expires 1y;
  add_header Cache-Control "public, max-age=31536000, immutable";
  access_log off;
}

Express:

import express from 'express';
import { serveStatic } from 'express';

const app = express();

// Short cache for favicon.ico (1 day):
app.get('/favicon.ico', (req, res) => {
  res.set('Cache-Control', 'public, max-age=86400');
  res.sendFile('public/favicon.ico', { root: process.cwd() });
});

Vercel (vercel.json):

{
  "headers": [
    {
      "source": "/favicon.ico",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=86400, stale-while-revalidate=604800"
        }
      ]
    }
  ]
}

Force favicon reload in the browser (development)

During development, force a favicon refresh without clearing the entire cache:

// Paste in browser console to force favicon reload:
(function() {
  const link = document.querySelector("link[rel*='icon']") || document.createElement('link');
  link.type = 'image/x-icon';
  link.rel = 'shortcut icon';
  link.href = '/favicon.ico?' + Date.now();
  document.head.appendChild(link);
})();

Or in Chrome: open DevTools → Application → Storage → Clear storage → check “Cache storage” → click “Clear site data.”

SVG favicon: no caching issues

SVG favicons accept <style> and currentColor, and because they’re often inlined or served with proper headers by modern build tools, they update more reliably:

<!-- favicon.svg — embed in HTML for instant updates: -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <rect width="32" height="32" rx="6" fill="#3b82f6"/>
  <text x="16" y="22" font-size="18" text-anchor="middle" fill="white" font-family="sans-serif">X</text>
</svg>
<!-- Inline SVG as data URI — never cached separately: -->
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='6' fill='%233b82f6'/><text x='16' y='22' font-size='18' text-anchor='middle' fill='white'>X</text></svg>">

An inline data URI favicon is embedded in the HTML and caches with the page — if your HTML is cache-busted on deploy, so is the favicon.


Related posts

Related tool

Favicon Generator

Generate favicon package from any image or emoji. Multiple sizes (16, 32, 48, 180, 192, 512), manifest.json, and HTML snippet.

Written by Mian Ali Khalid. Part of the Frontend & Design pillar.