{"id":37803,"date":"2025-06-23T07:00:00","date_gmt":"2025-06-23T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=37803"},"modified":"2025-09-26T14:51:10","modified_gmt":"2025-09-26T21:51:10","slug":"next-js-storefront-interactive-visual-media-ai-tagging","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging","title":{"rendered":"Power Your Next.js Storefront With Interactive Visual Media and Smart AI Tagging Using Cloudinary"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-storefront\"><strong>GitHub Repository<\/strong><\/a> | <a href=\"https:\/\/nextjs-cloudinary-storefront.vercel.app\"><strong>Live Demo<\/strong><\/a><\/p>\n<p>Imagine landing on an e-commerce page where each product instantly springs to life: You can swipe through high-res images, play looped videos, spin 360-degree views, or even inspect a 3D model without ever leaving the page. Now add AI-powered tagging that automatically categorizes every asset, so your search, filtering, and personalization become effortlessly smart.<\/p>\n<p>That\u2019s exactly what Cloudinary\u2019s interactive Product Gallery + AI tagging delivers.<\/p>\n<ul>\n<li>\n<strong>Interactive Gallery.<\/strong> A fully customizable visual media carousel supporting images, video, 360-degree spinsets,  and 3D models.<\/li>\n<li>\n<strong>AI Auto-Tagging.<\/strong> Use Cloudinary\u2019s AWS Rekognition add-on to automatically extract scene categories and save them as tags no manual work required. Read our documentation <a href=\"https:\/\/cloudinary.com\/documentation\/aws_rekognition_auto_tagging_addon\">here<\/a>.<\/li>\n<\/ul>\n<p>In this tutorial, you\u2019ll build a <strong>Next.js 15<\/strong> storefront that:<\/p>\n<ol>\n<li>Boots up a shared Cloudinary URL-Gen client for secure, optimized media URLs.<\/li>\n<li>Wraps the Product Gallery widget in a client-only component for zero hydration errors.<\/li>\n<li>Lazily applies Rekognition auto-tagging on first view and caches tags in Cloudinary.<\/li>\n<li>Exposes an API route and an <code>&lt;AITags \/&gt;<\/code> component to fetch and display those tags as Shadcn-style pills.<\/li>\n<\/ol>\n<h2>Project Setup and Environment Variables<\/h2>\n<p>Let\u2019s get our Next.js storefront scaffolded and wired up for Cloudinary in just a few commands.<\/p>\n<h3>1. Scaffold a New Next.js App<\/h3>\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>  nextjs-cloudinary-storefront\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<p>Choose the options you prefer (App Router, TypeScript, ESLint, Tailwind, import aliases, etc.).<\/p>\n<h3>2. Install Required Libraries<\/h3>\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-keyword\">@cloudinary<\/span>\/url-gen  @cloudinary\/react  cloudinary  motion  shadcn\/ui  lucide-react  sonner\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<strong>@cloudinary\/url-gen and @cloudinary\/react.<\/strong> Generate and render optimized Cloudinary assets.<\/li>\n<li>\n<strong>cloudinary.<\/strong> Node SDK for Admin API calls (auto-tagging, metadata).<\/li>\n<li>\n<strong>motion.<\/strong> Framer-style React animations.<\/li>\n<li>\n<strong>shadcn\/ui.<\/strong> Accessible UI components.<\/li>\n<li>\n<strong>lucide-react.<\/strong> Lightweight icon set.<\/li>\n<li>\n<strong>sonner.<\/strong> Toast notifications.<\/li>\n<\/ul>\n<h3>3. Configure Environment Variables<\/h3>\n<p>Create a file named <code>.env.local<\/code> in your project root:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name\nNEXT_PUBLIC_CLOUDINARY_FOLDER=cloudinary-storefront \nCLOUDINARY_API_KEY=your_api_key\nCLOUDINARY_API_SECRET=your_api_secret\n<\/code><\/span><\/pre>\n<blockquote>\n<p><strong>Note:<\/strong> Never commit <code>.env.local<\/code>, as it contains your private keys.<\/p>\n<\/blockquote>\n<h3>4. Allow Cloudinary Media in Next.js<\/h3>\n<p>Edit <code>next.config.js<\/code> (or <code>next.config.ts<\/code>) to whitelist your Cloudinary domain:<\/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-comment\">\/** <span class=\"hljs-doctag\">@type <span class=\"hljs-type\">{import('next').NextConfig}<\/span> <\/span>*\/<\/span>\n<span class=\"hljs-keyword\">const<\/span> 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 ensures Next\u2019s <code>&lt;Image \/&gt;<\/code> optimizer can fetch and cache your Cloudinary assets.<\/p>\n<h3>5. Include Cloudinary Scripts in Your Layout<\/h3>\n<p>In <code>src\/app\/layout.tsx<\/code>, add only the essential <code>&lt;head&gt;<\/code> snippets:<\/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-comment\">\/\/ src\/app\/layout.tsx (excerpt)<\/span>\n<span class=\"hljs-keyword\">import<\/span> Script <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/script\"<\/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\">RootLayout<\/span>(<span class=\"hljs-params\">{ children }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">html<\/span> <span class=\"hljs-attr\">lang<\/span>=<span class=\"hljs-string\">\"en\"<\/span> <span class=\"hljs-attr\">suppressHydrationWarning<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head<\/span>&gt;<\/span>\n        {\/* Preconnect to Cloudinary CDN *\/}\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span> <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"preconnect\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"https:\/\/res.cloudinary.com\"<\/span> \/&gt;<\/span>\n\n        {\/* Product Gallery bundle *\/}\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span>\n          <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"stylesheet\"<\/span>\n          <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"https:\/\/product-gallery.cloudinary.com\/all.css\"<\/span>\n        \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Script<\/span>\n          <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/product-gallery.cloudinary.com\/all.js\"<\/span>\n          <span class=\"hljs-attr\">strategy<\/span>=<span class=\"hljs-string\">\"beforeInteractive\"<\/span>\n        \/&gt;<\/span><span class=\"handlebars\"><span class=\"xml\">\n\n        {\/* Optional: Upload Widget *\/}\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Script<\/span>\n          <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/upload-widget.cloudinary.com\/global\/all.js\"<\/span>\n          <span class=\"hljs-attr\">strategy<\/span>=<span class=\"hljs-string\">\"lazyOnload\"<\/span>\n        \/&gt;<\/span><span class=\"handlebars\"><span class=\"xml\">\n\n        {\/* 3D <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">model-viewer<\/span>&gt;<\/span> *\/}\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Script<\/span>\n          <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"module\"<\/span>\n          <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/unpkg.com\/@google\/model-viewer\/dist\/model-viewer.min.js\"<\/span>\n          <span class=\"hljs-attr\">strategy<\/span>=<span class=\"hljs-string\">\"beforeInteractive\"<\/span>\n        \/&gt;<\/span><span class=\"handlebars\"><span class=\"xml\">\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">head<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body<\/span>&gt;<\/span>{children}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">html<\/span>&gt;<\/span>\n  );\n}\n<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/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<p><strong>What each does:<\/strong><\/p>\n<ol>\n<li>\n<strong>Preconnect.<\/strong> Speeds up fetching of Cloudinary-hosted assets.<\/li>\n<li>\n<strong>Product Gallery CSS.<\/strong> Styles for the interactive gallery widget.<\/li>\n<li>\n<strong>Product Gallery JS.<\/strong> Core bundle to initialize and render the cloudinary.galleryWidget.<\/li>\n<li>\n<strong>Upload Widget (opt-in).<\/strong> Enables you to add Cloudinary upload UI later without extra bundling.<\/li>\n<li>\n<strong><code>&lt;model-viewer&gt;<\/code>.<\/strong> Browser support for displaying GLB\/3D models out of the box.<\/li>\n<\/ol>\n<p>For the full layout component, see <a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-storefront\/blob\/main\/src\/app\/layout.tsx\">layout.tsx on GitHub<\/a>.<\/p>\n<h2>Cloudinary Helpers: <code>cld<\/code>  and  <code>ensureTags<\/code><\/h2>\n<p>Before we can build a smart product gallery, we\u2019ll need two key helpers:<\/p>\n<ul>\n<li>A <strong>URL generator<\/strong> for rendering optimized, signed Cloudinary assets in the browser.<\/li>\n<li>A <strong>lazy auto-tagger<\/strong> that applies AWS Rekognition the first time we encounter an image.<\/li>\n<\/ul>\n<p>Let\u2019s set them up.<\/p>\n<h3><code>cloudinary.ts<\/code>: Building URLs the Right Way<\/h3>\n<p>Create a shared Cloudinary client to generate image and video URLs from your configured account:<\/p>\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-comment\">\/\/ src\/lib\/cloudinary.ts<\/span>\n<span class=\"hljs-keyword\">import<\/span> { Cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@cloudinary\/url-gen'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> cld = <span class=\"hljs-keyword\">new<\/span> Cloudinary({\n  <span class=\"hljs-attr\">cloud<\/span>: { <span class=\"hljs-attr\">cloudName<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME! },\n  <span class=\"hljs-attr\">url<\/span>:   { <span class=\"hljs-attr\">secure<\/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<p>Now, anywhere in your app, you can call <code>cld.image('publicId')<\/code> or <code>cld.video('publicId')<\/code> and transform it on the fly.<\/p>\n<blockquote>\n<p>You\u2019ll use this client inside display components like CloudinaryRawGallery or to generate thumbnails or transformations.<\/p>\n<\/blockquote>\n<h3><code>ensureTags.ts<\/code>: Auto-tag an Image Only Once<\/h3>\n<p>Now create <code>src\/lib\/ensureTags.ts<\/code>. This file exports a function we\u2019ll call whenever an image appears in the gallery. It checks whether it\u2019s been tagged before. If not, it triggers the AWS Rekognition add-on to extract high-confidence labels and save them as Cloudinary tags.<\/p>\n<p>We\u2019ll start by importing and configuring the Admin SDK:<\/p>\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\"><span class=\"hljs-keyword\">import<\/span> { v2 <span class=\"hljs-keyword\">as<\/span> cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>;\n\ncloudinary.config({\n  <span class=\"hljs-attr\">cloud_name<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,\n  <span class=\"hljs-attr\">api_key<\/span>: process.env.CLOUDINARY_API_KEY,\n  <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_API_SECRET,\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<h3>The Actual Logic<\/h3>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><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\">ensureTags<\/span>(<span class=\"hljs-params\">publicId: string<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> cloudinary.api.resource(publicId, {\n    <span class=\"hljs-attr\">resource_type<\/span>: <span class=\"hljs-string\">\"image\"<\/span>,\n  });\n  <span class=\"hljs-keyword\">const<\/span> tags = res.tags || &#91;];\n  <span class=\"hljs-keyword\">const<\/span> folder = process.env.NEXT_PUBLIC_CLOUDINARY_FOLDER;\n\n  <span class=\"hljs-comment\">\/\/ Only tag if the asset only has the folder tag<\/span>\n  <span class=\"hljs-keyword\">const<\/span> isUntagged = tags.length === <span class=\"hljs-number\">1<\/span> &amp;&amp; tags&#91;<span class=\"hljs-number\">0<\/span>] === folder;\n  <span class=\"hljs-keyword\">if<\/span> (!isUntagged) <span class=\"hljs-keyword\">return<\/span>;\n\n  <span class=\"hljs-comment\">\/\/ Trigger Rekognition with a 70% confidence threshold<\/span>\n  <span class=\"hljs-keyword\">await<\/span> cloudinary.api.update(publicId, {\n    <span class=\"hljs-attr\">categorization<\/span>: <span class=\"hljs-string\">\"aws_rek_tagging\"<\/span>,\n    <span class=\"hljs-attr\">auto_tagging<\/span>: <span class=\"hljs-number\">0.7<\/span>,\n  });\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\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The Bigger Picture<\/h3>\n<ul>\n<li>This function gets called from your API or gallery display logic.<\/li>\n<li>It runs once per asset and avoids redundant tagging calls.<\/li>\n<li>Once Cloudinary saves tags, they\u2019re cached and instantly available in future fetches.<\/li>\n<\/ul>\n<h2>Product Gallery Component<\/h2>\n<p>With our helpers in place, let\u2019s bring your storefront to life with Cloudinary\u2019s Interactive Product Gallery. This widget lets visitors swipe through images, play videos, spin 360\u00b0 views and inspect 3D models all in one sleek carousel.<\/p>\n<h3>Define Your Asset List<\/h3>\n<p>In <code>CloudinaryRawGallery.tsx<\/code>, start by declaring the assets you\u2019ve uploaded to your <code>cloudinary-storefront<\/code> folder:<\/p>\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> mediaAssets = &#91;\n  { <span class=\"hljs-attr\">publicId<\/span>: <span class=\"hljs-string\">\"cloudinary-storefront\/iphone_front\"<\/span>, <span class=\"hljs-attr\">mediaType<\/span>: <span class=\"hljs-string\">\"image\"<\/span> },\n  { <span class=\"hljs-attr\">publicId<\/span>: <span class=\"hljs-string\">\"cloudinary-storefront\/iphone_back\"<\/span>, <span class=\"hljs-attr\">mediaType<\/span>: <span class=\"hljs-string\">\"image\"<\/span> },\n  { <span class=\"hljs-attr\">publicId<\/span>: <span class=\"hljs-string\">\"cloudinary-storefront\/iphone_side\"<\/span>, <span class=\"hljs-attr\">mediaType<\/span>: <span class=\"hljs-string\">\"image\"<\/span> },\n  { <span class=\"hljs-attr\">publicId<\/span>: <span class=\"hljs-string\">\"cloudinary-storefront\/iphone_video\"<\/span>, <span class=\"hljs-attr\">mediaType<\/span>: <span class=\"hljs-string\">\"video\"<\/span> },\n  { <span class=\"hljs-attr\">publicId<\/span>: <span class=\"hljs-string\">\"cloudinary-storefront\/iphone\"<\/span>, <span class=\"hljs-attr\">mediaType<\/span>: <span class=\"hljs-string\">\"3d\"<\/span> },\n];\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>These <code>publicId<\/code> entries must match exactly what you see in your Cloudinary Media Library.<\/p>\n<h3>Initialize the Gallery in the Client<\/h3>\n<p>Wrap the widget initialization in a React effect so it only runs in the browser:<\/p>\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\">useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> widget = <span class=\"hljs-built_in\">window<\/span>.cloudinary.galleryWidget({\n    <span class=\"hljs-attr\">container<\/span>: <span class=\"hljs-string\">\"#my-gallery\"<\/span>,\n    <span class=\"hljs-attr\">cloudName<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,\n    mediaAssets,\n    <span class=\"hljs-attr\">displayProps<\/span>: { <span class=\"hljs-attr\">spacing<\/span>: <span class=\"hljs-number\">15<\/span>, <span class=\"hljs-attr\">mode<\/span>: <span class=\"hljs-string\">\"classic\"<\/span> },\n    <span class=\"hljs-attr\">thumbnailProps<\/span>: {\n      <span class=\"hljs-attr\">width<\/span>: <span class=\"hljs-number\">130<\/span>,\n      <span class=\"hljs-attr\">height<\/span>: <span class=\"hljs-number\">110<\/span>,\n      <span class=\"hljs-attr\">navigationFloat<\/span>: <span class=\"hljs-literal\">true<\/span>,\n      <span class=\"hljs-attr\">spacing<\/span>: <span class=\"hljs-number\">20<\/span>,\n    },\n    <span class=\"hljs-attr\">navigationButtonProps<\/span>: { <span class=\"hljs-attr\">shape<\/span>: <span class=\"hljs-string\">\"round\"<\/span>, <span class=\"hljs-attr\">size<\/span>: <span class=\"hljs-number\">52<\/span> },\n    <span class=\"hljs-attr\">transition<\/span>: <span class=\"hljs-string\">\"fade\"<\/span>,\n    <span class=\"hljs-attr\">zoomProps<\/span>: { <span class=\"hljs-attr\">level<\/span>: <span class=\"hljs-number\">1.6<\/span> },\n    <span class=\"hljs-attr\">secureDistribution<\/span>: <span class=\"hljs-string\">\"res.cloudinary.com\"<\/span>,\n  });\n\n  widget.on(<span class=\"hljs-string\">\"mediachanged\"<\/span>, (ev) =&gt; {\n    <span class=\"hljs-keyword\">if<\/span> (ev.public_id) setActiveId(ev.public_id);\n  });\n\n  widget.render();\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> widget.destroy?.();\n}, &#91;]);\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<blockquote>\n<p>We\u2019re listening for media changes to update our &lt;AITags \/&gt; component with the new publicId.<\/p>\n<\/blockquote>\n<h3>Upload Your Assets in Cloudinary<\/h3>\n<ol>\n<li>\n<p><strong>Create a folder<\/strong> called <code>cloudinary-storefront<\/code> in your Cloudinary dashboard.<\/p>\n<\/li>\n<li>\n<p><strong>Upload<\/strong> at least:<\/p>\n<ul>\n<li>One high-res <strong>image<\/strong>\n<\/li>\n<li>One <strong>video<\/strong> (MP4)<\/li>\n<li>One <strong>3D model<\/strong> (<code>.glb<\/code> or <code>.gltf<\/code>)<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>Name<\/strong> each asset\u2019s Public ID to match the entries in your <code>mediaAssets<\/code> array above.<\/p>\n<\/li>\n<\/ol>\n<p>Once that\u2019s done, your gallery component will automatically display them in the order you defined.<\/p>\n<blockquote>\n<p>See the full gallery wrapper on GitHub: <a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-storefront\/blob\/main\/src\/components\/CloudinaryRawGallery.tsx\">CloudinaryRawGallery.tsx \u203a<\/a><\/p>\n<\/blockquote>\n<h2>AI Tagging Setup<\/h2>\n<p>Before your storefront can display smart, auto-generated tags, you\u2019ll need to enable and configure Cloudinary\u2019s AWS Rekognition add-on, then apply it to your existing assets.<\/p>\n<h3>1. Enable the AWS Rekognition Add-on<\/h3>\n<ol>\n<li>\n<strong>Log in<\/strong> to your Cloudinary account and open the <strong>Media Library<\/strong>.<\/li>\n<li>In the left sidebar, click <strong>App Marketplace<\/strong> &gt; <strong>Add-on Marketplace<\/strong>.<\/li>\n<li>Search for <strong>AWS Rekognition Auto-Tagging<\/strong>, then click <strong>Enable<\/strong>.<\/li>\n<li>Verify it appears under <strong>Active Add-ons<\/strong>.<\/li>\n<\/ol>\n<blockquote>\n<p><strong>Tip:<\/strong> Once enabled, any Admin API calls using<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http shcb-wrap-lines\"><span class=\"hljs-attribute\">categorization<\/span>:  'aws_rek_tagging\u2019\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>will automatically analyze and tag your images.<\/p>\n<h3>2. Review the Rekognition Documentation<\/h3>\n<p>Familiarize yourself with the available parameters (e.g., confidence thresholds, categorization vs. tagging): <a href=\"https:\/\/cloudinary.com\/documentation\/aws_rekognition_auto_tagging_addon\">https:\/\/cloudinary.com\/documentation\/aws_rekognition_auto_tagging_addon<\/a><\/p>\n<h3>3. Trigger Auto-Tagging via the Admin API<\/h3>\n<p>For any asset you\u2019ve already uploaded, call the Admin API\u2019s <code>update<\/code> method to run Rekognition and persist high-confidence labels as tags.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ snippet from src\/lib\/ensureTags.ts (full file on GitHub)<\/span>\n<span class=\"hljs-keyword\">import<\/span> { v2 <span class=\"hljs-keyword\">as<\/span> cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'cloudinary'<\/span>;\n\ncloudinary.config({\n  <span class=\"hljs-attr\">cloud_name<\/span>:  process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME!,\n  <span class=\"hljs-attr\">api_key<\/span>:     process.env.CLOUDINARY_API_KEY!,\n  <span class=\"hljs-attr\">api_secret<\/span>:  process.env.CLOUDINARY_API_SECRET!,\n});\n\n<span class=\"hljs-keyword\">await<\/span> cloudinary.api.update(<span class=\"hljs-string\">'cloudinary-storefront\/my-image'<\/span>, {\n  <span class=\"hljs-attr\">categorization<\/span>: <span class=\"hljs-string\">'aws_rek_tagging'<\/span>,\n  <span class=\"hljs-attr\">auto_tagging<\/span>:    <span class=\"hljs-number\">0.7<\/span>,   <span class=\"hljs-comment\">\/\/ only tags \u226570% confidence<\/span>\n});\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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 will analyze <code>my-image<\/code> and merge all detected labels (with \u2265 70% confidence) into its <code>tags<\/code> array.<\/p>\n<blockquote>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-storefront\/blob\/main\/src\/lib\/ensureTags.ts\">See the full implementation here<\/a>.<\/p>\n<\/blockquote>\n<h3>4. (BTW) Batch-Tagging Script<\/h3>\n<p>To automate tagging across your entire <code>cloudinary-storefront<\/code> folder, run our included CLI script:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">chmod  +x  scripts\/tag-assets.cjs\nnpm  run  tag-assets\n<\/code><\/span><\/pre>\n<p>Under the hood it loops through each public ID and invokes the same Admin API update call.<\/p>\n<blockquote>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-storefront\/blob\/main\/scripts\/tag-assets.cjs\">See the script here<\/a>.<\/p>\n<\/blockquote>\n<p><strong>Next up:<\/strong> We\u2019ll expose those tags via an API route and render them as Shadcn-styled badges in <code>&lt;AITags \/&gt;<\/code>.<\/p>\n<h2>Expose Tags via API and Render in the UI<\/h2>\n<p>With Rekognition tags now stored in Cloudinary, it\u2019s time to expose them with an API route and surface them in the frontend UI. We\u2019ll do that in two quick moves:<\/p>\n<ul>\n<li>\n<strong>Build <code>\/api\/tags<\/code>.<\/strong> Fetch and merge tags via Cloudinary\u2019s Admin API.<\/li>\n<li>\n<strong>Show <code>&lt;AITags \/&gt;<\/code>.<\/strong> Render them as colorful badges under each media item.<\/li>\n<\/ul>\n<h3>1. <code>\/api\/tags<\/code> \u2013 Your Rekognition Gateway<\/h3>\n<p>We\u2019ll start by creating an API route at:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">src\/app\/api\/tags\/route.ts\n<\/code><\/span><\/pre>\n<p>This handler accepts a <code>publicId<\/code> (e.g., <code>cloudinary-storefront\/iphone_front<\/code>) and:<\/p>\n<ul>\n<li>Queries Cloudinary\u2019s Admin API using <code>cloudinary.api.resource(...)<\/code>.<\/li>\n<li>Merges <code>resource.tags<\/code> with <code>info.categorization.aws_rek_tagging.data<\/code>.<\/li>\n<li>Returns a deduplicated tag list for your frontend.<\/li>\n<\/ul>\n<h3>The Essential Logic<\/h3>\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\"><span class=\"hljs-keyword\">const<\/span> resource = <span class=\"hljs-keyword\">await<\/span> cloudinary.api.resource(publicId, {\n  <span class=\"hljs-attr\">resource_type<\/span>: <span class=\"hljs-string\">\"image\"<\/span>,\n  <span class=\"hljs-attr\">with_field<\/span>: <span class=\"hljs-string\">\"aws_rek_tagging\"<\/span>,\n});\n\n<span class=\"hljs-keyword\">const<\/span> rekTags = (\n  resource.info?.categorization?.aws_rek_tagging?.data ?? &#91;]\n).map(<span class=\"hljs-function\">(<span class=\"hljs-params\">d<\/span>) =&gt;<\/span> d.tag);\n\n<span class=\"hljs-keyword\">const<\/span> merged = <span class=\"hljs-built_in\">Array<\/span>.from(<span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Set<\/span>(&#91;...(resource.tags || &#91;]), ...rekTags]));\n\n<span class=\"hljs-keyword\">return<\/span> NextResponse.json({ <span class=\"hljs-attr\">tags<\/span>: merged });\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 gives us both human tags (<code>tags: [\u2018iphone\u2019, \u2026]<\/code>) and machine tags (<code>rekTags: [\u2018Electronics\u2019, \u2026]<\/code>) in one payload.<\/p>\n<blockquote>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-storefront\/blob\/main\/src\/app\/api\/tags\/route.ts\">View full API route here<\/a>.<\/p>\n<\/blockquote>\n<h3>2. <code>&lt;AITags \/&gt;<\/code> \u2013 Shadcn-Styled AI Labels<\/h3>\n<p>Next, we\u2019ll build a component that listens for a changing <code>publicId<\/code>, fetches the tags via our API route, and displays them as <code>Badge<\/code> components.<\/p>\n<p>File:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">src\/components\/AITags.tsx\n<\/code><\/span><\/pre>\n<p>The core idea:<\/p>\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\">useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">if<\/span> (!publicId) <span class=\"hljs-keyword\">return<\/span>;\n\n  fetch(<span class=\"hljs-string\">`\/api\/tags?publicId=<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">encodeURIComponent<\/span>(publicId)}<\/span>`<\/span>)\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">res<\/span>) =&gt;<\/span> res.json())\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">data<\/span>) =&gt;<\/span> setTags(data.tags ?? &#91;]));\n}, &#91;publicId]);\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>Then map those tags into stylish Shadcn <code>&lt;Badge&gt;<\/code> pills:<\/p>\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\">tags.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">t, i<\/span>) =&gt;<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Badge<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{t}<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{<\/span>`${<span class=\"hljs-attr\">COLORS<\/span>&#91;<span class=\"hljs-attr\">i<\/span> % <span class=\"hljs-attr\">COLORS.length<\/span>]} <span class=\"hljs-attr\">capitalize<\/span>`}&gt;<\/span>\n    {t}\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Badge<\/span>&gt;<\/span><\/span>\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>The result: Your UI auto-updates to show fresh AI tags as users interact with the Product Gallery.<\/p>\n<blockquote>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-storefront\/blob\/main\/src\/components\/AITags.tsx\">View the full component here<\/a>.<\/p>\n<\/blockquote>\n<p>Next, you\u2019ll need to hook everything up in <code>page.tsx<\/code><\/p>\n<p>At this point:<\/p>\n<ul>\n<li>You\u2019ve configured Cloudinary and Rekognition.<\/li>\n<li>You\u2019ve built a dynamic product gallery.<\/li>\n<li>You\u2019ve exposed an API for fetching AI tags.<\/li>\n<li>You\u2019ve styled the tags with Shadcn <code>&lt;Badge&gt;<\/code> components.<\/li>\n<\/ul>\n<p>Now it\u2019s time to bring it all together on the homepage.<\/p>\n<p>Open (or create):<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">src\/app\/page.tsx\n<\/code><\/span><\/pre>\n<p>This is your root landing page in a Next.js 15 app using the App Router.<\/p>\n<p>All you need to do is import your wrapped Product Gallery component:<\/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-keyword\">import<\/span>  {  CloudinaryRawGallery  }  <span class=\"hljs-keyword\">from<\/span>  <span class=\"hljs-string\">\"@\/components\/CloudinaryRawGallery\"<\/span>;\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<p>Render it inside a centered layout:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-16\" 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\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"container mx-auto px-4 py-14 space-y-10\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-3xl font-bold\"<\/span>&gt;<\/span>Interactive Storefront Demo<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CloudinaryRawGallery<\/span> \/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><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>From here:<\/p>\n<ul>\n<li>The Cloudinary Product Gallery loads your images, video, and 3D assets.<\/li>\n<li>The currently visible asset updates <code>publicId<\/code> state.<\/li>\n<li>\n<code>&lt;AITags \/&gt;<\/code> fetches and shows its AI-generated tags in real time.<\/li>\n<\/ul>\n<p>It looks slick and works instantly with no hydration issues, no manual tagging.<\/p>\n<blockquote>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-storefront\/blob\/main\/src\/app\/page.tsx\">See the full file here<\/a>.<\/p>\n<\/blockquote>\n<h2>Conclusion and Further Reading<\/h2>\n<p>You\u2019ve just built a smart, immersive Next.js storefront that goes beyond static product displays:<\/p>\n<ul>\n<li>AI-powered tagging with AWS Rekognition.<\/li>\n<li>Cloudinary\u2019s Interactive Product Gallery (images, video, 360-degree spinsets, 3D).<\/li>\n<li>Lazy auto-tagging on first view via <code>ensureTags.ts<\/code>.<\/li>\n<li>Shadcn\/UI badge display with searchable, reusable tags.<\/li>\n<li>Admin API and App Router integration using modern <code>route.ts<\/code> files.<\/li>\n<\/ul>\n<p>Together, these features create an experience that\u2019s not just visually rich, but also semantically aware.<\/p>\n<p><a href=\"https:\/\/cloudinary.com\/users\/register_free\">Sign up for a free Cloudinary account today<\/a> to get started.<\/p>\n<h2>Project Links<\/h2>\n<ul>\n<li>GitHub Repository: <a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-storefront\">musebe\/nextjs-cloudinary-storefront<\/a>\n<\/li>\n<li>Live Demo: <a href=\"https:\/\/nextjs-cloudinary-storefront.vercel.app\/\">nextjs-cloudinary-storefront.vercel.app<\/a>\n<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":37805,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[336,98,212,286],"class_list":["post-37803","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-ai","tag-e-commerce","tag-next-js","tag-tagging"],"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>Power Your Next.js Storefront With Interactive Visual Media and Smart AI Tagging Using 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\/next-js-storefront-interactive-visual-media-ai-tagging\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Power Your Next.js Storefront With Interactive Visual Media and Smart AI Tagging Using Cloudinary\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-06-23T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-09-26T21:51:10+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1749681834\/NextJS_interactive_Storefront-blog\/NextJS_interactive_Storefront-blog.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\/next-js-storefront-interactive-visual-media-ai-tagging#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Power Your Next.js Storefront With Interactive Visual Media and Smart AI Tagging Using Cloudinary\",\"datePublished\":\"2025-06-23T14:00:00+00:00\",\"dateModified\":\"2025-09-26T21:51:10+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging\"},\"wordCount\":15,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1749681834\/NextJS_interactive_Storefront-blog\/NextJS_interactive_Storefront-blog.jpg?_i=AA\",\"keywords\":[\"AI\",\"E-commerce\",\"Next.js\",\"Tagging\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging\",\"url\":\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging\",\"name\":\"Power Your Next.js Storefront With Interactive Visual Media and Smart AI Tagging Using Cloudinary\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1749681834\/NextJS_interactive_Storefront-blog\/NextJS_interactive_Storefront-blog.jpg?_i=AA\",\"datePublished\":\"2025-06-23T14:00:00+00:00\",\"dateModified\":\"2025-09-26T21:51:10+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1749681834\/NextJS_interactive_Storefront-blog\/NextJS_interactive_Storefront-blog.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1749681834\/NextJS_interactive_Storefront-blog\/NextJS_interactive_Storefront-blog.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Power Your Next.js Storefront With Interactive Visual Media and Smart AI Tagging Using 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":"Power Your Next.js Storefront With Interactive Visual Media and Smart AI Tagging Using 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\/next-js-storefront-interactive-visual-media-ai-tagging","og_locale":"en_US","og_type":"article","og_title":"Power Your Next.js Storefront With Interactive Visual Media and Smart AI Tagging Using Cloudinary","og_url":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging","og_site_name":"Cloudinary Blog","article_published_time":"2025-06-23T14:00:00+00:00","article_modified_time":"2025-09-26T21:51:10+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1749681834\/NextJS_interactive_Storefront-blog\/NextJS_interactive_Storefront-blog.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\/next-js-storefront-interactive-visual-media-ai-tagging#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Power Your Next.js Storefront With Interactive Visual Media and Smart AI Tagging Using Cloudinary","datePublished":"2025-06-23T14:00:00+00:00","dateModified":"2025-09-26T21:51:10+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging"},"wordCount":15,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1749681834\/NextJS_interactive_Storefront-blog\/NextJS_interactive_Storefront-blog.jpg?_i=AA","keywords":["AI","E-commerce","Next.js","Tagging"],"inLanguage":"en-US","copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging","url":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging","name":"Power Your Next.js Storefront With Interactive Visual Media and Smart AI Tagging Using Cloudinary","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1749681834\/NextJS_interactive_Storefront-blog\/NextJS_interactive_Storefront-blog.jpg?_i=AA","datePublished":"2025-06-23T14:00:00+00:00","dateModified":"2025-09-26T21:51:10+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1749681834\/NextJS_interactive_Storefront-blog\/NextJS_interactive_Storefront-blog.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1749681834\/NextJS_interactive_Storefront-blog\/NextJS_interactive_Storefront-blog.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/next-js-storefront-interactive-visual-media-ai-tagging#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Power Your Next.js Storefront With Interactive Visual Media and Smart AI Tagging Using 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\/v1749681834\/NextJS_interactive_Storefront-blog\/NextJS_interactive_Storefront-blog.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37803","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=37803"}],"version-history":[{"count":3,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37803\/revisions"}],"predecessor-version":[{"id":38634,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37803\/revisions\/38634"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/37805"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=37803"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=37803"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=37803"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}