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
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.
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.
npm install react-site-icon| Prop | Type | Default | Description |
|---|---|---|---|
domain |
string |
required | Domain to fetch the favicon for (e.g. "github.com" or "https://github.com/user/repo") |
size |
SiteIconSize |
32 |
Requested favicon size in pixels (12 | 16 | 24 | 28 | 32 | 40 | 48 | 50 | 64 | 96 | 128) |
fallback |
ReactNode |
null |
Content to render when no favicon is available |
strategy |
'lazy' | 'eager' | 'hidden' |
'lazy' |
Detection strategy (see Strategies) |
onResolved |
(found: boolean) => void |
-- | Called when detection completes. true = found, false = globe/error |
All standard <img> props (className, style, alt, loading, decoding, data-*, aria-*) are spread onto the underlying element. src, width, height, onLoad, onError are reserved.
Shows fallback during detection, swaps to favicon when found.
<SiteIcon domain="github.com" fallback={<Spinner />} />Shows the <img> immediately. May flash Google's default globe briefly before detection completes.
<SiteIcon domain="github.com" strategy="eager" />hidden
Renders a sized empty placeholder during detection. Prevents layout shift.
<SiteIcon domain="github.com" strategy="hidden" />| Strategy | During detection | Best for |
|---|---|---|
lazy |
Shows fallback | Lists with loading states |
eager |
Shows <img> directly |
Above-the-fold content, fastest paint |
hidden |
Sized empty placeholder | Preventing layout shift |
On the server, SiteIcon renders the fallback (for lazy) or a placeholder (for hidden). Detection runs on the client after hydration. No window or document access during SSR.
const imgRef = useRef<HTMLImageElement>(null);
<SiteIcon ref={imgRef} domain="github.com" />The ref points to the <img> element when a favicon is found. When the fallback renders, the ref is null.
Track favicon availability:
function FaviconWithStatus({ domain }: { domain: string }) {
const [found, setFound] = useState<boolean | null>(null);
return (
<div>
<SiteIcon domain={domain} onResolved={setFound} />
{found === false && <span>No favicon available</span>}
</div>
);
}Memoize
onResolvedif you don't want re-fires on re-render.
| Feature | react-site-icon | favicon-stealer | DIY Google CDN | DIY domain fetch | Proxy services |
|---|---|---|---|---|---|
| Bundle size | < 1KB | ~3.5KB | 0 | 0 | 0 |
| Dependencies | 0 | 2 | 0 | 0 | 0 |
| Fallback detection | Yes | No | No | No | No |
| Network requests | 1 | 1-2 | 1 | 1+ (may fail) | 1 |
| React versions | 17, 18, 19 | 19 only | Any | Any | Any |
| SSR compatible | Yes | Unknown | Manual | Manual | Manual |
| TypeScript | Full | Full | Manual | Manual | Manual |
git clone https://github.com/TomyCesaille/react-site-icon.git
cd react-site-icon
npm install
npm testPRs welcome.