{"id":37636,"date":"2025-05-20T07:00:00","date_gmt":"2025-05-20T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=37636"},"modified":"2025-05-14T15:17:54","modified_gmt":"2025-05-14T22:17:54","slug":"shoppable-videos-next-js-e-commerce-store","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store","title":{"rendered":"How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p><a href=\"https:\/\/github.com\/musebe\/Shoppable-Video-Demo-Next.js-Cloudinary\"><strong>GitHub Repository<\/strong><\/a> | <a href=\"https:\/\/cloudinary-shoppable-video-next-js-cloudinary.vercel.app\/\"><strong>Live Demo<\/strong><\/a><\/p>\n<p>Product videos don\u2019t just entertain, they convert. From <a href=\"https:\/\/cloudinary.com\/solutions\/video\/survey\">Cloudinary\u2019s Video Survey<\/a>, 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\u2019re 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.<\/p>\n<p>In this tutorial, we\u2019ll use <a href=\"https:\/\/cloudinary.com\/documentation\/video_player_shoppable_videos\"><strong>Cloudinary\u2019s Shoppable Video Player<\/strong><\/a> and <strong>Next.js<\/strong> to transform a simple product walkthrough into an interactive buying experience. You\u2019ll learn how to:<\/p>\n<ol>\n<li>\n<strong>Embed<\/strong> a Cloudinary-hosted MP4 into your Next.js app.<\/li>\n<li>\n<strong>Overlay<\/strong> tappable product hotspots at precise timestamps.<\/li>\n<li>\n<strong>Sync<\/strong> your app\u2019s UI to the video\u2019s playhead for a seamless \u201cshop-as-you-watch\u201d flow.<\/li>\n<\/ol>\n<p>By the end, you\u2019ll have a polished demo (see it live <a href=\"https:\/\/cloudinary-shoppable-video-next-js-cloudinary.vercel.app\/\">here<\/a>) and a GitHub repo you can fork (\u2197\ufe0e <a href=\"https:\/\/github.com\/musebe\/Shoppable-Video-Demo-Next.js-Cloudinary\">Shoppable-Video-Demo-Next.js-Cloudinary<\/a>). Whether you\u2019re building the next big fashion storefront or just exploring interactive video.<\/p>\n<h2>Project Setup<\/h2>\n<p>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.<\/p>\n<h3>Create a Next.js App<\/h3>\n<p>Start by scaffolding a new <strong>Next.js 15<\/strong> project.<\/p>\n<p>We\u2019ll name it <code>Shoppable-Video-Demo-Next.js-Cloudinary<\/code>:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npx<\/span>  <span class=\"hljs-selector-tag\">create-next-app<\/span><span class=\"hljs-keyword\">@latest<\/span>  Shoppable-Video-Demo-Next.js-Cloudinary  \\\n\n--ts --app --tailwind\n\ncd  Shoppable-Video-Demo-Next.js-Cloudinary\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<ul>\n<li>\n<p><strong>TypeScript<\/strong> gives us a safer dev experience.<\/p>\n<\/li>\n<li>\n<p><strong>Tailwind CSS<\/strong> is already baked in for rapid UI building.<\/p>\n<\/li>\n<li>\n<p><strong>App Router<\/strong> (the new <code>app\/<\/code> directory) keeps everything organized.<\/p>\n<\/li>\n<\/ul>\n<h3>Install Needed Libraries<\/h3>\n<p>Next.js already set up Tailwind for us. Now, let\u2019s add just the essentials:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npm<\/span>  <span class=\"hljs-selector-tag\">install<\/span>  <span class=\"hljs-selector-tag\">cloudinary-core<\/span>  <span class=\"hljs-selector-tag\">cloudinary-video-player<\/span>  <span class=\"hljs-keyword\">@shadcn<\/span>\/ui  motion\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<ul>\n<li>\n<p><strong>cloudinary-core<\/strong> + <strong>cloudinary-video-player<\/strong> for integrating Cloudinary\u2019s shoppable player.<\/p>\n<\/li>\n<li>\n<p><strong>@shadcn\/ui<\/strong> for clean, accessible UI components.<\/p>\n<\/li>\n<li>\n<p><strong>motion<\/strong> for buttery-smooth animations (thanks to Motion.dev).<\/p>\n<\/li>\n<\/ul>\n<h3>Set Up Your Cloudinary Account<\/h3>\n<p>If you don\u2019t already have a Cloudinary account, <a href=\"https:\/\/cloudinary.com\/users\/register_free\">sign up here for free<\/a>.<\/p>\n<p>Once you\u2019re in:<\/p>\n<ol>\n<li>\n<p>Go to your <strong>Dashboard<\/strong>.<\/p>\n<\/li>\n<li>\n<p>Under the <strong>Settings \u2192 API Keys<\/strong> tab, copy your:<\/p>\n<\/li>\n<\/ol>\n<ul>\n<li>\n<p><strong>Cloud Name<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>API Key<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>API Secret<\/strong><\/p>\n<\/li>\n<\/ul>\n<p>We\u2019ll need these to securely upload and stream videos.<\/p>\n<h3>Create a Folder and Upload the Demo Video<\/h3>\n<p>Inside your <strong>Cloudinary Media Library<\/strong>:<\/p>\n<ol>\n<li>\n<p>Create a new folder called <strong><code>shoppable-video<\/code><\/strong> (this keeps assets clean and organized).<\/p>\n<\/li>\n<li>\n<p>Download the demo video: <a href=\"https:\/\/res.cloudinary.com\/hackit-africa\/video\/upload\/v1745525294\/shoppable-video\/shoppable_demo.mp4\">https:\/\/res.cloudinary.com\/hackit-africa\/video\/upload\/v1745525294\/shoppable-video\/shoppable_demo.mp4<\/a><\/p>\n<\/li>\n<li>\n<p>Upload <code>shoppable_demo.mp4<\/code> into your <code>shoppable-video<\/code> folder.<\/p>\n<\/li>\n<\/ol>\n<ul>\n<li>Make sure it\u2019s uploaded as a <strong>video<\/strong> (<code>resource_type: video<\/code>) not as an image.<\/li>\n<\/ul>\n<blockquote>\n<p>We\u2019ll reference this video in our player setup shortly.<\/p>\n<\/blockquote>\n<h3>Configure Environment Variables<\/h3>\n<p>Create a <code>.env.local<\/code> file at the root of your project and add:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name\n\nNEXT_PUBLIC_CLOUDINARY_FOLDER=shoppable-video\n\nCLOUDINARY_API_KEY=your-api-key\n\nCLOUDINARY_API_SECRET=your-api-secret\n<\/code><\/span><\/pre>\n<ul>\n<li>\n<p><code>NEXT_PUBLIC_<\/code> keys will be available on the client side.<\/p>\n<\/li>\n<li>\n<p>Server-only keys (API Key + Secret) stay hidden.<\/p>\n<\/li>\n<\/ul>\n<p>Next.js will automatically inject these at runtime.<\/p>\n<h2>Enable Cloudinary Assets in Next.js<\/h2>\n<p>Since our images and videos live on Cloudinary, we need to tell Next.js to allow them.<\/p>\n<p>Open <code>next.config.ts<\/code> and add:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> type { NextConfig } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'next'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> nextConfig: NextConfig = {\n  <span class=\"hljs-attr\">images<\/span>: {\n    <span class=\"hljs-attr\">remotePatterns<\/span>: &#91;\n      {\n        <span class=\"hljs-attr\">protocol<\/span>: <span class=\"hljs-string\">'https'<\/span>,\n        <span class=\"hljs-attr\">hostname<\/span>: <span class=\"hljs-string\">'res.cloudinary.com'<\/span>,\n        <span class=\"hljs-attr\">pathname<\/span>: <span class=\"hljs-string\">`\/<span class=\"hljs-subst\">${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}<\/span>\/**`<\/span>,\n      },\n    ],\n    <span class=\"hljs-attr\">formats<\/span>: &#91;<span class=\"hljs-string\">'image\/avif'<\/span>, <span class=\"hljs-string\">'image\/webp'<\/span>],\n  },\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> nextConfig;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This allows Next.js Image components to safely load optimized Cloudinary URLs.<\/p>\n<p>In the next step, we\u2019ll <strong>create the player utility<\/strong> that actually loads the shoppable experience into your store.<\/p>\n<h1>Create the Cloudinary Shoppable Player Utility<\/h1>\n<p>Now that our app is connected to Cloudinary, it\u2019s time to build a simple utility that controls the <strong>Shoppable Video Player<\/strong> inside <strong>Next.js<\/strong>.<\/p>\n<p>We\u2019ll create two small functions:<\/p>\n<ul>\n<li>\n<p><strong><code>createShoppablePlayer(container, options)<\/code><\/strong>. Initialize the player on a video element.<\/p>\n<\/li>\n<li>\n<p><strong><code>getShoppablePlayer()<\/code><\/strong>. Retrieve the active player later when needed.<\/p>\n<\/li>\n<\/ul>\n<p>This will give us full control over video playback, syncing, and events from anywhere in the app.<\/p>\n<h3>Create the Utility File<\/h3>\n<p>Inside your project, create a new folder (if it doesn\u2019t already exist):<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">mkdir  -p  src\/lib\n<\/code><\/span><\/pre>\n<p>Then create a new file for the player functions:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">touch  src\/lib\/cloudinary-player.ts\n<\/code><\/span><\/pre>\n<p>This will keep our video logic separate and easy to manage.<\/p>\n<h3>Add the Player Functions<\/h3>\n<p>Inside <code>src\/lib\/cloudinary-player.ts<\/code>, add:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">let<\/span> playerInstance: any = <span class=\"hljs-literal\">null<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">createShoppablePlayer<\/span>(<span class=\"hljs-params\">container: HTMLVideoElement | string, options: any<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">typeof<\/span> <span class=\"hljs-built_in\">window<\/span> === <span class=\"hljs-string\">'undefined'<\/span>) <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">'Browser only'<\/span>);\n  <span class=\"hljs-comment\">\/\/ @ts-ignore<\/span>\n  <span class=\"hljs-keyword\">const<\/span> player = <span class=\"hljs-built_in\">window<\/span>.cloudinary.videoPlayer(container, options);\n  playerInstance = player;\n  <span class=\"hljs-keyword\">return<\/span> player;\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getShoppablePlayer<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> playerInstance;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<ul>\n<li>\n<p><code>createShoppablePlayer<\/code> spins up a Cloudinary player on the given container.<\/p>\n<\/li>\n<li>\n<p><code>getShoppablePlayer<\/code> gives us a way to interact with the active video player later.<\/p>\n<\/li>\n<\/ul>\n<blockquote>\n<p>Full version with better typings and comments is available on <a href=\"https:\/\/github.com\/musebe\/Shoppable-Video-Demo-Next.js-Cloudinary\/blob\/main\/src\/lib\/cloudinary-player.ts\">GitHub<\/a>.<\/p>\n<\/blockquote>\n<p>In the next step, we\u2019ll <strong>build the video component<\/strong> that uses this utility to actually render the shoppable experience on the page.<\/p>\n<h2>Build the Shoppable Video Player Component<\/h2>\n<p>Now that we can create a Cloudinary Video Player, it\u2019s time to load it inside a real React component.<\/p>\n<p>At its core, the <strong>ShoppableVideoPlayer<\/strong> does three simple things:<\/p>\n<ul>\n<li>\n<p>It <strong>creates<\/strong> the player on a video element.<\/p>\n<\/li>\n<li>\n<p>It <strong>starts playing<\/strong> a shoppable video automatically.<\/p>\n<\/li>\n<li>\n<p>It <strong>listens<\/strong> to time updates so we can sync other UI elements later.<\/p>\n<\/li>\n<\/ul>\n<h2>How the Player Logic Works<\/h2>\n<p>Inside the component:<\/p>\n<ol>\n<li>\n<p>We\u2019ll grab a reference to the <code>&lt;video&gt;<\/code> tag with <code>useRef<\/code>.<\/p>\n<\/li>\n<li>\n<p>On mount (<code>useEffect<\/code>), we create the Cloudinary player using our helper:<\/p>\n<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> player = <span class=\"hljs-keyword\">await<\/span> createShoppablePlayer(videoEl, {\n  <span class=\"hljs-attr\">cloud_name<\/span>: cloudName,\n  <span class=\"hljs-attr\">fluid<\/span>: <span class=\"hljs-literal\">true<\/span>,\n  <span class=\"hljs-attr\">autoplay<\/span>: <span class=\"hljs-literal\">true<\/span>,\n  <span class=\"hljs-attr\">muted<\/span>: <span class=\"hljs-literal\">true<\/span>,\n  <span class=\"hljs-attr\">controls<\/span>: <span class=\"hljs-literal\">true<\/span>,\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<ol start=\"3\">\n<li>We\u2019ll tell the player which video to load:<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">player.source(<span class=\"hljs-string\">'shoppable-video\/shoppable_demo'<\/span>, {\n  <span class=\"hljs-attr\">shoppable<\/span>: { <span class=\"hljs-attr\">startState<\/span>: <span class=\"hljs-string\">'closed'<\/span>, <span class=\"hljs-attr\">autoClose<\/span>: <span class=\"hljs-number\">0<\/span> },\n  <span class=\"hljs-attr\">sourceTypes<\/span>: &#91;<span class=\"hljs-string\">'mp4'<\/span>],\n  <span class=\"hljs-attr\">resourceType<\/span>: <span class=\"hljs-string\">'video'<\/span>,\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<ol start=\"4\">\n<li>We\u2019ll hook into the <code>timeupdate<\/code> event:<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">player.on(<span class=\"hljs-string\">'timeupdate'<\/span>, () =&gt; {\n  onTimeUpdate(player.currentTime());\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This lets the parent component know where the user is in the video \u2014 perfect for syncing products or effects.<\/p>\n<blockquote>\n<p>Want the full ShoppableVideoPlayer component with all animations and cleanup?<\/p>\n<\/blockquote>\n<blockquote>\n<p>You can view it <a href=\"https:\/\/github.com\/musebe\/Shoppable-Video-Demo-Next.js-Cloudinary\/blob\/main\/src\/components\/ShoppableVideoPlayer.tsx\">here on GitHub<\/a>.<\/p>\n<\/blockquote>\n<p>With the video player now working, next we\u2019ll <strong>combine it with a dynamic product side panel<\/strong> to build a complete shoppable section.<\/p>\n<h2>Assemble the Shoppable Section<\/h2>\n<p>Now that we have a working video player, let\u2019s build the full <strong>shoppable experience<\/strong> by combining:<\/p>\n<ul>\n<li>\n<p>The <strong>video player<\/strong><\/p>\n<\/li>\n<li>\n<p>A <strong>dynamic product side panel<\/strong><\/p>\n<\/li>\n<li>\n<p>Basic <strong>replay\/reset<\/strong> functionality<\/p>\n<\/li>\n<\/ul>\n<p>This creates a smooth \u201cwatch and shop\u201d flow where products appear in sync with the video.<\/p>\n<h3>How the Shoppable Section Works<\/h3>\n<p>Inside the <code>ShoppableSection<\/code> component:<\/p>\n<ol>\n<li>We\u2019ll track the <strong>current playback time<\/strong> using <code>useState<\/code>:<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span>  &#91;currentTime,  setCurrentTime]  =  useState(<span class=\"hljs-number\">0<\/span>);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This lets us know exactly where the viewer is in the video at any moment.<\/p>\n<ol start=\"2\">\n<li>Then we\u2019ll handle <strong>replay logic<\/strong> by watching for clicks on the video\u2019s replay button:<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> observer = <span class=\"hljs-keyword\">new<\/span> MutationObserver(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> btn = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'.cld-replay-button'<\/span>);\n  <span class=\"hljs-keyword\">if<\/span> (btn) {\n    btn.addEventListener(<span class=\"hljs-string\">'click'<\/span>, handleReplay);\n    observer.disconnect();\n  }\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>When the video is replayed, we reset everything back to the start.<\/p>\n<ol start=\"3\">\n<li>We\u2019ll render the <strong>ShoppableVideoPlayer<\/strong> and <strong>ProductSidePanel<\/strong> side by side:<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col lg:flex-row gap-6\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ShoppableVideoPlayer<\/span> <span class=\"hljs-attr\">onTimeUpdate<\/span>=<span class=\"hljs-string\">{setCurrentTime}<\/span> \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ProductSidePanel<\/span>\n    <span class=\"hljs-attr\">products<\/span>=<span class=\"hljs-string\">{SHOPPABLE_CONFIG.shoppable.products}<\/span>\n    <span class=\"hljs-attr\">currentTime<\/span>=<span class=\"hljs-string\">{currentTime}<\/span>\n    <span class=\"hljs-attr\">resetSignal<\/span>=<span class=\"hljs-string\">{resetSignal}<\/span>\n  \/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<ul>\n<li>\n<p>The video updates the <code>currentTime<\/code>.<\/p>\n<\/li>\n<li>\n<p>The side panel reacts by showing the right products at the right moments.<\/p>\n<\/li>\n<\/ul>\n<blockquote>\n<p>The full ShoppableSection component with animations and replay handling is available <a href=\"https:\/\/github.com\/musebe\/Shoppable-Video-Demo-Next.js-Cloudinary\/blob\/main\/src\/components\/ShoppableSection.tsx\">here on GitHub.<\/a><\/p>\n<\/blockquote>\n<p>Now we have a full shoppable experience where the video and product panel <strong>talk to each other<\/strong> seamlessly!<\/p>\n<h2>Implement the Product Side Panel<\/h2>\n<p>The video is playing, but how do we show the right products at the right time?<\/p>\n<p>That\u2019s exactly what the <strong>ProductSidePanel<\/strong> does. It listens to the video\u2019s current time and shows products when they\u2019re supposed to appear.<\/p>\n<h3>How the Product Side Panel Works<\/h3>\n<p>Inside the <code>ProductSidePanel<\/code> component:<\/p>\n<ol>\n<li>We\u2019ll <strong>track which products have appeared<\/strong> so far:<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">const  &#91;seenIds,  setSeenIds]  =  useState<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">number&#91;]<\/span>&gt;<\/span>(&#91;]);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>As the video plays, we mark products as \u201cseen\u201d once their start time is reached.<\/p>\n<ol start=\"2\">\n<li>We\u2019ll <strong>watch the video time<\/strong> and update the panel when needed:<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> newSeen = products\n    .filter(<span class=\"hljs-function\"><span class=\"hljs-params\">p<\/span> =&gt;<\/span> currentTime &gt;= p.startTime + BUFFER &amp;&amp; !seenIds.includes(p.productId))\n    .map(<span class=\"hljs-function\"><span class=\"hljs-params\">p<\/span> =&gt;<\/span> p.productId);\n\n  <span class=\"hljs-keyword\">if<\/span> (newSeen.length &gt; <span class=\"hljs-number\">0<\/span>) {\n    setSeenIds(<span class=\"hljs-function\"><span class=\"hljs-params\">prev<\/span> =&gt;<\/span> &#91;...prev, ...newSeen]);\n  }\n}, &#91;currentTime]);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This makes products smoothly appear one after another, synced to the video timeline.<\/p>\n<ol start=\"3\">\n<li>Next, we\u2019ll <strong>highlight the currently active product<\/strong> (the one currently showing on screen):<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> active = products.find(\n  <span class=\"hljs-function\"><span class=\"hljs-params\">p<\/span> =&gt;<\/span> currentTime &gt;= p.startTime &amp;&amp; currentTime &lt;= p.endTime\n);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>When a product is active, we\u2019ll animate or highlight its card in the side panel.<\/p>\n<ol start=\"4\">\n<li>We\u2019ll <strong>allow users to seek<\/strong> by clicking product thumbnails:<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> handleSeek = <span class=\"hljs-function\">(<span class=\"hljs-params\">t: number<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> player = getShoppablePlayer();\n  <span class=\"hljs-keyword\">if<\/span> (player) {\n    player.pause();\n    player.currentTime(t);\n  }\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Clicking a product instantly jumps the video to that moment.<\/p>\n<blockquote>\n<p>Full ProductSidePanel component (with scroll-to-active logic and styling) is available <a href=\"https:\/\/github.com\/musebe\/Shoppable-Video-Demo-Next.js-Cloudinary\/blob\/main\/src\/components\/ProductSidePanel.tsx\">here on GitHub.<\/a><\/p>\n<\/blockquote>\n<p>Now the video and the product list <strong>stay perfectly in sync<\/strong>.<\/p>\n<p>The user can:<\/p>\n<ul>\n<li>\n<p>Watch the video normally and see products appear.<\/p>\n<\/li>\n<li>\n<p>Click any product to instantly jump to that part of the video.<\/p>\n<\/li>\n<li>\n<p>Replay the whole experience from the start.<\/p>\n<\/li>\n<\/ul>\n<h2>Put It All Together on the Main Page<\/h2>\n<p>Now that the <strong>Shoppable Video Player<\/strong> and <strong>Product Side Panel<\/strong> are ready, we\u2019ll render them directly on the homepage.<\/p>\n<h3>Update Your Main Page<\/h3>\n<p>In <code>src\/app\/page.tsx<\/code>, add:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-string\">'use client'<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { ShoppableVideoPlayer } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/components\/ShoppableVideoPlayer'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { ProductSidePanel } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/components\/ProductSidePanel'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { SHOPPABLE_CONFIG } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/lib\/shoppable-config'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;currentTime, setCurrentTime] = useState(<span class=\"hljs-number\">0<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;resetSignal, setResetSignal] = useState(<span class=\"hljs-number\">0<\/span>);\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col items-center space-y-8 pt-12 pb-16\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col lg:flex-row gap-6 max-w-6xl w-full px-4 sm:px-6 md:px-8\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ShoppableVideoPlayer<\/span> <span class=\"hljs-attr\">onTimeUpdate<\/span>=<span class=\"hljs-string\">{setCurrentTime}<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ProductSidePanel<\/span>\n          <span class=\"hljs-attr\">products<\/span>=<span class=\"hljs-string\">{SHOPPABLE_CONFIG.shoppable.products}<\/span>\n          <span class=\"hljs-attr\">currentTime<\/span>=<span class=\"hljs-string\">{currentTime}<\/span>\n          <span class=\"hljs-attr\">resetSignal<\/span>=<span class=\"hljs-string\">{resetSignal}<\/span>\n        \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/span>\n  );\n}\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<ul>\n<li>\n<p><code>ShoppableVideoPlayer<\/code> displays the video and tracks playback time.<\/p>\n<\/li>\n<li>\n<p><code>ProductSidePanel<\/code> shows and syncs products based on the video time.<\/p>\n<\/li>\n<\/ul>\n<blockquote>\n<p>You can view the full working example <a href=\"https:\/\/github.com\/musebe\/Shoppable-Video-Demo-Next.js-Cloudinary\/blob\/main\/src\/app\/page.tsx\">here on GitHub<\/a>.<\/p>\n<\/blockquote>\n<p>The homepage now immediately shows the video with interactive shopping built in.<\/p>\n<h2>Conclusion<\/h2>\n<p>In just a few steps, we turned a simple video into a fully interactive <strong>shoppable experience<\/strong> inside a <strong>Next.js 15<\/strong> app, powered by <strong>Cloudinary<\/strong>.<\/p>\n<p>Here\u2019s a quick recap of what we built:<\/p>\n<ul>\n<li>\n<p>A <strong>Cloudinary Shoppable Player Utility<\/strong> to manage video instances cleanly.<\/p>\n<\/li>\n<li>\n<p>A <strong>Shoppable Video Player<\/strong> component that streams and tracks the video.<\/p>\n<\/li>\n<li>\n<p>A <strong>Product Side Panel<\/strong> that highlights products at exactly the right moment.<\/p>\n<\/li>\n<li>\n<p>A clean <strong>homepage setup<\/strong> that combines everything into one seamless flow.<\/p>\n<\/li>\n<\/ul>\n<p>By uploading just one video and using Cloudinary\u2019s powerful player, we can now:<\/p>\n<ul>\n<li>Showcase products inside a live video.<\/li>\n<li>Let users click products to jump directly to that scene.<\/li>\n<li>Sync the video timeline with the UI automatically.<\/li>\n<\/ul>\n<blockquote>\n<p>Full source code available here: <a href=\"https:\/\/github.com\/musebe\/Shoppable-Video-Demo-Next.js-Cloudinary\">Shoppable-Video-Demo-Next.js-Cloudinary (GitHub)<\/a><\/p>\n<\/blockquote>\n<blockquote>\n<p>Live demo: <a href=\"https:\/\/cloudinary-shoppable-video-next-js-cloudinary.vercel.app\/\">View it live<\/a><\/p>\n<\/blockquote>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":37637,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[98,212,270,303],"class_list":["post-37636","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-e-commerce","tag-next-js","tag-shoppable-video","tag-video"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-05-20T14:00:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746057356\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary\",\"datePublished\":\"2025-05-20T14:00:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store\"},\"wordCount\":13,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746057356\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary.jpg?_i=AA\",\"keywords\":[\"E-commerce\",\"Next.js\",\"Shoppable Video\",\"Video\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store\",\"url\":\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store\",\"name\":\"How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746057356\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary.jpg?_i=AA\",\"datePublished\":\"2025-05-20T14:00:00+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746057356\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746057356\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store","og_locale":"en_US","og_type":"article","og_title":"How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary","og_url":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store","og_site_name":"Cloudinary Blog","article_published_time":"2025-05-20T14:00:00+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746057356\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary","datePublished":"2025-05-20T14:00:00+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store"},"wordCount":13,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746057356\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary.jpg?_i=AA","keywords":["E-commerce","Next.js","Shoppable Video","Video"],"inLanguage":"en-US","copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store","url":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store","name":"How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746057356\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary.jpg?_i=AA","datePublished":"2025-05-20T14:00:00+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746057356\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746057356\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/shoppable-videos-next-js-e-commerce-store#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How to Add Shoppable Videos to Your Next.js E-commerce Store With Cloudinary"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746057356\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary\/Blog_How_to_Add_Shoppable_Videos_to_Your_Next.js_E-commerce_Store_with_Cloudinary.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37636","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=37636"}],"version-history":[{"count":4,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37636\/revisions"}],"predecessor-version":[{"id":37644,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37636\/revisions\/37644"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/37637"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=37636"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=37636"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=37636"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}