Skip to content

RESOURCES / BLOG

How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary

Why It Matters

  • Use Cloudinary’s Shoppable Video Player and Next.js to embed interactive hotspots on product videos, allowing users to directly purchase items within the video.
  • Set up a Next.js 15 e-commerce store and integrate the Cloudinary Shoppable Video Player using provided libraries and utilities.
  • Overlay interactive product hotspots at specific timestamps and synchronize your app’s UI with the video’s playhead for a smooth shopping experience.
  • Access a live demo and GitHub repo with the full source code to implement shoppable video functionality in your own e-commerce store.

GitHub Repository | Live Demo

Product videos don’t just entertain, they convert. From Cloudinary’s Video Survey, in which we surveyed developers, marketers, and other business leaders, 54% view video as key for driving purchase and conversions. Interactive shoppable videos, specifically, are changing the way customers buy goods online. They enable customers to click directly on products they’re interested in, jump straight to a product page, and complete a purchase without ever leaving the video player. This eliminates time-consuming steps such as searching through multiple websites or scrolling through tons of product lists.

In this tutorial, we’ll use Cloudinary’s Shoppable Video Player and Next.js to transform a simple product walkthrough into an interactive buying experience. You’ll learn how to:

  1. Embed a Cloudinary-hosted MP4 into your Next.js app.
  2. Overlay tappable product hotspots at precise timestamps.
  3. Sync your app’s UI to the video’s playhead for a seamless “shop-as-you-watch” flow.

By the end, you’ll have a polished demo (see it live here) and a GitHub repo you can fork (↗︎ Shoppable-Video-Demo-Next.js-Cloudinary). Whether you’re building the next big fashion storefront or just exploring interactive video.

Before we can bring products to life inside a video, we need a solid foundation: a Next.js app, a Cloudinary account, and the right libraries.

Start by scaffolding a new Next.js 15 project.

We’ll name it Shoppable-Video-Demo-Next.js-Cloudinary:

npx  create-next-app@latest  Shoppable-Video-Demo-Next.js-Cloudinary  \

--ts --app --tailwind

cd  Shoppable-Video-Demo-Next.js-Cloudinary
Code language: CSS (css)
  • TypeScript gives us a safer dev experience.

  • Tailwind CSS is already baked in for rapid UI building.

  • App Router (the new app/ directory) keeps everything organized.

Next.js already set up Tailwind for us. Now, let’s add just the essentials:

npm  install  cloudinary-core  cloudinary-video-player  @shadcn/ui  motion
Code language: CSS (css)
  • cloudinary-core + cloudinary-video-player for integrating Cloudinary’s shoppable player.

  • @shadcn/ui for clean, accessible UI components.

  • motion for buttery-smooth animations (thanks to Motion.dev).

If you don’t already have a Cloudinary account, sign up here for free.

Once you’re in:

  1. Go to your Dashboard.

  2. Under the Settings → API Keys tab, copy your:

  • Cloud Name

  • API Key

  • API Secret

We’ll need these to securely upload and stream videos.

Inside your Cloudinary Media Library:

  1. Create a new folder called shoppable-video (this keeps assets clean and organized).

  2. Download the demo video: https://res.cloudinary.com/hackit-africa/video/upload/v1745525294/shoppable-video/shoppable_demo.mp4

  3. Upload shoppable_demo.mp4 into your shoppable-video folder.

  • Make sure it’s uploaded as a video (resource_type: video) not as an image.

We’ll reference this video in our player setup shortly.

Create a .env.local file at the root of your project and add:

NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name

NEXT_PUBLIC_CLOUDINARY_FOLDER=shoppable-video

CLOUDINARY_API_KEY=your-api-key

CLOUDINARY_API_SECRET=your-api-secret
  • NEXT_PUBLIC_ keys will be available on the client side.

  • Server-only keys (API Key + Secret) stay hidden.

Next.js will automatically inject these at runtime.

Since our images and videos live on Cloudinary, we need to tell Next.js to allow them.

Open next.config.ts and add:

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'res.cloudinary.com',
        pathname: `/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/**`,
      },
    ],
    formats: ['image/avif', 'image/webp'],
  },
};

