React component on npm
react-site-icon
For once, I had a problem at work with no great pre-existing solution online, so I made one.
Display any site’s favicon effortlessly. One prop in, the right icon out. Zero dependencies. < 1KB.
import { SiteIcon } from 'react-site-icon';
<SiteIcon domain="github.com" />
<SiteIcon
domain="example.com"
fallback={<span>?</span>}
/>
Live demo | Try it on StackBlitz
Why
Favicons sound easy. They aren’t. Different CDNs, missing icons, blurry upscales, layout shift, SSR caching, default-globe placeholders that look like real favicons — most “just give me a favicon” libraries punt on these.
react-site-icon handles them for you:
- Reliable fallback detection. Distinguishes real favicons from Google’s default globe via a
naturalWidthcheck (see How it works below) — no other React favicon library does this. - Sharp at every size. Only exposes sizes Google’s CDN actually serves (
12 | 16 | 24 | 28 | 32 | 40 | 48 | 50 | 64 | 96 | 128) — no blurry upscaling. - Forgiving domain input.
"github.com","https://github.com","https://github.com/user/repo"— all work. Internally normalized to hostname. - Three render strategies. Pick
lazy(fallback during load),eager(fastest paint), orhidden(zero layout shift). - SSR + hydration aware. Renders fallback on the server. Post-mount, checks
img.completevia a ref callback so browser-cached / SSR-prefetched images don’t get stuck on the fallback. - Stale-prop safe. Change
domainmid-load and you’ll never see the previous domain’s icon flash through. - One CDN request. No canvas pixel-comparison, no
crossOrigintainting, no second fetch to the target domain.
How it works
Google’s faviconV2 CDN returns a favicon for any domain. When the domain has no favicon, it returns a default globe icon that is always 16x16 pixels, regardless of the size you requested. This size mismatch is the detection mechanism:
react-site-icon
|
Google faviconV2 CDN request
(single request)
|
+-----+-----+
| |
Real favicon Default globe
(64x64 px) (always 16x16)
| |
naturalWidth > 16 naturalWidth = 16
| |
Show <img> Show fallback
After the image loads, we check naturalWidth on the <img> element. A real favicon comes back at the requested size (e.g. 64x64). The default globe is always 16x16. One comparison, zero CORS issues, single network request.
Note: When
size={16}, the component internally requests 24px from the CDN (and renders at 16x16) so detection still works — otherwise both real favicons and the globe would be 16px, making them indistinguishable.
Why other approaches fail:
- Direct
fetch()to Google CDN — blocked by CORS. Google doesn’t sendAccess-Control-Allow-Origin. - Canvas pixel comparison — requires
crossOriginattribute, which Google’s CDN rejects. Canvas is tainted. - Fetching from target domain — unreliable. Many sites don’t serve
/favicon.ico, return redirects, or have restrictive CORS. Adds latency from a second request.
Install
npm install react-site-icon