How to Build an Image Gallery with Next.js using the Pexels API and Chakra UI

In this tutorial, we‘ll walk through how to build an image gallery application using the Next.js framework, Chakra UI component library, and Pexels API for high-quality stock photos. By the end, you‘ll have a fully functional app that allows you to browse curated photos, search for specific images, and view image details on a separate page.

Here‘s a preview of what we‘ll be building:

[Screenshot of completed image gallery]

Why Next.js for an Image-Heavy App?

Next.js is a powerful, flexible React framework that provides a great developer experience and ships with many performance optimizations out of the box. This makes it an excellent choice for an image gallery, where performance is critical.

A few of the key benefits of Next.js for this use case are:

  • Built-in code splitting and dynamic imports to load minimal code on each page
  • Automatic image optimization using the next/image component
  • API routes for securely communicating with third-party APIs like Pexels
  • Static site generation and server-side rendering options for optimal performance
  • Streamlined deployment to Vercel hosting

Setting up the Pexels API

The Pexels API provides free access to thousands of high-quality stock photos we can use in our app. To get started:

  1. Sign up for a free account at https://pexels.com/join-consumer
  2. After verifying your email, visit https://pexels.com/api/new and click "Generate API Key"
  3. Copy your API key

We‘ll use this key later on to authenticate requests to the Pexels API and retrieve images to display in the app.

Initializing the Next.js App

First, make sure you have Node.js installed (version 10.13 or later). Then open your terminal and run:

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

This will create a new Next.js project in the image-gallery directory and navigate you into it.

Next, we‘ll install a few dependencies we need, including Chakra UI and the Pexels JavaScript client:

npm install @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4 @pexels/javascript-sdk

Now open the project in your preferred code editor. The basic file structure should look like this:

image-gallery/
  node_modules/
  pages/
    api/
    _app.js
    index.js
  public/
  styles/
  package.json

The key directories to note are:

  • pages: Where each file becomes a route based on its name
  • pages/api: Where we can define API endpoints
  • public: For static assets like images
  • styles: For global CSS (though we‘ll use Chakra‘s styling instead)

Configuring Chakra UI

Chakra UI is a simple, modular component library that will help us quickly build the UI for our image gallery.

To use it, we need to wrap our app in the Chakra Provider component. Open pages/_app.js and replace the contents with:

import { ChakraProvider } from ‘@chakra-ui/react‘

function MyApp({ Component, pageProps }) {
  return (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  )
}

export default MyApp

This will allow us to use Chakra components anywhere in the app.

Fetching Data from the Pexels API

Now let‘s fetch some actual images from Pexels to display in the gallery. We‘ll do this on the server side to keep our API key secure.

Create a new file called pexels.js in the pages/api directory:

// pages/api/pexels.js
import { createClient } from ‘@pexels/javascript-sdk‘;

const client = createClient(process.env.PEXELS_API_KEY);

export default async (req, res) => {
  const query = req.query.q;
  let photos;

  if (query) {
    photos = await client.photos.search({ query, per_page: 20 });
  } else {
    photos = await client.photos.curated({ per_page: 20 });
  }

  res.status(200).json(photos);
};

Here‘s what‘s happening:

  1. We import the createClient function from the Pexels SDK and use it to initialize a client with our API key (which we‘ll add to an .env file shortly).

  2. We create a default async function to handle incoming requests to this API route.

  3. If the request includes a ‘q‘ query param, we use that to search for specific photos. Otherwise, we just retrieve the curated photos.

  4. In either case, we specify to load 20 photos per page.

  5. Finally, we send a JSON response with the resulting photos back to the client.

To keep our API key secure, create a new file called .env.local in the root directory and add your Pexels key like so:

PEXELS_API_KEY=YOUR_API_KEY

Make sure to .gitignore this file so you don‘t accidentally commit your key!

Displaying Images in a Grid

With the API route set up, we can now fetch images in our main index.js page and display them in a grid.

Replace the contents of pages/index.js with:

import { useState } from ‘react‘;
import { Box, SimpleGrid, Spinner } from ‘@chakra-ui/react‘;
import Image from ‘next/image‘;
import { getPlaiceholder } from ‘plaiceholder‘;

export default function Home({ initialPhotos }) {
  const [photos, setPhotos] = useState(initialPhotos);

  return (
    <Box maxW="7xl" mx="auto" my={10}>
      <SimpleGrid columns={[2, null, 3]} spacing={10}>
        {photos.map(photo => (
          <Box key={photo.id} rounded="md" overflow="hidden">
            <Image
              src={photo.src.large}
              width={photo.width} 
              height={photo.height}
              layout="responsive"
              alt={photo.alt}
              placeholder="blur"
              blurDataURL={photo.blurDataURL}
            />
          </Box>
        ))}
      </SimpleGrid>

      {!photos && <Spinner size="xl" />}
    </Box>
  );
}

