react-site-icon
v0.1.2
Published
A zero-dependency React component that displays any website's favicon from its domain name
Maintainers
Readme
react-site-icon
A React component to display any website's favicon from its domain. 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
Most favicon solutions have the same problem: no reliable fallback detection.
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 fallbackAfter 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-iconAPI
| 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.
Strategies
lazy (default)
Shows fallback during detection, swaps to favicon when found.
<SiteIcon domain="github.com" fallback={<Spinner />} />eager
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" />When to use which
| 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 |
Advanced
SSR
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.
Ref forwarding
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.
onResolved callback
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.
Compare
| 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 |
Contributing
git clone https://github.com/TomyCesaille/react-site-icon.git
cd react-site-icon
npm install
npm testPRs welcome.
