Building a Lazy Loading Artwork Carousel

web development

When I first published my art page I dropped eighteen high resolution images on the screen at once. It looked gorgeous but the initial load was terrible—every file was fetched eagerly because each <Image /> component had priority enabled. Visitors on mobile or slower connections paid the price for my enthusiasm.

I wanted something that felt better to browse and also respected bandwidth, so I built a carousel. Here's how it works.

1. Only render the image you need

Instead of mapping over every artwork, the carousel holds an index in React state. The main stage only renders a single <Image /> component bound to that index. Because the rest of the images are not in the tree, Next.js waits to load them until they are requested, which slashes the page's initial network waterfall.

const [currentIndex, setCurrentIndex] = useState(0)
const currentImage = images[currentIndex]
 
<Image
  src={currentImage.src}
  alt={currentImage.alt}
  priority={currentIndex === 0}
  loading={currentIndex === 0 ? 'eager' : 'lazy'}
  sizes="(max-width: 768px) 100vw, 1125px"
  style={{ width: '100%', height: 'auto' }}
/>

The very first artwork still uses priority so the page feels instant on load, but every subsequent slide is lazy loaded by the browser.

2. Add friendly controls

Buttons labelled “Previous” and “Next” drive the index forward and backward. I also register left and right arrow keys so that browsing with a keyboard feels natural. The active slide indicator (“3 / 18”) updates automatically because it is derived from the same piece of state.

useEffect(() => {
  const handleKeyDown = (event) => {
    if (event.key === 'ArrowRight') handleNext()
    if (event.key === 'ArrowLeft') handlePrevious()
  }
 
  window.addEventListener('keydown', handleKeyDown)
  return () => window.removeEventListener('keydown', handleKeyDown)
}, [handleNext, handlePrevious])

3. Lazy thumbnails for quick jumps

Finally, I render a horizontal strip of thumbnail buttons. They use the same Next.js image component but rely on the default lazy behaviour. That keeps the DOM light while still giving visitors a fast way to jump directly to a particular illustration.

<button
  type="button"
  onClick={() => selectImage(index)}
  aria-current={index === currentIndex}
>
  <Image
    src={image.src}
    alt={image.alt}
    loading="lazy"
    sizes="(max-width: 600px) 33vw, 200px"
  />
</button>

The end result is a page that loads quickly, feels smooth on mobile, and stays fun to explore. I'm excited to keep iterating on this pattern for future galleries.

© Aman Garg.