export async function getStaticProps() {
  const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/pexels`);
  const data = await res.json();

  const photos = await Promise.all(
    data.photos.map(async photo => {      
      const { base64 } = await getPlaiceholder(photo.src.large);
      return { ...photo, blurDataURL: base64 };
    })
  );

  return {
    props: {
      initialPhotos: photos,
    },
  };
}

This may look intimidating, but let‘s break it down piece by piece:

  • We import the useState hook to manage the image state, some Chakra components for the grid and loading spinner, the Next.js Image component for optimization, and the getPlaiceholder library to generate blurred placeholder images.

  • The Home component receives initialPhotos as a prop and uses that to initialize the photos state.

  • In the component body, we render a SimpleGrid with the mapped photo data. The grid is responsive, showing 2 columns on mobile and 3 on larger screens.

  • For each photo, we render an Image component, passing the photo URL, width, height, and alt text. We use layout="responsive" so the images scale nicely.

  • To improve perceived loading, we pass a blurred placeholder as well, which will display while the full image loads. This is what the blurDataURL and placeholder="blur" props achieve.

  • If no photos are loaded yet (while waiting for the API call), we show a loading spinner.

  • The magic happens in getStaticProps, which is called at build time to fetch the initial photos. Here we:

    1. Fetch the photos from our API route
    2. For each photo, use getPlaiceholder to generate a blurred base64-encoded placeholder
    3. Return the fetched photos (with placeholders) as a prop
  • By doing this data fetching server-side at build time, we ensure the initial page load is very fast, since the data is available immediately. Subsequent updates to the photos (e.g. from searching) can then happen client-side.

Adding Search Functionality

Now let‘s allow users to search for specific types of images using the Pexels search endpoint we added to our API route.

In pages/index.js, add the following code above the returned JSX:

const handleSearch = async query => {
  const res = await fetch(`/api/pexels?q=${encodeURIComponent(query)}`);
  const data = await res.json();
  setPhotos(data.photos);
};

This function will fire when the user submits a search. It calls our API route with the search query, parses the response, and updates the photos state with the results.

Now add a search bar to the UI by importing Input and IconButton from Chakra and adding the following above the grid:

<Box mb={8}>
  <form onSubmit={e => {
    e.preventDefault();
    handleSearch(e.target.elements.query.value);
  }}>
    <Input
      name="query"      
      placeholder="Search for photos"
      size="lg"
      w="100%"
      maxW="500px"
    />
    <IconButton 
      type="submit"
      aria-label="Search"
      icon={<SearchIcon />} 
      size="lg"
      colorScheme="blue"
      ml={2}
    />
  </form>  
</Box>

Now users can search for any kind of photo and instantly see the results in the grid!

Creating an Image Details Page

As a final feature, let‘s allow users to click on an image to view more details on a separate page.

First, update the grid items in pages/index.js to link to a details page:

<Link href={`/photos/${photo.id}`}>
  <Box key={photo.id} rounded="md" overflow="hidden" cursor="pointer">
    <Image ... />
  </Box>  
</Link>

Now each image will link to a page like /photos/12345.

Next, create a new file pages/photos/[id].js:

import { useState } from ‘react‘;
import { Box, Heading, Image, Text } from ‘@chakra-ui/react‘; 

const PhotoPage = ({ photo }) => {
  return (
    <Box maxW="3xl" mx="auto" my={10}>
      <Heading as="h1" size="xl" mb={4}>
        Photo by {photo.photographer}
      </Heading>

      <Box mb={8}>
        <Image
          src={photo.src.large}
          width={photo.width}
          height={photo.height}
          layout="responsive"
          alt={photo.alt}
        />
      </Box>

      <Text color="gray.600">
        Download image:{‘ ‘}
        <Link href={photo.src.original}>
          Original size ({Math.round(photo.width / 1024)}MB)
        </Link>
      </Text>
    </Box>
  );
};

export default PhotoPage;

export async function getServerSideProps({ params }) {
  const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/pexels/${params.id}`);
  const data = await res.json();
  const photo = data.photo;

  if (!photo) {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      photo,
    }, 
  };
}

The key points:

  • This is a dynamic route that catches any value after /photos/ and passes it as a param (the photo ID).
  • We use getServerSideProps to fetch the photo data from an endpoint like /api/pexels/12345. If no photo is found, we return a 404.
  • The photo data is passed as a prop, which we use to display the photographer attribution, full-size image, and download link.

To finish wiring this up, add the /api/pexels/[id] endpoint to pages/api/pexels.js:

if (req.query.id) {
  const photo = await client.photos.show({ id: req.query.id });
  res.status(200).json({ photo });
}

And that‘s it! We now have a fully functional image gallery app with:

  • Blazing fast initial load by fetching data at build time
  • Seamless image optimization and lazy loading
  • Search functionality to find specific types of images
  • Detailed pages for individual photos
  • Clean, responsive design powered by Chakra UI

Deployment and Next Steps

The last step is to deploy your beautiful creation for the world to see. Vercel makes this incredibly easy. Simply push your code to a Git repository and import the project into Vercel. It will automatically detect the Next.js app, build it, and provide you a URL.

Potential next steps and enhancements:

  • Add filtering by color, orientation, size, etc. using the Pexels API
  • Infinite loading to fetch more images as you scroll
  • Create shareable links for searches and individual photos
  • Allow users to favorite photos and view their favorites
  • Dig into the Pexels API docs and see what other features you can build!

I hope this tutorial has given you a solid foundation for building your own image gallery in Next.js. Feel free to take the concepts you‘ve learned and make it your own. Happy coding!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *