Skip to content

Commit a203207

Browse files
ved015sreedharsreeram
authored andcommitted
perf: deduplicate OG data fetches across document cards (#831)
1 parent 6c28c57 commit a203207

1 file changed

Lines changed: 66 additions & 21 deletions

File tree

apps/web/components/memories-grid.tsx

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,48 @@ type OgData = {
7676
image?: string
7777
}
7878

79+
const ogCache = new Map<string, OgData>()
80+
const ogInflight = new Map<string, Promise<OgData | null>>()
81+
const ogFailures = new Map<string, number>()
82+
const OG_FAILURE_TTL = 30_000
83+
84+
function fetchOgData(url: string): Promise<OgData | null> {
85+
const cached = ogCache.get(url)
86+
if (cached) return Promise.resolve(cached)
87+
88+
const failedAt = ogFailures.get(url)
89+
if (failedAt && Date.now() - failedAt < OG_FAILURE_TTL) {
90+
return Promise.resolve(null)
91+
}
92+
93+
const inflight = ogInflight.get(url)
94+
if (inflight) return inflight
95+
96+
const promise = fetch(`/api/og?url=${encodeURIComponent(url)}`)
97+
.then((res) => {
98+
if (!res.ok) throw new Error("Failed")
99+
return res.json()
100+
})
101+
.then((data) => {
102+
const result: OgData = { title: data?.title, image: data?.image }
103+
if (!result.title && !result.image) {
104+
throw new Error("Empty metadata")
105+
}
106+
ogCache.set(url, result)
107+
ogInflight.delete(url)
108+
ogFailures.delete(url)
109+
return result
110+
})
111+
.catch(() => {
112+
ogInflight.delete(url)
113+
ogFailures.set(url, Date.now())
114+
return null
115+
})
116+
117+
ogInflight.set(url, promise)
118+
return promise
119+
}
120+
79121
const PAGE_SIZE = 100
80122
const MAX_TOTAL = 1000
81123

@@ -682,7 +724,6 @@ const DocumentCard = memo(
682724
const [rotation, setRotation] = useState({ rotateX: 0, rotateY: 0 })
683725
const cardRef = useRef<HTMLButtonElement>(null)
684726
const [ogData, setOgData] = useState<OgData | null>(null)
685-
const [isLoadingOg, setIsLoadingOg] = useState(false)
686727

687728
const ogImage = (document as DocumentWithMemories & { ogImage?: string })
688729
.ogImage
@@ -699,27 +740,31 @@ const DocumentCard = memo(
699740
const hideURL = document.url?.includes("docs.googleapis.com")
700741

701742
useEffect(() => {
702-
if (needsOgData && !ogData && !isLoadingOg && document.url) {
703-
setIsLoadingOg(true)
704-
fetch(`/api/og?url=${encodeURIComponent(document.url)}`)
705-
.then((res) => {
706-
if (!res.ok) throw new Error("Failed")
707-
return res.json()
708-
})
709-
.then((data) => {
710-
setOgData({
711-
title: data?.title,
712-
image: data?.image,
713-
})
714-
})
715-
.catch(() => {
716-
setOgData({})
717-
})
718-
.finally(() => {
719-
setIsLoadingOg(false)
720-
})
743+
if (!needsOgData || ogData || !document.url) return
744+
745+
let timeoutId: ReturnType<typeof setTimeout>
746+
let mounted = true
747+
748+
const attemptFetch = () => {
749+
if (!mounted || !document.url) return
750+
fetchOgData(document.url).then((data) => {
751+
if (!mounted) return
752+
if (data) {
753+
setOgData(data)
754+
} else {
755+
// Retry when the global TTL expires
756+
timeoutId = setTimeout(attemptFetch, 30_000)
757+
}
758+
})
759+
}
760+
761+
attemptFetch()
762+
763+
return () => {
764+
mounted = false
765+
clearTimeout(timeoutId)
721766
}
722-
}, [needsOgData, ogData, isLoadingOg, document.url])
767+
}, [needsOgData, ogData, document.url])
723768

724769
useEffect(() => {
725770
if (isSelectionMode) setRotation({ rotateX: 0, rotateY: 0 })

0 commit comments

Comments
 (0)