diff --git a/src/components/Favicon/index.tsx b/src/components/Favicon/index.tsx
index b7a4684e..fc003506 100644
--- a/src/components/Favicon/index.tsx
+++ b/src/components/Favicon/index.tsx
@@ -1,3 +1,4 @@
+import { isFaviconLoadFailed, markFaviconLoadFailed, normalizeFaviconDomain } from '@/lib/favicon-fail-cache'
import { cn } from '@/lib/utils'
import { useState } from 'react'
@@ -10,19 +11,23 @@ export function Favicon({
className?: string
fallback?: React.ReactNode
}) {
- const [loading, setLoading] = useState(true)
- const [error, setError] = useState(false)
- const trimmed = domain?.trim() ?? ''
- if (error || !trimmed) return fallback
+ const host = normalizeFaviconDomain(domain)
+ const knownFailed = host ? isFaviconLoadFailed(host) : true
+ const [loading, setLoading] = useState(!knownFailed)
+ const [error, setError] = useState(knownFailed)
+ if (error || !host) return fallback
return (
{loading &&
{fallback}
}

setError(true)}
+ onError={() => {
+ markFaviconLoadFailed(host)
+ setError(true)
+ }}
onLoad={() => setLoading(false)}
/>
diff --git a/src/lib/favicon-fail-cache.test.ts b/src/lib/favicon-fail-cache.test.ts
new file mode 100644
index 00000000..f63da435
--- /dev/null
+++ b/src/lib/favicon-fail-cache.test.ts
@@ -0,0 +1,20 @@
+import { describe, expect, it } from 'vitest'
+import {
+ isFaviconLoadFailed,
+ markFaviconLoadFailed,
+ normalizeFaviconDomain
+} from '@/lib/favicon-fail-cache'
+
+describe('favicon-fail-cache', () => {
+ it('normalizes domain casing and trailing dot', () => {
+ expect(normalizeFaviconDomain(' Example.COM. ')).toBe('example.com')
+ })
+
+ it('remembers failed domains for the session', () => {
+ const host = `fail-cache-test-${Date.now()}.example`
+ expect(isFaviconLoadFailed(host)).toBe(false)
+ markFaviconLoadFailed(host)
+ expect(isFaviconLoadFailed(host)).toBe(true)
+ expect(isFaviconLoadFailed(host.toUpperCase())).toBe(true)
+ })
+})
diff --git a/src/lib/favicon-fail-cache.ts b/src/lib/favicon-fail-cache.ts
new file mode 100644
index 00000000..b0c323bb
--- /dev/null
+++ b/src/lib/favicon-fail-cache.ts
@@ -0,0 +1,20 @@
+import { LRUCache } from 'lru-cache'
+
+/** Domains whose `https://{host}/favicon.ico` already failed — skip repeat network requests. */
+const FAILED_FAVICON_DOMAINS = new LRUCache({ max: 512 })
+
+export function normalizeFaviconDomain(domain: string): string {
+ return domain.trim().toLowerCase().replace(/\.$/, '')
+}
+
+export function isFaviconLoadFailed(domain: string): boolean {
+ const key = normalizeFaviconDomain(domain)
+ if (!key) return true
+ return FAILED_FAVICON_DOMAINS.has(key)
+}
+
+export function markFaviconLoadFailed(domain: string): void {
+ const key = normalizeFaviconDomain(domain)
+ if (!key) return
+ FAILED_FAVICON_DOMAINS.set(key, true)
+}