Track Image Impressions in Gatsby.js with Supabase

Banner for a MediaJam post

Emmanuel Ugwu

How can we know if our efforts are worth the time and resources if we don't measure the most crucial digital analytics metrics? Understanding and tracking the correct metrics is critical in ensuring that marketing activities are effective.

Impression as a form of metric refers to the number of times a web content was displayed to users. This metric vastly differs from reach, which only measures the number of people that have viewed your content. It is important not to confuse an impression with an engagement.

This article demonstrates how to track image impressions using Gatsby.js and Supabase — a serverless database.

CodeSandbox and GitHub

We completed this project in CodeSandbox. Fork it to get started quickly.

The source code is on GitHub.


To get the most out of this article, the following requirements apply:

  • Basic understanding of Gatsby.js
  • Supabase account (sign up here)
  • Cloudinary account (sign up here)

Project Setup and Installation

Create a Gatsby.js app in a new folder by running the following command in the terminal:

1npm init gatsby

It will request a project title and the directory name for the project. Continue to follow the prompts to select a preferred language (JavaScript or TypeScript), CMS, styling tools, and other features. Then Gatsby.js will ask to create a Gatsby site in the directory of the project:

1Create a new Gatsby site in the folder <project-name>
2 Shall we do this? <Y/n> : Yes

Navigate into the project directory and install Cloudinary React SDK, Supabase, and react-intersection-observer dependencies.

1cd <project-name>
2 npm install cloudinary-react @supabase/supabase-js react-intersection-observer

Running npm run develop starts the project on the local development server at https://localhost:8000 in our browser.

Setting up a Supabase Project

What is Supabase?

Supabase is an open-source Firebase alternative made up of tools that help developers build projects faster by handling backend functions.

Behind the scenes, Supabase uses the PostgreSQL database and it is regarded widely as one of the best tools based on Postgres - a highly scalable relational database. The platform develops a REST API from the database's tables and columns. Its autogenerated APIs include built-in features like filtering and sorting.

To get started, create an account in Supabase here. We'll also need a GitHub account; click here to create one.

After logging in, we’ll be redirected to the Supabase dashboard, as shown below, where we’ll create a new Supabase project for the demo application.


Set a name and password for the new project and click on the “New project" button to create the project as shown below.


As the project is being built, we’ll create the database for the image impressions by clicking on the database icon shown on the sidebar. After that, click on the “New” button at the top right of the screen to create a table for the database.


We created multiple columns for the database table in the above image. The created columns represent the following:

  • id - key value to access the database
  • name - name of the database
  • created_at - time stamp for creating the database
  • views - number of image impressions

After creating these columns, we’ll access the Table editor by clicking the Table icon shown on the sidebar to add a default value for our table. Click on “insert row” and add the default values below.


Navigate to the SQL editor and create a query tab to add a stored procedure to our database. A stored procedure allows us to add or extend functionality to the database.


Add the code snippet below to the new query to create the stored procedure for our database:

1create function increment (row_id int)
2 returns void as
3 $$
4 update pages
5 set views = views + 1
6 where id = row_id;
7 $$
8 language sql volatile;

Let's break down the query above:

  • Creates an increment function, with the row id as an argument
  • set views = views + 1 updates/increases the image impressions value called views in the database table by 1

Then, click the “RUN” button to create the function. Later we’ll utilize the function to update the image impressions.

Setting up an Image in Cloudinary

Cloudinary is a cloud-based picture and video management service that includes uploads, storage, manipulations, optimizations, and distribution. It also enables developers to include video players in their apps that properly handle video events.

After successfully creating an account, Cloudinary will redirect us to our account's dashboard page, where we can upload the demo image.


Click on the “Upload” button as shown above and select the image file to be uploaded.

Implementing the Cloudinary Image

In the index.js file, we’ll import the required components from Cloudinary React SDK and integrate the Cloudinary image, as shown below:

3 import React from "react";
4 import { Image, CloudinaryContext } from "cloudinary-react";
6 const IndexPage = () => {
7 return(
8 <CloudinaryContext cloud_name="OUR-CLOUD-NAME">
9 <div className="image-container">
10 <Image
11 publicId="OUR-IMAGE-NAME"
12 width="300px"
13 height="400px"
14 />
15 </div>
16 </CloudinaryContext>
17 )};
19 export default IndexPage;

In the code above, we do the following:

  • Apply the Cloud name from our Cloudinary account details on the dashboard page
  • Include the name of the image from Cloudinary in the Image component
  • Give the image a width of 300px and a height of 400px