export default nextConfig;
Code language: JavaScript (javascript)

This allows Next.js Image components to safely load optimized Cloudinary URLs.

In the next step, we’ll create the player utility that actually loads the shoppable experience into your store.

Now that our app is connected to Cloudinary, it’s time to build a simple utility that controls the Shoppable Video Player inside Next.js.

We’ll create two small functions:

  • createShoppablePlayer(container, options). Initialize the player on a video element.

  • getShoppablePlayer(). Retrieve the active player later when needed.

This will give us full control over video playback, syncing, and events from anywhere in the app.

Inside your project, create a new folder (if it doesn’t already exist):

mkdir  -p  src/lib

Then create a new file for the player functions:

touch  src/lib/cloudinary-player.ts

This will keep our video logic separate and easy to manage.

Inside src/lib/cloudinary-player.ts, add:

let playerInstance: any = null;

export async function createShoppablePlayer(container: HTMLVideoElement | string, options: any) {
  if (typeof window === 'undefined') throw new Error('Browser only');
  // @ts-ignore
  const player = window.cloudinary.videoPlayer(container, options);
  playerInstance = player;
  return player;
}

export function getShoppablePlayer() {
  return playerInstance;
}
Code language: JavaScript (javascript)
  • createShoppablePlayer spins up a Cloudinary player on the given container.

  • getShoppablePlayer gives us a way to interact with the active video player later.

Full version with better typings and comments is available on GitHub.

In the next step, we’ll build the video component that uses this utility to actually render the shoppable experience on the page.

Now that we can create a Cloudinary Video Player, it’s time to load it inside a real React component.

At its core, the ShoppableVideoPlayer does three simple things:

  • It creates the player on a video element.

  • It starts playing a shoppable video automatically.

  • It listens to time updates so we can sync other UI elements later.

Inside the component:

  1. We’ll grab a reference to the <video> tag with useRef.

  2. On mount (useEffect), we create the Cloudinary player using our helper:

const player = await createShoppablePlayer(videoEl, {
  cloud_name: cloudName,
  fluid: true,
  autoplay: true,
  muted: true,
  controls: true,
});
Code language: JavaScript (javascript)
  1. We’ll tell the player which video to load:
player.source('shoppable-video/shoppable_demo', {
  shoppable: { startState: 'closed', autoClose: 0 },
  sourceTypes: ['mp4'],
  resourceType: 'video',
});
Code language: JavaScript (javascript)
  1. We’ll hook into the timeupdate event:
player.on('timeupdate', () => {
  onTimeUpdate(player.currentTime());
});
Code language: PHP (php)

This lets the parent component know where the user is in the video — perfect for syncing products or effects.

Want the full ShoppableVideoPlayer component with all animations and cleanup?

You can view it here on GitHub.

With the video player now working, next we’ll combine it with a dynamic product side panel to build a complete shoppable section.

Now that we have a working video player, let’s build the full shoppable experience by combining:

  • The video player

  • A dynamic product side panel

  • Basic replay/reset functionality

This creates a smooth “watch and shop” flow where products appear in sync with the video.

Inside the ShoppableSection component:

  1. We’ll track the current playback time using useState:
const  [currentTime,  setCurrentTime]  =  useState(0);
Code language: JavaScript (javascript)

This lets us know exactly where the viewer is in the video at any moment.

  1. Then we’ll handle replay logic by watching for clicks on the video’s replay button:
const observer = new MutationObserver(() => {
  const btn = document.querySelector('.cld-replay-button');
  if (btn) {
    btn.addEventListener('click', handleReplay);
    observer.disconnect();
  }
});
Code language: JavaScript (javascript)

When the video is replayed, we reset everything back to the start.

  1. We’ll render the ShoppableVideoPlayer and ProductSidePanel side by side:
<div className="flex flex-col lg:flex-row gap-6">
  <ShoppableVideoPlayer onTimeUpdate={setCurrentTime} />
  <ProductSidePanel
    products={SHOPPABLE_CONFIG.shoppable.products}
    currentTime={currentTime}
    resetSignal={resetSignal}
  />
