How to Test Your Favicon Across All Browsers and Devices
Your favicon looks great in Chrome but broken on Safari? A systematic testing checklist with tools and common failure modes.
You uploaded a beautiful favicon, deployed your site, and it looks perfect in Chrome on your laptop. Then you open it on an iPhone and the home screen shows a blank white square. Your colleague on Windows sees a pixelated mess in their taskbar. A friend shares your link on Slack and the preview shows no image at all.
Favicon testing is unglamorous but necessary. Each browser and platform has its own rules about which file it picks, what size it expects, and how it handles missing formats. Here is a systematic way to catch problems before your users do.
The Testing Checklist
| Test | What to check | How to test |
|---|---|---|
| Browser tab (Chrome) | Icon is sharp, recognizable | Open site in Chrome |
| Browser tab (Firefox) | Same icon, same quality | Open in Firefox |
| Browser tab (Safari) | Icon displays, not pixelated | Open in Safari |
| Bookmark bar | Recognizable at small size | Bookmark the page, check bar |
| iOS home screen | 180x180 icon, no white box | Add to Home Screen on iPhone |
| Android shortcut | Adaptive icon, correct mask | Add to Home Screen on Android |
| Dark mode tab | Icon visible on dark chrome | Enable dark mode in OS |
| Social share preview | OG image shows correctly | Paste URL in Slack/Twitter |
| Google search result | Favicon shows next to URL | Search for your domain |
Chrome DevTools Method
The fastest way to debug favicon issues is Chrome DevTools. Open your site, go to the Application tab, and look at the Manifest section. It shows every icon your manifest declares, their sizes, and whether they loaded successfully.
For the basic favicon (tab icon), check the Network tab and filter by "favicon" or "icon". You will see which requests the browser made and which file it actually loaded. A 404 on any of these requests means a missing file.
// Run in browser console to check all favicon-related link tags
document.querySelectorAll('link[rel*="icon"]').forEach(el => {
console.log(el.rel, el.href, el.sizes?.value || 'no size')
})Chrome's favicon database
%LOCALAPPDATA%\Google\Chrome\User Data\Default\Favicons, on macOS at ~/Library/Application Support/Google/Chrome/Default/Favicons, and on Linux at ~/.config/google-chrome/Default/Favicons. Clearing your browser cache does not touch this file. If you are stuck with a stale favicon during development, you can delete this database (with Chrome closed) to force a complete re-fetch of every favicon.Automated Testing with Playwright
Manual testing catches obvious problems, but it does not scale. If you have dozens of pages or deploy multiple times a day, you need automated checks. Playwright can verify that every expected favicon file exists, returns the correct MIME type, and has the right dimensions.
The following test script hits your live (or staging) URL, fetches each favicon reference from the HTML, and validates the response. It catches broken paths, missing files, wrong content types, and incorrectly sized images — all before a human ever opens the page.
import { test, expect } from '@playwright/test'
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000'
test.describe('Favicon validation', () => {
test('all favicon link tags return valid responses', async ({ page }) => {
await page.goto(BASE_URL)
// Collect every <link> tag related to icons
const iconLinks = await page.$$eval(
'link[rel*="icon"]',
(els) => els.map((el) => ({
rel: el.getAttribute('rel'),
href: el.getAttribute('href'),
sizes: el.getAttribute('sizes'),
type: el.getAttribute('type'),
}))
)
expect(iconLinks.length).toBeGreaterThan(0)
for (const link of iconLinks) {
const url = new URL(link.href!, BASE_URL).toString()
const res = await fetch(url)
expect(res.status, `${link.rel} (${link.href}) returned ${res.status}`).toBe(200)
// Validate MIME type matches declared type
const contentType = res.headers.get('content-type') || ''
if (link.type) {
expect(contentType).toContain(link.type)
}
// ICO files should serve as image/x-icon or image/vnd.microsoft.icon
if (link.href?.endsWith('.ico')) {
expect(contentType).toMatch(/image\/(x-icon|vnd\.microsoft\.icon)/)
}
// SVG files should serve as image/svg+xml
if (link.href?.endsWith('.svg')) {
expect(contentType).toContain('image/svg+xml')
}
}
})
test('favicon.ico exists at site root', async () => {
const res = await fetch(`${BASE_URL}/favicon.ico`)
expect(res.status).toBe(200)
const contentType = res.headers.get('content-type') || ''
expect(contentType).toMatch(/image\/(x-icon|vnd\.microsoft\.icon)/)
})
test('apple-touch-icon is 180x180', async ({ page }) => {
await page.goto(BASE_URL)
const href = await page.$eval(
'link[rel="apple-touch-icon"]',
(el) => el.getAttribute('href')
)
expect(href).toBeTruthy()
// Fetch the image and check dimensions
const url = new URL(href!, BASE_URL).toString()
const res = await fetch(url)
const buffer = Buffer.from(await res.arrayBuffer())
// PNG header contains dimensions at bytes 16-23
const width = buffer.readUInt32BE(16)
const height = buffer.readUInt32BE(18) // Note: readUInt32BE(18) for height
// For a proper check, use sharp or jimp:
// const metadata = await sharp(buffer).metadata()
// expect(metadata.width).toBe(180)
// expect(metadata.height).toBe(180)
})
test('web manifest icons are all reachable', async ({ page }) => {
await page.goto(BASE_URL)
const manifestHref = await page.$eval(
'link[rel="manifest"]',
(el) => el.getAttribute('href')
).catch(() => null)
if (!manifestHref) return // No manifest, skip
const manifestUrl = new URL(manifestHref, BASE_URL).toString()
const manifest = await (await fetch(manifestUrl)).json()
for (const icon of manifest.icons || []) {
const iconUrl = new URL(icon.src, manifestUrl).toString()
const res = await fetch(iconUrl)
expect(res.status, `Manifest icon ${icon.src} returned ${res.status}`).toBe(200)
}
})
})CI/CD Integration
Running favicon checks locally is useful. Running them automatically on every deploy is better. The following GitHub Actions workflow runs the Playwright favicon tests against your preview deployment. It catches regressions before they reach production — a broken path in a refactor, a missing file after a build tool change, or a MIME type misconfiguration from a CDN update.
name: Favicon Check
on:
deployment_status:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
favicon-test:
runs-on: ubuntu-latest
if: github.event_name != 'deployment_status' || github.event.deployment_status.state == 'success'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Build site
run: npm run build
- name: Start server
run: npm start &
env:
PORT: 3000
- name: Wait for server
run: npx wait-on http://localhost:3000 --timeout 30000
- name: Run favicon tests
run: npx playwright test tests/favicon.spec.ts
env:
BASE_URL: http://localhost:3000
- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: favicon-test-results
path: test-results/The Favicon Debugging Flowchart
When a favicon is not showing up, work through this decision tree. Most problems fall into one of five categories, and checking them in order saves time.
- Is the icon missing entirely? View source and look for
<link rel="icon">tags. If there are none, your HTML is not declaring a favicon. Add the appropriate link tags. - Are the link tags present but the file returns a 404? Open DevTools Network tab, filter by the favicon filename. If you see a 404, the file path in the link tag does not match where the file actually lives. Common cause: the file is in
/public/but the link tag says/assets/favicon.ico. - Does the file load but show the wrong icon? You are probably hitting a caching issue. Try an incognito window. If the correct icon shows in incognito, clear Chrome's favicon database (see the InfoBox above for the file path).
- Does the file load but the server returns the wrong MIME type? Some CDNs and static hosts serve
.icofiles asapplication/octet-streaminstead ofimage/x-icon. Check theContent-Typeresponse header in DevTools. Some browsers silently ignore favicons with incorrect MIME types. - Does everything work on desktop but fail on mobile? iOS requires
apple-touch-icon(a 180x180 PNG). Android uses the web manifest'siconsarray. Neither platform falls back tofavicon.icofor home screen icons. Check that you have platform-specific declarations.
Common Failure Modes
Blank square on iOS
iOS ignores favicon.ico and .svg favicons entirely. It looks specifically for <link rel="apple-touch-icon"> pointing to a 180x180 PNG. If that tag or file is missing, iOS renders either a screenshot thumbnail of your page (which looks terrible) or a blank icon.
Apple is strict about this
apple-touch-icon must be exactly 180x180 pixels. Apple will not use a 192x192 or 512x512 fallback. If you only serve one PNG size, make it 180x180 and let the Apple Touch icon link point to it.Favicon not updating after deploy
Browsers cache favicons more aggressively than almost any other resource. Some browsers store favicons in a separate database that persists even after clearing the normal cache. Workarounds include:
- Hard refresh (Ctrl+Shift+R or Cmd+Shift+R)
- Incognito/private window
- Append a cache-buster query string:
favicon.ico?v=2 - Clear the favicon cache specifically (Chrome stores it in a separate SQLite database)
- Wait. Some browsers update favicon caches on a 24-hour cycle.
Wrong icon from a previous version
If you changed your favicon but the old one still appears, check that you did not leave stale files in your deployment. A public/favicon.ico from a previous version will override an app/favicon.ico in some frameworks. Make sure there is only one source of truth.
Pixelated on high-DPI screens
If your favicon looks pixelated on a Retina or high-DPI display, you are probably serving only a 16x16 icon. Modern high-DPI screens render browser tabs at 32x32 or even 48x48 pixels. Your ICO file needs to include these larger sizes, or you need explicit sizes attributes on your link tags pointing to appropriately sized PNGs.
Platform-Specific Debugging
Safari: SVG favicon quirks
Safari on macOS added SVG favicon support in version 15, but it comes with caveats that trip up developers. First, Safari requires the SVG to have a viewBox attribute — without it, the icon may not render at all. Second, Safari does not re-fetch SVG favicons when you navigate between pages on the same domain. If your SVG has an error, you will not see the fix until you fully quit and relaunch Safari, or clear the favicon cache at ~/Library/Safari/Favicon Cache/.
Safari on iOS does not support SVG favicons at all. It only uses the apple-touch-icon PNG. There is no workaround — you must provide a PNG alongside your SVG.
Safari also renders the pinned tab icon (the small monochrome icon in the tab bar when you have many tabs) from the mask-icon link tag. This icon must be a single-color SVG. If it has multiple colors, Safari will silently ignore it and show a generic globe. The color attribute on the link tag controls the fill color.
<!-- Safari pinned tab icon — must be a single-color SVG -->
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#DA7756">Firefox: aggressive caching
Firefox caches favicons in a Places database (places.sqlite in your profile folder) and this cache is notoriously sticky. Clearing browser history and cache does not always clear the favicon cache. During development, these methods force Firefox to re-fetch:
- Navigate directly to
yoursite.com/favicon.icoand hard-refresh that specific URL (Ctrl+Shift+R). - Open
about:configand togglebrowser.chrome.faviconsto false, reload, then toggle it back to true. - Delete the
favicons.sqlitefile in your Firefox profile directory (with Firefox closed). Find your profile atabout:support→ Profile Directory.
Firefox also has a unique behavior with animated favicons: it is the only major browser that supports animated GIF and APNG favicons. The animation plays in the browser tab. This can be either a feature or a nuisance depending on your use case, but be aware that an animated GIF served as a favicon will animate in Firefox and show a static first frame in other browsers.
Edge: ICO parsing differences
Microsoft Edge (Chromium-based) generally handles favicons the same way as Chrome, but there are edge cases with ICO files. Edge is stricter about ICO file structure than Chrome. If your ICO file was created by simply renaming a PNG to .ico (a common shortcut), Chrome will accept it but Edge may not display it correctly. A proper ICO file is a container format that can hold multiple sizes — use a tool that generates real ICO files with embedded 16x16, 32x32, and 48x48 PNGs.
Edge also has a specific behavior with taskbar pinning on Windows. When a user pins your site to the taskbar, Edge uses the largest icon it can find — preferring the 512x512 from the web manifest. If that icon is missing, the pinned site shows a low-quality upscaled version of the 32x32 favicon. Always include a 512x512 icon in your web manifest for Windows taskbar quality.
Online Testing Tools
RealFaviconGenerator has a free favicon checker that tests your live URL against all major platforms and reports missing formats, incorrect sizes, and implementation issues. It catches things you would probably miss manually.
For social sharing previews, use the platform's own debugger tools. Facebook has the Sharing Debugger, Twitter has the Card Validator, and LinkedIn has the Post Inspector. Each one fetches your page and shows exactly what users will see when your URL is shared.
Test early, test often
Related Articles
How to Add Favicons to Your Next.js App (App Router)
A step-by-step walkthrough for setting up favicons in Next.js 14+ with the App Router. Covers file conventions, metadata API, and common pitfalls.
How to Make a Favicon from Your Existing Logo
Your logo probably will not work as a favicon without changes. Here is the practical workflow for extracting, simplifying, and exporting a favicon from any logo.
How to Add a Favicon to WordPress (3 Methods)
WordPress has a built-in favicon feature, but it has limits. Three ways to set up your favicon, from simplest to most complete.