Next, we’ll loop through an array of dummy text to make the app look better:

1const IndexPage = () => {
2 return(
3 <main className="container">
4 <title>Track Image impressions in Gatsby.js with Supabase</title>
5 <h1>tracking image impressions in gatsby.js with supabase</h1>
6 {Array.from(Array(5).keys()).map((i) => (
7 <p key={i}>
8 Irure pariatur velit est anim ipsum anim aliquip officia velit
9 consectetur. Duis sint ut consectetur ea anim. Sit proident culpa
10 velit officia do incididunt Lorem in deserunt non adipisicing occaecat
11 magna. Occaecat occaecat esse excepteur consequat occaecat cupidatat
12 aliquip labore esse ad ea. Laboris id excepteur nisi voluptate sunt
13 anim commodo amet reprehenderit.
14 </p>
15 ))}
17 // ... { Cloudinary Image }
19 {Array.from(Array(8).keys()).map((i) => (
20 <p key={i}>
21 Irure pariatur velit est anim ipsum anim aliquip officia velit
22 consectetur. Duis sint ut consectetur ea anim. Sit proident culpa
23 velit officia do incididunt Lorem in deserunt non adipisicing occaecat
24 magna. Occaecat occaecat esse excepteur consequat occaecat cupidatat
25 aliquip labore esse ad ea. Laboris id excepteur nisi voluptate sunt
26 anim commodo amet reprehenderit.
27 </p>
28 ))}
29 </main>
30 );
31 };
32 export default IndexPage;

After configuring the dummy text, the demo application will look like this:


Integrating react-intersection-observer

react-intersection-observer is the React implementation of the Intersection Observer API that indicates when an element enters or leaves the viewport. It contains a hook, renders props, and plain children implementation.

It provides a useInView hook, which makes it easy to monitor the state of our components. We'll use the custom InView component, which will be called whenever the state changes, with a ref that should be assigned to the element root. The InView component uses a default state of true or false, so if the image enters the viewport, its state is true, and if it leaves the viewport, its state is false.

3import React from "react";
4import { Image, CloudinaryContext } from "cloudinary-react";
6const IndexPage = () => {
9//array of dummy text
11<CloudinaryContext cloud_name="OUR-CLOUD-NAME">
12 <InView>
13 {({ ref }) => (
14 <div className="image-container" ref={ref}>
15 <Image
16 publicId="OUR-IMAGE-NAME"
17 width="300px"
18 height="400px"
19 />
20 </div>
21 )}
22 </InView>
23 </CloudinaryContext>
25// array of dummy text
28export default IndexPage;

Updating the Image Impressions

We’ll need to link our Supabase API URL and API Key to the demo application. First off, head to Supabase’s settings, then go to the “API” section, where we can access our Supabase project’s API URL and API Key as shown below:


Then, create a .env file in the app’s root directory to store the API URL and API Key:


Next, we’ll add the API URL and API Key to the index.js file:

2 import { createClient } from "@supabase/supabase-js";
4 const IndexPage = () => {
5 const supabase = createClient(
6 process.env.SUPABASE_URL,
7 process.env.SUPABASE_KEY
8 );
9 return (
10 // Cloudinary Image
11 )};

To update the Supabase database, we’ll create the function, endFunction, which is triggered anytime the state of the InView component changes. The function uses a Supabase Remote Procedure Call (RPC) to increase the number of impressions at Supabase’s database by 1:

2 import React, { useState } from "react";
3 import { Image, CloudinaryContext } from "cloudinary-react";
5 const IndexPage = () => {
6 const supabase = createClient(
7 process.env.SUPABASE_URL,
8 process.env.SUPABASE_KEY
9 );
10 const endFunction = async (inView) => {
11 if (inView) {
12 const { data, error } = await supabase.rpc("increment", { row_id: 1 });
13 }
14 };
15 return (
16 <CloudinaryContext cloud_name="OUR-CLOUD-NAME">
17 <InView onChange={(inView) => endFunction(inView)}>
19 // .....
21 </InView>
22 </CloudinaryContext>
23 )};

After testing the demo application, it should look like this:



This article discussed what Supabase is, the advantages of implementing it in applications, and, more importantly, how to integrate Supabase into web applications.


Emmanuel Ugwu

Software Engineering | Technical Writing | Web Development

I am a software engineer who is passionate about REST API's and building fast and scalable applications. I also write articles and other technical content to guide developers in the tech community.