Improve gallery loading speed with image optimization and lazy loading

Implement a thumbnail proxy endpoint and lazy loading for gallery images to significantly reduce page load times and improve user experience.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 517dfa7b-26ac-463d-a6e1-a58c6df97188
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 1488855f-6772-48e1-9244-282d1cc91352
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/517dfa7b-26ac-463d-a6e1-a58c6df97188/VgutZ7W
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sebastjanartic
2026-03-01 18:52:46 +00:00
parent 97d09ba27e
commit 39d43cd876
4 changed files with 616 additions and 535 deletions
+43 -5
View File
@@ -1,5 +1,5 @@
import { useQuery } from "@tanstack/react-query";
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback, useRef } from "react";
import { ChevronLeft, ChevronRight, X, Images, Maximize2 } from "lucide-react";
interface GalleryImage {
@@ -9,6 +9,45 @@ interface GalleryImage {
large: string;
}
function thumbUrl(src: string) {
return `/api/gallery/thumb?src=${encodeURIComponent(src)}`;
}
function LazyImage({ src, alt, className, onClick }: { src: string; alt: string; className?: string; onClick?: () => void }) {
const ref = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver(
([entry]) => { if (entry.isIntersecting) { setVisible(true); obs.disconnect(); } },
{ rootMargin: "200px" }
);
obs.observe(el);
return () => obs.disconnect();
}, []);
return (
<div ref={ref} className={className} onClick={onClick}>
{visible ? (
<>
{!loaded && <div className="absolute inset-0 bg-muted animate-pulse" />}
<img
src={src}
alt={alt}
className={`w-full h-full object-cover transition-opacity duration-300 ${loaded ? "opacity-100" : "opacity-0"}`}
onLoad={() => setLoaded(true)}
/>
</>
) : (
<div className="w-full h-full bg-muted" />
)}
</div>
);
}
function Lightbox({
images,
startIndex,
@@ -242,11 +281,10 @@ export default function GalleryPage() {
className="group relative aspect-square rounded-lg overflow-hidden bg-card border border-card-border cursor-pointer"
data-testid={`button-gallery-image-${i}`}
>
<img
src={img.thumb}
<LazyImage
src={thumbUrl(img.thumb)}
alt={img.fileName}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
loading="lazy"
className="absolute inset-0 w-full h-full transition-transform duration-500 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" />
<div className="absolute bottom-0 left-0 right-0 p-2 opacity-0 group-hover:opacity-100 transition-opacity">