</div>
Code language: HTML, XML (xml)
  • The video updates the currentTime.

  • The side panel reacts by showing the right products at the right moments.

The full ShoppableSection component with animations and replay handling is available here on GitHub.

Now we have a full shoppable experience where the video and product panel talk to each other seamlessly!

The video is playing, but how do we show the right products at the right time?

That’s exactly what the ProductSidePanel does. It listens to the video’s current time and shows products when they’re supposed to appear.

Inside the ProductSidePanel component:

  1. We’ll track which products have appeared so far:
const  [seenIds,  setSeenIds]  =  useState<number[]>([]);
Code language: HTML, XML (xml)

As the video plays, we mark products as “seen” once their start time is reached.

  1. We’ll watch the video time and update the panel when needed:
useEffect(() => {
  const newSeen = products
    .filter(p => currentTime >= p.startTime + BUFFER && !seenIds.includes(p.productId))
    .map(p => p.productId);

  if (newSeen.length > 0) {
    setSeenIds(prev => [...prev, ...newSeen]);
  }
}, [currentTime]);
Code language: JavaScript (javascript)

This makes products smoothly appear one after another, synced to the video timeline.

  1. Next, we’ll highlight the currently active product (the one currently showing on screen):
const active = products.find(
  p => currentTime >= p.startTime && currentTime <= p.endTime
);
Code language: JavaScript (javascript)

When a product is active, we’ll animate or highlight its card in the side panel.

  1. We’ll allow users to seek by clicking product thumbnails:
const handleSeek = (t: number) => {
  const player = getShoppablePlayer();
  if (player) {
    player.pause();
    player.currentTime(t);
  }
};
Code language: JavaScript (javascript)

Clicking a product instantly jumps the video to that moment.

Full ProductSidePanel component (with scroll-to-active logic and styling) is available here on GitHub.

Now the video and the product list stay perfectly in sync.

The user can:

  • Watch the video normally and see products appear.

  • Click any product to instantly jump to that part of the video.

  • Replay the whole experience from the start.

Now that the Shoppable Video Player and Product Side Panel are ready, we’ll render them directly on the homepage.

In src/app/page.tsx, add:

'use client';

import { useState } from 'react';
import { ShoppableVideoPlayer } from '@/components/ShoppableVideoPlayer';
import { ProductSidePanel } from '@/components/ProductSidePanel';
import { SHOPPABLE_CONFIG } from '@/lib/shoppable-config';

export default function Home() {
  const [currentTime, setCurrentTime] = useState(0);
  const [resetSignal, setResetSignal] = useState(0);

  return (
    <main className="flex flex-col items-center space-y-8 pt-12 pb-16">
      <div className="flex flex-col lg:flex-row gap-6 max-w-6xl w-full px-4 sm:px-6 md:px-8">
        <ShoppableVideoPlayer onTimeUpdate={setCurrentTime} />
        <ProductSidePanel
          products={SHOPPABLE_CONFIG.shoppable.products}
          currentTime={currentTime}
          resetSignal={resetSignal}
        />
      </div>
    </main>
  );
}

Code language: JavaScript (javascript)
  • ShoppableVideoPlayer displays the video and tracks playback time.

  • ProductSidePanel shows and syncs products based on the video time.

You can view the full working example here on GitHub.

The homepage now immediately shows the video with interactive shopping built in.

In just a few steps, we turned a simple video into a fully interactive shoppable experience inside a Next.js 15 app, powered by Cloudinary.

Here’s a quick recap of what we built:

  • A Cloudinary Shoppable Player Utility to manage video instances cleanly.

  • A Shoppable Video Player component that streams and tracks the video.

  • A Product Side Panel that highlights products at exactly the right moment.

  • A clean homepage setup that combines everything into one seamless flow.

By uploading just one video and using Cloudinary’s powerful player, we can now:

  • Showcase products inside a live video.
  • Let users click products to jump directly to that scene.
  • Sync the video timeline with the UI automatically.

Full source code available here: Shoppable-Video-Demo-Next.js-Cloudinary (GitHub)

Live demo: View it live

Start Using Cloudinary

Sign up for our free plan and start creating stunning visual experiences in minutes.

Sign Up for Free