DEV Community

Joseph42A
Joseph42A

Posted on

Practical Applications of the Next.js 14 Image Component: Hands-on Examples

Introduction

In today's web development landscape, optimizing images is pivotal for performance and user experience. While Next.js offers an Image component for image optimization, crafting a reusable image solution tailored to specific application needs proves invaluable.

This blog explores the significance of a customizable image component beyond Next.js' defaults. We'll dive into the benefits of creating a bespoke image component, enabling versatile image handling for diverse use cases in Next.js applications. Let's uncover how this customizable approach enhances image management, elevates user experience, and optimizes performance across Next.js projects.

Setting Up Next.js & Required Libraries

You can easily create nextjs app in my time 14 by running

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

I'll stick with javascript and tailwindcss, feel free to use typescript if you want, I'll provide repo link at the end.

After creating your project, we have to install three more libraries that we need for the image component that we gonna create

npm install sharp plaiceholder @plaiceholder/next
Enter fullscreen mode Exit fullscreen mode

We need these packages to convert our images to base64 for blurDataUrl the next image component requires us when we want to use external images.

Since we are using remote images from a remote source, in my case I will use Unsplash, so we have to add Unsplash image API into next config remotePatterns, as well as add the image formats

const nextConfig = {
  images: {
    formats: ["image/avif", "image/webp"],
    remotePatterns: [
      {
        hostname: "images.unsplash.com",
      },
    ],
  },
};

Enter fullscreen mode Exit fullscreen mode

Creating a Reusable Image Component

Our image component is very simple you can copy and paste the code below, I named BaseImage since it will be my base Image component throughout my project

"use client";
import React, { useState } from "react";
import placeholderImage from "@/assets/placeholder.jpg";
import Image from "next/image";
import defaultBlur from "@/assets/blurData";

