Next-Sanity-Tailwind image component

Luciano Villalobos
Luciano Villalobos
|

Introduction

Sanity offers a toolkit to interact with Next.js with the following features:

  • Client-side live real-time preview for authenticated users.
  • URL-helper for Sanity's image pipeline
  • Rich-text component for Portable Text
  • GROQ syntax highlighting

In this post we will deal with the second feature. Once our application is initialized using this package, the default way we have to present an image in the browser looks like this.

import { sanityClient } from "../lib/sanity.server";
import imageUrlBuilder from "@sanity/image-url";
import Image from "next/image";

const builder = imageUrlBuilder(sanityClient);

function urlFor(source) {
  return builder.image(source);
}

export default function ComponentWithImage({data}) {
	return (
		<>
			<Image
			  className="object-contain object-center filter grayscale"
			  src={urlFor(data.image).url()}
			  alt={data.name}
			  layout="fill"
			/>
		</>
	)
}

SanityImage component

My intention is to create a reusable component not only to reduce lines of code when working with images coming from Sanity, but also to provide a better user experience when loading images using Tailwind filters like blur or grayscale.
We only need React hooks to handle the loading/loaded state and the clsx library to apply CSS classes conditionally. This is our reusable component.

import Image from "next/image";
import { useState, useEffect } from "react";
import clsx from "clsx";
import imageUrlBuilder from "@sanity/image-url";
import { sanityClient } from "@/lib/sanity.server";

const builder = imageUrlBuilder(sanityClient);

function urlFor(source) {
  return builder.image(source);
}

const filters = [
  {
    name: "blur",
    loading: "blur-2xl scale-110",
    loaded: "blur-0 scale-100",
  },
  {
    name: "grayscale",
    loading: "grayscale blur-2xl scale-110",
    loaded: "grayscale-0 blur-0 scale-100",
  },
  {
    name: "hue-rotate",
    loading: "hue-rotate-90 blur-2xl scale-110",
    loaded: "hue-rotate-0 blur-0 scale-100",
  },
];

export default function SanityImage(props) {
  const [isLoading, setLoading] = useState(true);
  const [selectedFilter, setSelectedFilter] = useState(filters[0]);

  useEffect(() => {
    const filter = filters.find((f) => f.name === props.placeholderFilter);
    if (filter) {
      setSelectedFilter(filter);
    }
  }, [props.placeholderFilter]);

  return (
    <>
      <Image
        src={urlFor(props.src).url()}
        alt={props.alt}
        className={clsx(
          props.className,
          "duration-300 ease-in-out",
          isLoading ? selectedFilter.loading : selectedFilter.loaded
        )}
        layout="fill"
        onLoadingComplete={() => setLoading(false)}
      />
    </>
  );
}

Finally we can use our SanityImage component as follows.

import SanityImage from "./SanityImage";

export default function ComponentWithImage({data}) {
	return (
		<>
			<SanityImage
			  placeholderFilter="grayscale"
			  className="object-contain object-center"
			  src={data.image}
			/>
		</>
	)
}

The default filter is Blur, so if this is the only filter you require it will not be necessary to add the placeholderFilter property to our component. Cheers!


Previous Post →

Why I decided to build this site

Next Post →

Next-Sanity starter.