Skip to content

Track Image impressions in Next.js

An impression in web statistics counts how many times a user views a particular element on a website. The element could be an ad, image, or something else.

Impressions are an essential piece of data that analytics software tracks.

In this article, we will learn how to use react-intersection-observer to track image impressions and then how to store them in our browser’s localStorage.

The completed project is on Codesandbox. Fork it and run the code.

<Codesandbox id="image-intersection-observer-lkhpbg" title="image-intersection-observer" />
Code language: HTML, XML (xml)

The source code is also available on GitHub.

The knowledge of Next.js and the localStorage API is required to get the most out of this article. We will also use the react-intersection-observer library to create the demo, so familiarity with it will be helpful.

We create a new Next.js application by running the command below in our terminal.

npx create-next-app next-image-intersection-observer-app

Next, we navigate into the project directory.

cd next-image-intersection-observer-app

Then run the code below to install react-intersection-observer.

npm install react-intersection-observer

We are building an image tracking application. As the user scrolls through an application, we track and increase an impression count when the user scrolls past a particular image.

We will also add a visual indicator to the image to inform us when it is in view.

This demo is similar to an analytics system that tracks impressions.

Update the index.js file with the code below:

import Image from "next/image";
export default function Home() {
  return (
    <div className="w-full h-full flex flex-col justify-center items-center px-10  pt-10 bg-zinc-900 relative">
    
      <h1 className="text-4xl font-bold text-white mb-16 text-center">
        Next.js Intersection Observer Article
      </h1>
      
      {Array.from(Array(3).keys()).map((i) => (
        <p className="text-white text-2xl mb-16" key={i}>
          Irure pariatur velit est anim ipsum anim aliquip officia velit
          consectetur. Duis sint ut consectetur ea anim. Sit proident culpa
          velit officia do incididunt Lorem in deserunt non adipisicing occaecat
          magna. Occaecat occaecat esse excepteur consequat occaecat cupidatat
          aliquip labore esse ad ea. Laboris id excepteur nisi voluptate sunt
          anim commodo amet reprehenderit.
        </p>
      ))}
      
      <div className="mb-10">
        <Image
          src="/images/1.png"
          width={300}
          height={400}
          alt="something amazing"
        />
      </div>
      
      {Array.from(Array(5).keys()).map((i) => (
        <p className="text-white text-2xl mb-16" key={i}>
          Irure pariatur velit est anim ipsum anim aliquip officia velit
          consectetur. Duis sint ut consectetur ea anim. Sit proident culpa
          velit officia do incididunt Lorem in deserunt non adipisicing occaecat
          magna. Occaecat occaecat esse excepteur consequat occaecat cupidatat
          aliquip labore esse ad ea. Laboris id excepteur nisi voluptate sunt
          anim commodo amet reprehenderit.
        </p>
      ))}
      
    </div>
  );
}
Code language: JavaScript (javascript)

Here, we create an array, loop through some dummy text, and add the image between the text.

react-intersection-observer is an implementation of the Intersection Observer API for React. It informs us when an element enters or leaves the viewport.

It provides a useInView hook we can use to monitor the inView state of components. There is also a custom InView component, which we will be using. We modify the index.js file to include these with:

import Image from "next/image";
import { InView } from "react-intersection-observer";

export default function Home() {
  return (
    <div className="w-full h-full flex flex-col justify-center items-center px-10  pt-10 bg-zinc-900 relative">
      <h1 className="text-4xl font-bold text-white mb-16 text-center">
        Next.js Intersection Observer Article
      </h1>
      
      {Array.from(Array(3).keys()).map((i) => (
        <p className="text-white text-2xl mb-16" key={i}>
          Irure pariatur velit est anim ipsum anim aliquip officia velit
          consectetur....
        </p>
      ))}
      
      <InView threshold={0.6}>
        {({ ref, inView }) => (
          <div className="mb-10" ref={ref}>
            <Image
              src="/images/1.png"
              width={300}
              height={400}
              alt="something amazing"
            />
            <p
              className={`text-white text-2xl font-bold ${
                inView ? "text-green-600" : "text-red-600"
              }`}
            >{`Image in view? ${inView} `}</p>
          </div>
        )}
      </InView>
      
      {Array.from(Array(5).keys()).map((i) => (
        <p className="text-white text-2xl mb-16" key={i}>
          Irure pariatur velit est anim ipsum anim aliquip officia velit
          consectetur....
        </p>
      ))}
      
    </div>
  );
}
Code language: JavaScript (javascript)

In the code above, we do the following:

  • Import InView from react-intersection-observer.

  • Wrap our image with InView using the render props method.

  • We have access to an inView boolean in the render props, and we use it to add a visual indicator. The indicator informs us when the image is in view. We change the text color to green when the image is visible and red when it’s not. We also change the content of the indicator’s text based on the inView state.

Having integrated react-intersection-observer and can track when the image is in view, let’s set up the functionality for the impression counter. Updating the index.js file, we have:

import { useState } from "react";
import Image from "next/image";
import { InView } from "react-intersection-observer";

function ImpressionCounter(inView, count, setCount) {
  if (inView) {
    setCount((count) => count + 1);
    localStorage.setItem("impressionCount", count);
  }
}
export default function Home() {
  const [count, setCount] = useState(0);
  
  return (
    <div className="w-full h-full flex flex-col justify-center items-center px-10  pt-10 bg-zinc-900 relative">
    
      <h2 className="text-4xl font-bold text-white mb-16 fixed top-10 right-10">
        Impression Count: {count}
      </h2>
      
      {Array.from(Array(3).keys()).map((i) => (
        <p className="text-white text-2xl mb-16" key={i}>
          Irure pariatur velit est anim ipsum anim aliquip officia velit
          consectetur...
        </p>
      ))}
      
      <InView
        threshold={0.6}
        onChange={(inView) => ImpressionCounter(inView, count, setCount)}
      >
        {({ ref, inView }) => (
          <div className="mb-10" ref={ref}>
            <Image
              src="/images/1.png"
              width={300}
              height={400}
              alt="something amazing"
            />
            <p
              className={`text-white text-2xl font-bold ${
                inView ? "text-green-600" : "text-red-600"
              }`}
            >{`Image in view? ${inView} `}</p>
          </div>
        )}
      </InView>
      
      {Array.from(Array(5).keys()).map((i) => (
        <p className="text-white text-2xl mb-16" key={i}>
          Irure pariatur velit est anim ipsum anim aliquip officia velit
          consectetur...
        </p>
      ))}
      
    </div>
  );
}
Code language: JavaScript (javascript)

In the code above, we do the following:

  • Create a count state using the useState hook. count will hold the impression count of the image, which we want to store. We set its default value to 0.

  • Create an ImpressionCounter function. It takes in inView, count, and setCount as arguments. We check if inView is true, and if so, increase the count by 1 and save that new count to localStorage.

  • To call ImpressionCounter, we pass it to InView’s onChange method. onChange will run and call ImpressionCounter only when the inView state changes.

  • Display the total count in the h2 tag so people can easily know how many impressions the image has.

Here’s what the final page looks like. Notice the increasing impression count.

In this article, we learned how to track image impressions using Next.js and react-intersection-observer and store those impressions in our browser’s localStorage.

React Intersection Observer React Intersection Observer – Multiple Observers

Back to top

Featured Post