Track Image Impressions in LocalStorage and NuxtJS

Divine Orji

An impression is when any form of digital media appears on a user’s screen. Knowing what kind of content your users often view can help you focus on delivering better content to improve user experience.

This article demonstrates how to track image impressions using Intersection Observer API and store impression counts in your browser’s local storage.

According to MDN Docs:

“The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport.”

With this API, you can track your images and execute a callback function when each image comes into view on your browser.

NuxtJS is an open-source framework that enhances the capabilities of VueJS with features such as fast server-side rendering, pre-configurations, and a large ecosystem of modules. All these tools provide an excellent developer experience.

CodeSandbox and GitHub Repo

Click the CodeSandbox link below to view a complete demo of the content presented in this article:

To view its source code on GitHub, click here.


To understand the concepts presented in this article, you need the following:

  • Good knowledge of JavaScript and VueJS.
  • Experience with NuxtJS is not strictly required, but it will make things easier.
  • Create a free Cloudinary account to store, manipulate, and deliver your images.

Get started with create-nuxt-app

In your terminal, navigate to your preferred project folder and run the command below to set up Nuxt for the project:

1npx create-nuxt-app nuxt-image-impressions

You will be prompted with selections to aid project setup. Choose based on the recommendations below:

After a successful setup, open the project in your preferred code editor, and run the command below in its terminal:

1yarn dev

This will set up a dev environment with hot reload that you can view on your browser at localhost:3000.

Set up the project

In your project, navigate to pages/index.vue and update <script> with the code below:

2export default {
3 name: 'IndexPage',
4 data() {
5 return {
6 count: 0,
7 poems: [
8 {
9 text: `Low-anchored cloud, Newfoundland air, fountain head and source of rivers, dew-cloth, dream drapery, and napkin spread by fays; Drifting meadow of the air, where bloom the dasied banks and violets, and in whose fenny labyrinth the bittern booms and heron wades; Spirit of the lake and seas and rivers, bear only perfumes and the scent Of healing herbs to just men's fields! - Henry David Thoreau`,
10 imageUrl: ``,
11 imageAlt: `mist`,
12 },
13 {
14 text: `Last night I drove a car, not knowing how to drive; not owning a car, I drove and knocked down people I loved...went 120 through one town. I stopped at Hedgeville and slept in the back seat... excited about my new life. - Gregory Corso`,
15 imageUrl: ``,
16 imageAlt: `car`,
17 },
18 {
19 text: `As your screen began to fade & flicker, and I shut you down & closed your cover, I knew all you needed was a little rest; I am so sorry for putting you to the test, but I cannot get through a day without you; You know how much I need you in all I do; You knew I would just die without your light; Good little laptop you gave me such a fright - Tia Maria`,
20 imageUrl: ``,
21 imageAlt: `laptop`,
22 },
23 ],
24 }
25 },

In the code above, data() returns a count variable with its initial value at 0; it also returns an array of objects accessed by the variable poems.

You will use this data to set up the project’s markup dynamically.

In your pages/index.vue, update <template> with the code below:

2 <div>
3 <nav class="sticky top-0 bg-white font-bold text-2xl p-5 shadow">
4 <h1>Impressions: {{ count }}</h1>
5 </nav>
6 <div class="w-full max-w-4xl p-5 mx-auto">
7 <div v-for="(poem, idx) in poems" :key="idx"
8 class="grid lg:grid-cols-12 gap-4 min-h-screen content-center"
9 >
10 <div class="lg:col-span-7">
11 <div>
12 <img :src="poem.imageUrl" :alt="poem.imageAlt"
13 class="w-full" />
14 </div>
15 </div>
16 <div class="lg:col-span-5">
17 <p class="text-xl">{{ poem.text }}</p>
18 </div>
19 </div>
20 </div>
21 </div>

In this step, you created a sticky nav to display the image impressions when you begin tracking them. You also used v-for to iterate through the poems array, showing each of the poems and their images.

Implement Intersection Observer API

In Vue/Nuxt applications, you can implement the Intersection Observer API manually or use [vue-intersection-observer](, a component that greatly simplifies the process.

To install, run the command below in your project’s terminal:

1yarn add vue-intersection-observer

After its successful installation, update <script> in your pages/index.vue with the code below:

2import Observer from 'vue-intersection-observer'
4export default {
5 // pre-existing code, add the new ones below
6 components: {
7 Observer,
8 },
9 methods: {
10 onChange(entry) {
11 if (entry.isIntersecting) {
12 this.count = this.count + 1;
13 }
14 }
15 }

In the code above:

  • You imported Observer from vue-intersection-observer and specified it as a component.
  • entry.isIntersecting checks whether the specified element is in view and runs the onChange function to update the impression count.

Next, update your <template> code to implement the Observer component on the images you want to track:

2 <!-- pre-existing code, update only parent div for img element -->
3 <observer @on-change="onChange">
4 <img :src="poem.imageUrl" :alt="poem.imageAlt" class="w-full" />
5 </observer>

In the code above:

  • You changed the parent <div> of your <img /> element to <observer>.
  • You added an @on-change attribute to execute the callback function when the view is active.

When you scroll up and down in your browser, you will notice that the number of image impressions is being recorded and displayed on the nav, as shown below:

Store impression counts in localStorage

To store your impression counts in localStorage, navigate to pages/index.vue and update methods: in your <script> with the code below:

2export default {
3// pre-existing code
4 methods: {
5 onChange(entry) {
6 if (entry.isIntersecting) {
7 this.count = this.count + 1
8 localStorage.setItem('Impressions', this.count)
9 }
10 }
11 }

In your browser, check your localStorage to be sure it works:


In this article, you learned how to track impressions in your Nuxt project with Intersection Observer API and store impression counts in your browser’s local storage. To learn more about these concepts, check out the resources below.


Divine Orji

Software Engineer and Technical Writer

I am a software engineer passionate about building fast, scalable apps with beautiful user interfaces.