diff --git a/src/app/(category-sidebar)/[collection]/page.tsx b/src/app/(category-sidebar)/[collection]/page.tsx index 55fcf75..e71cc3c 100644 --- a/src/app/(category-sidebar)/[collection]/page.tsx +++ b/src/app/(category-sidebar)/[collection]/page.tsx @@ -5,16 +5,18 @@ import { count } from "drizzle-orm"; import Image from "next/image"; export default async function Home(props: { - params: { + params: Promise<{ collection: string; - }; + }>; }) { + const collectionName = decodeURIComponent((await props.params).collection); + const collections = await db.query.collections.findMany({ with: { categories: true, }, where: (collections, { eq }) => - eq(collections.name, decodeURIComponent(props.params.collection)), + eq(collections.name, decodeURIComponent(collectionName)), orderBy: (collections, { asc }) => asc(collections.name), }); let imageCount = 0; diff --git a/src/components/ui/link.tsx b/src/components/ui/link.tsx index e15c2fd..71bb9fe 100644 --- a/src/components/ui/link.tsx +++ b/src/components/ui/link.tsx @@ -2,7 +2,7 @@ import NextLink from "next/link"; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; type PrefetchImage = { srcset: string; @@ -38,17 +38,52 @@ const seen = new Set(); export const Link: typeof NextLink = (({ children, ...props }) => { const [images, setImages] = useState([]); + const linkRef = useRef(null); const router = useRouter(); + let prefetchTimeout: NodeJS.Timeout | null = null; // Track the timeout ID + useEffect(() => { if (props.prefetch === false) { return; } - void prefetchImages(String(props.href)).then((images) => { - setImages(images); - }, console.error); + + const linkElement = linkRef.current; + if (!linkElement) return; + + const observer = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + if (entry.isIntersecting) { + // Set a timeout to trigger prefetch after 1 second + prefetchTimeout = setTimeout(() => { + void prefetchImages(String(props.href)).then((images) => { + setImages(images); + }, console.error); + // Stop observing once images are prefetched + observer.unobserve(entry.target); + }, 300); // 300ms delay + } else if (prefetchTimeout) { + // If the element leaves the viewport before 1 second, cancel the prefetch + clearTimeout(prefetchTimeout); + prefetchTimeout = null; + } + }, + { rootMargin: "0px", threshold: 0.1 }, // Trigger when at least 10% is visible + ); + + observer.observe(linkElement); + + return () => { + observer.disconnect(); // Cleanup the observer when the component unmounts + if (prefetchTimeout) { + clearTimeout(prefetchTimeout); // Clear any pending timeouts when component unmounts + } + }; }, [props.href, props.prefetch]); + return ( { for (const image of images) { if (image.loading === "lazy" || seen.has(image.srcset)) {