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
InViewfromreact-intersection-observer. -
Wrap our image with
InViewusing the render props method. -
We have access to an
inViewboolean 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 theinViewstate.
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
countstate using theuseStatehook.countwill hold the impression count of the image, which we want to store. We set its default value to 0. -
Create an
ImpressionCounterfunction. It takes ininView,count, andsetCountas arguments. We check ifinViewis true, and if so, increase the count by 1 and save that new count to localStorage. -
To call
ImpressionCounter, we pass it toInView’sonChangemethod.onChangewill run and callImpressionCounteronly when theinViewstate changes. -
Display the total count in the
h2tag 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