Build a Simple Next.js Interactive Web-Based Image Gallery

Written by

in

In the digital age, images are ubiquitous. From personal photos to professional portfolios, we interact with images daily. Creating a dynamic and user-friendly image gallery is a common need for web developers. This tutorial will guide you through building a simple, yet functional, interactive image gallery using Next.js, a powerful React framework for building modern web applications. We’ll cover everything from setting up your project to implementing features like image display, navigation, and basic responsiveness.

Why Build an Image Gallery with Next.js?

Next.js offers several advantages for building web applications, especially those that involve content like images:

  • Performance: Next.js provides features like server-side rendering (SSR) and static site generation (SSG), which can significantly improve your website’s performance and SEO. SSR and SSG mean that the initial HTML is pre-rendered on the server or at build time, leading to faster initial load times.
  • SEO Optimization: SSR and SSG make your content easily crawlable by search engines, leading to better search engine rankings.
  • Developer Experience: Next.js simplifies many aspects of React development, such as routing, image optimization, and API routes.
  • Image Optimization: Next.js includes built-in image optimization features, allowing you to automatically resize and optimize images for different devices, improving loading speed and user experience.

Prerequisites

Before we begin, make sure you have the following installed:

  • Node.js and npm (or yarn): You’ll need Node.js and npm (or yarn) installed on your system. You can download them from the official Node.js website.
  • A Code Editor: A code editor like VS Code, Sublime Text, or Atom is recommended.
  • Basic knowledge of JavaScript and React: Familiarity with JavaScript and React fundamentals will be helpful.

Setting Up Your Next.js Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app image-gallery-app
cd image-gallery-app

This command creates a new Next.js project named “image-gallery-app” and navigates you into the project directory. Next.js will ask a few questions during the setup, you can generally accept the defaults.

Project Structure Overview

Let’s take a quick look at the project structure:

  • pages/: This directory is where you’ll create your pages. Each file in this directory represents a route in your application. For example, `pages/index.js` corresponds to the `/` route.
  • public/: This directory is for static assets like images, fonts, and other files that are directly accessible by the browser.
  • styles/: This directory is where you’ll keep your CSS or other styling files.
  • package.json: This file contains information about your project, including dependencies and scripts.

Creating the Image Gallery Layout

Now, let’s create the basic layout for our image gallery. We’ll start by modifying the `pages/index.js` file. Open this file in your code editor and replace its contents with the following code:

import Image from 'next/image';
import styles from '../styles/Home.module.css';

export default function Home() {
  const images = [
    { id: 1, src: '/images/image1.jpg', alt: 'Image 1' },
    { id: 2, src: '/images/image2.jpg', alt: 'Image 2' },
    { id: 3, src: '/images/image3.jpg', alt: 'Image 3' },
    // Add more images here
  ];

  return (
    <div>
      <main>
        <h1>My Image Gallery</h1>
        <div>
          {images.map((image) => (
            <div>
              
            </div>
          ))}
        </div>
      </main>
    </div>
  );
}

Let’s break down this code:

  • Import Statements: We import `Image` from `next/image` for optimized image handling and the `styles` object from `../styles/Home.module.css` to apply styles.
  • Image Data: An array named `images` stores the image data. Each object in the array has an `id`, `src` (the path to the image), and `alt` (alternative text for accessibility). Make sure you have image files in your `public/images` directory.
  • JSX Structure: The code renders a basic layout with a title and a container for the images.
  • Image Component: We use the `Image` component from Next.js to render each image.
  • Image Props:
    • src: The path to the image.
    • alt: The alternative text for the image.
    • width: The desired width of the image.
    • height: The desired height of the image.
    • layout="responsive": This tells Next.js to make the image responsive, scaling it to fit the container.
    • objectFit="cover": This property ensures that the image covers the entire container while maintaining its aspect ratio.

Styling the Image Gallery

Now, let’s add some styles to make our image gallery look good. Open the `styles/Home.module.css` file and replace its contents with the following code:

.container {
  min-height: 100vh;
  padding: 0 0.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.main {
  padding: 5rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.title {
  margin: 0;
  line-height: 1.15;
  font-size: 3rem;
  text-align: center;
}

.gallery {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
  max-width: 1000px;
}

.imageContainer {
  width: 100%;
  max-width: 500px;
  border: 1px solid #ccc;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}

@media (max-width: 600px) {
  .imageContainer {
    width: 100%;
  }
}

This CSS code styles the overall layout, the title, and the image containers. The `gallery` class uses `flex-wrap: wrap` to make the images wrap to the next line on smaller screens. The `imageContainer` provides a border, rounded corners, and a subtle shadow for each image.

Adding Images to Your Project

Before you run your application, you need to add some images to your project. Create a folder named `images` inside the `public` directory. Then, add a few image files (e.g., `image1.jpg`, `image2.jpg`, `image3.jpg`) to the `images` folder. You can use any images you like.

Running Your Application

To run your Next.js application, open your terminal, navigate to your project directory, and run the following command:

npm run dev

or

yarn dev

This will start the development server. Open your web browser and go to `http://localhost:3000`. You should see your basic image gallery with the images you added.

Adding Image Navigation (Click to Enlarge)

A common feature of image galleries is the ability to click on an image to enlarge it. Let’s add this functionality. We’ll use a state variable to track the currently selected image and a modal to display the enlarged image.

Modify your `pages/index.js` file with the following changes:

import Image from 'next/image';
import styles from '../styles/Home.module.css';
import { useState } from 'react';

export default function Home() {
  const [selectedImage, setSelectedImage] = useState(null);
  const images = [
    { id: 1, src: '/images/image1.jpg', alt: 'Image 1' },
    { id: 2, src: '/images/image2.jpg', alt: 'Image 2' },
    { id: 3, src: '/images/image3.jpg', alt: 'Image 3' },
    // Add more images here
  ];

  const openModal = (image) => {
    setSelectedImage(image);
  };

  const closeModal = () => {
    setSelectedImage(null);
  };

  return (
    <div>
      <main>
        <h1>My Image Gallery</h1>
        <div>
          {images.map((image) => (
            <div>
               openModal(image)}
                style={{ cursor: 'pointer' }}
              />
            </div>
          ))}
        </div>
        {selectedImage && (
          <div>
            <div> e.stopPropagation()}>
              
              <button>Close</button>
            </div>
          </div>
        )}
      </main>
    </div>
  );
}

Here’s what changed:

  • Import `useState`: We import the `useState` hook from React.
  • `selectedImage` State: We declare a state variable `selectedImage` to hold the currently selected image. Initially, it’s set to `null`.
  • `openModal` Function: This function sets the `selectedImage` state to the clicked image, opening the modal.
  • `closeModal` Function: This function sets `selectedImage` back to `null`, closing the modal.
  • `onClick` Handler: We add an `onClick` handler to the `Image` component. When an image is clicked, the `openModal` function is called, passing the image data.
  • Conditional Rendering of Modal: The modal is conditionally rendered based on the value of `selectedImage`. If `selectedImage` is not `null`, the modal is displayed.
  • Modal Structure: The modal contains a background overlay and a content area. The `onClick` handler on the modal background calls `closeModal` to close it. The inner `onClick` on the `modalContent` prevents the modal from closing when you click inside it.
  • Enlarged Image: Inside the modal, we display the `selectedImage` using the `Image` component.

Now, let’s add the CSS for the modal. Add the following CSS rules to your `styles/Home.module.css` file:


.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.8);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.modalContent {
  background-color: white;
  padding: 20px;
  border-radius: 8px;
  max-width: 90%;
  max-height: 90%;
  overflow: auto;
  position: relative;
}

.closeButton {
  position: absolute;
  top: 10px;
  right: 10px;
  background-color: #ccc;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
}

This CSS styles the modal’s background, content, and close button. The modal is positioned fixed to cover the entire screen, and the background is semi-transparent. The modal content is centered and has a white background. The close button is positioned in the top-right corner.

Adding Image Navigation (Next/Prev Buttons)

To make the gallery even more user-friendly, let’s add “Next” and “Previous” buttons to navigate through the enlarged images. Modify your `pages/index.js` file as follows:

import Image from 'next/image';
import styles from '../styles/Home.module.css';
import { useState } from 'react';