export default function BaseImage({ src, width, height, blurData, ...rest }) {
  const [imgSrc, setImgSrc] = useState(src);

  return (
    <Image
      src={imgSrc}
      width={width}
      height={height}
      placeholder="blur"
      blurDataURL={!!blurData ? blurData : defaultBlur}
      onError={() => {
        setImgSrc(placeholderImage);
      }}
      {...rest}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

Notice that the component is a client component because we are using a state to set the fallback src image, and we're passing the blurData of a remote image that we gonna use, notice we also have a default blur data, case remote image has some problem to access it we will show our fallback blur data.

Creating base64 util function

Before we use our BaseImage component, we have to add a function that takes our remote source and returns a base64 of that image so then we'll be able to use that as a blurDataUrl for that image, here is the implementation

import { unstable_noStore } from "next/cache";
import { getPlaiceholder } from "plaiceholder";

async function getBlurData(src) {
  unstable_noStore();
  try {
    const buffer = await fetch(src).then(async (res) => {
      return Buffer.from(await res.arrayBuffer());
    });

    const data = await getPlaiceholder(buffer);
    return data;
  } catch (err) {
    console.error("Error fetching or processing image:", err);
    return { base64: "", img: "" };
  }
}

export { getBlurData };
Enter fullscreen mode Exit fullscreen mode

Note that the function I created under the root of app/lib/utils.js because the plaiceholder package is working under the nodejs environment so we have to put it under the app directory.

Example: Implementing in a Card Component

Here is an example of how to use our image component with a grid card container

import { getBlurData } from "@/app/lib/utils";

import React from "react";
import BaseImage from "./BaseImage";
import Link from "next/link";

const data = [
  {
    id: 1,
    title: "Cat 1",
    description:
      "Cat 1 is a playful and energetic feline who loves to explore and chase toys around the house.",
    imgSrc:
      "https://images.unsplash.com/photo-1646753522408-077ef9839300?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwcm9maWxlLXBhZ2V8NjZ8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=500&q=60",
  },
  {
    id: 2,
    title: "Cat 2",
    description:
      "Cat 2 is a calm and affectionate furry friend who enjoys lounging in cozy spots and getting cuddles.",
    imgSrc:
      "https://images.unsplash.com/photo-1651950519238-15835722f8bb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwcm9maWxlLXBhZ2V8Mjh8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=500&q=60",
  },
  {
    id: 3,
    title: "Cat 3",
    description:
      "Cat 3 is an adventurous and curious cat who loves exploring the outdoors and climbing trees.",
    imgSrc:
      "https://images.unsplash.com/photo-1651950537598-373e4358d320?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwcm9maWxlLXBhZ2V8MjV8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=500&q=60",
  },
  // This is for an error image
  // {
  //   id: 4,
  //   title: "Cat 2",
  //   description:
  //     "Cat 3 is an adventurous and curious cat who loves exploring the outdoors and climbing trees.",
  //   imgSrc:
  //     "https://images.unsplash.com/photo-1651950537598-373e4358d3202?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwcm9maWxlLXBhZ2V8MjV8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=500&q=60",
  // },
];

const Cards = () => {
  return (
    <section className="w-fit mx-auto grid grid-cols-1 lg:grid-cols-3 md:grid-cols-2 justify-items-center justify-center gap-y-20 gap-x-14 mt-10 mb-5">
      {data.map((x) => (
        <Card key={x.id} {...x} />
      ))}
    </section>
  );
};

async function Card({ imgSrc, title, description }) {
  const { base64 } = await getBlurData(imgSrc);
  return (
    <div className="bg-white shadow-md rounded-xl duration-500 hover:scale-105 hover:shadow-xl">
      <Link href="#">
        <div className="relative h-80">
          <BaseImage
            blurData={base64}
            src={imgSrc}
            alt={title}
            fill
            sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 30vw"
            className="object-cover rounded-t-xl"
          />
        </div>
        <div className="px-4 py-3 w-72">
          <p className="text-lg font-bold text-black truncate block capitalize">
            {title}
          </p>
          <div className="flex items-center">
            <p className="text-lg font-semibold text-gray cursor-auto my-3">
              {description}
            </p>
          </div>
        </div>
      </Link>
    </div>
  );
}

export default Cards;

Enter fullscreen mode Exit fullscreen mode

Implementation with Two-Section Layout

import React from "react";
import BaseImage from "./BaseImage";
import { getBlurData } from "@/app/lib/utils";

const LAYOUT_URL =
  "https://images.unsplash.com/photo-1465146344425-f00d5f5c8f07";
const TwoSectionLayout = async () => {
  const { base64 } = await getBlurData(LAYOUT_URL);

  return (
    <section className="text-gray-600 body-font">
      <div className="w-11/12 max-w-4xl bg-orange-400 grid grid-cols-1 sm:grid-cols-12 mx-auto items-center  px-5 py-24 ">
        <div className="lg:pr-24 md:pr-16 col-span-12 md:col-span-6 md:text-left mb-16 md:mb-0 text-center">
          <h1 className="title-font sm:text-4xl text-3xl mb-4 font-medium text-gray-900">
            Before they sold out
            <br className="hidden lg:inline-block" />
            readme gluten
          </h1>
          <p className="mb-8 leading-relaxed">
            Copper mug try-hard pitchfork pour-over freeway heirloom neutral air
            plant cold-pressed tacos poke beard tote bag. Heirloom echo park
            mash tote bag selvage hot chicken authentic tumeric truffaut hexagon
            try-hard chambray.
          </p>
          <div className="">
            <button className="inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg">
              Button
            </button>
          </div>
        </div>
        <div className="w-full relative  h-[600px] col-span-12 md:col-span-6 ">
          <BaseImage
            fill
            sizes="(min-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
            className="object-cover rounded"
            alt="hero"
            src={LAYOUT_URL}
            blurData={base64}
          />
        </div>
      </div>
    </section>
  );
};

export default TwoSectionLayout;

Enter fullscreen mode Exit fullscreen mode

Conclusion

Optimizing images is integral to web development's performance and user appeal. Throughout this exploration, we've emphasized the pivotal role of a tailored image component in Next.js, surpassing default capabilities.

Github link: Source Code

Top comments (0)