export default function Home() {
  const [selectedImage, setSelectedImage] = useState(null);
  const [currentIndex, setCurrentIndex] = useState(0);
  const images = [
    { id: 1, src: '/images/image1.jpg', alt: 'Image 1' },
    { id: 2, src: '/images/image2.jpg', alt: 'Image 2' },
    { id: 3, src: '/images/image3.jpg', alt: 'Image 3' },
    // Add more images here
  ];

  const openModal = (image) => {
    setSelectedImage(image);
    setCurrentIndex(images.findIndex(img => img.id === image.id));
  };

  const closeModal = () => {
    setSelectedImage(null);
  };

  const goToNext = () => {
    setCurrentIndex((prevIndex) => (prevIndex + 1) % images.length);
    setSelectedImage(images[(currentIndex + 1) % images.length]);
  };

  const goToPrev = () => {
    setCurrentIndex((prevIndex) => (prevIndex - 1 + images.length) % images.length);
    setSelectedImage(images[(currentIndex - 1 + images.length) % images.length]);
  };

  return (
    <div>
      <main>
        <h1>My Image Gallery</h1>
        <div>
          {images.map((image) => (
            <div>
               openModal(image)}
                style={{ cursor: 'pointer' }}
              />
            </div>
          ))}
        </div>
        {selectedImage && (
          <div>
            <div> e.stopPropagation()}>
              <button>‹</button>
              
              <button>›</button>
              <button>Close</button>
            </div>
          </div>
        )}
      </main>
    </div>
  );
}

Here’s what changed:

  • `currentIndex` State: We add a state variable `currentIndex` to keep track of the index of the currently displayed image in the `images` array.
  • Updating `openModal` Function: Inside `openModal`, we now set `currentIndex` to the index of the clicked image.
  • `goToNext` Function: This function calculates the index of the next image using the modulo operator (`%`) to handle looping. It then updates `currentIndex` and `selectedImage`.
  • `goToPrev` Function: This function calculates the index of the previous image, again using the modulo operator to loop back to the beginning when at the first image. It then updates `currentIndex` and `selectedImage`.
  • Navigation Buttons: We add two buttons inside the modal: one for “Previous” and one for “Next”. The “Previous” button uses the left-pointing arrow character (`‹`), and the “Next” button uses the right-pointing arrow character (`›`).
  • Button Handlers: The `onClick` handlers of these buttons call `goToPrev` and `goToNext`, respectively.

Now, add the following CSS to `styles/Home.module.css`:


.navButtonPrev, .navButtonNext {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background-color: rgba(0, 0, 0, 0.5);
  color: white;
  border: none;
  padding: 10px 15px;
  font-size: 20px;
  cursor: pointer;
  border-radius: 4px;
}

.navButtonPrev {
  left: 10px;
}

.navButtonNext {
  right: 10px;
}

This CSS styles the navigation buttons, positioning them on the left and right sides of the modal content.

Adding Image Loading States

To improve the user experience, let’s add a loading state while images are being loaded. This will prevent a blank space from appearing while the image is loading. We can use the `onLoadingComplete` and `placeholder` props of the `Image` component.

Modify your `pages/index.js` file as follows:

import Image from 'next/image';
import styles from '../styles/Home.module.css';
import { useState } from 'react';

export default function Home() {
  const [selectedImage, setSelectedImage] = useState(null);
  const [currentIndex, setCurrentIndex] = useState(0);
  const [loading, setLoading] = useState(false);
  const images = [
    { id: 1, src: '/images/image1.jpg', alt: 'Image 1' },
    { id: 2, src: '/images/image2.jpg', alt: 'Image 2' },
    { id: 3, src: '/images/image3.jpg', alt: 'Image 3' },
    // Add more images here
  ];

  const openModal = (image) => {
    setSelectedImage(image);
    setCurrentIndex(images.findIndex(img => img.id === image.id));
  };

  const closeModal = () => {
    setSelectedImage(null);
  };

  const goToNext = () => {
    setCurrentIndex((prevIndex) => (prevIndex + 1) % images.length);
    setSelectedImage(images[(currentIndex + 1) % images.length]);
  };

  const goToPrev = () => {
    setCurrentIndex((prevIndex) => (prevIndex - 1 + images.length) % images.length);
    setSelectedImage(images[(currentIndex - 1 + images.length) % images.length]);
  };

  return (
    <div>
      <main>
        <h1>My Image Gallery</h1>
        <div>
          {images.map((image) => (
            <div>
               openModal(image)}
                style={{ cursor: 'pointer' }}
                placeholder="blur"
                blurDataURL={`data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=`}
              />
            </div>
          ))}
        </div>
        {selectedImage && (
          <div>
            <div> e.stopPropagation()}>
              <button>‹</button>
              
              <button>›</button>
              <button>Close</button>
            </div>
          </div>
        )}
      </main>
    </div>
  );
}

Here’s what changed:

  • `loading` State: We introduced a new state variable named `loading`, which is not used directly in this example, but it sets the stage for a more advanced loading indicator implementation.
  • `placeholder=”blur”` Prop: We added `placeholder=”blur”` to the `Image` component. This tells Next.js to use a blurred version of the image while the actual image is loading.
  • `blurDataURL` Prop: We added `blurDataURL` to the `Image` component. This is a base64 encoded string representing a tiny blurred version of the image. This small image is displayed immediately, creating a smoother loading experience. You can generate a placeholder image using tools like `next/image`’s built-in functionality, or you can use a service like TinyPNG to optimize a smaller version of your image.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to fix them when building a Next.js image gallery:

  • Incorrect Image Paths: Double-check that your image paths (`src` attributes) are correct and that the images are in the `public` directory.
  • Missing Image Files: Make sure you have the image files in the correct location (`public/images/`).
  • Incorrect Styling: Ensure your CSS is correctly applied and that the selectors are targeting the right elements. Use your browser’s developer tools to inspect the elements and see if the styles are being applied.
  • Layout Issues: Use the `layout=”responsive”` and `objectFit` props correctly to ensure your images are displayed properly on different screen sizes.
  • Modal Not Closing: Make sure the `onClick` event on the modal background correctly calls the `closeModal` function. Also, ensure that the `onClick` event on the modal content prevents the event from bubbling up to the background.
  • Navigation Errors: The modulo operator (`%`) is crucial for creating circular navigation. Double-check your calculations in the `goToNext` and `goToPrev` functions.
  • Accessibility Issues: Always provide `alt` text for your images to improve accessibility for users with disabilities.

Key Takeaways

  • Next.js provides excellent features for building performant and SEO-friendly image galleries.
  • The `next/image` component simplifies image optimization.
  • State management with `useState` is essential for creating interactive features like modals and navigation.
  • CSS is used to style the layout and appearance of your gallery.
  • Consider adding features like image loading indicators and responsive design for a better user experience.

FAQ

Here are some frequently asked questions about building an image gallery in Next.js:

  1. How do I optimize images for different devices?

    The `next/image` component automatically optimizes images. It generates different image sizes based on the user’s device and screen size. You can also customize the optimization process using the `next.config.js` file.

  2. How can I add captions to my images?

    You can add captions by adding a `caption` property to your image data objects and rendering the caption below the image using a `p` tag or similar element. You would need to update the `images` array with a `caption` property for each image and then render the caption within the gallery and modal.

  3. How do I handle a large number of images?

    For a large number of images, you should consider implementing pagination or infinite scrolling to improve performance. You can fetch image data in chunks from an API or a database to avoid loading all images at once. You might also want to explore using a headless CMS to manage your image data.

  4. How can I deploy my image gallery?

    You can deploy your Next.js image gallery to various platforms, such as Vercel (recommended), Netlify, or AWS. Vercel is particularly well-suited for Next.js applications, offering easy deployment and automatic optimization.

Building a dynamic image gallery with Next.js is a rewarding project that combines modern web development practices with a practical application. By following these steps and understanding the underlying concepts, you can create a visually appealing and functional image gallery that enhances your web projects. Remember to experiment with different features, styles, and optimizations to tailor the gallery to your specific needs. With Next.js, the possibilities are vast, and the journey of learning and building is just as important as the final product. Keep exploring, keep building, and always strive to improve your skills. The web is constantly evolving, and so should your knowledge.