{"id":37591,"date":"2025-05-13T07:00:00","date_gmt":"2025-05-13T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=37591"},"modified":"2025-05-12T14:47:44","modified_gmt":"2025-05-12T21:47:44","slug":"automatic-video-previews-next-js-cloudinary-ai","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai","title":{"rendered":"Automatic Video Previews in Next.js With Cloudinary AI"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\"><strong>GitHub Repository<\/strong><\/a> | <a href=\"https:\/\/nextjs-cloudinary-ai-video-previews.vercel.app\"><strong>Live Demo<\/strong><\/a><\/p>\n<p>You\u2019re browsing an online course platform and hover over a lesson thumbnail. It shows an eight-second clip, just enough to pique your interest without forcing you to watch the entire video. Now you have to watch the rest. That\u2019s the power of AI-powered previews: Giving users a sneak peek of your content boosts engagement and reduces bounce rates. <a href=\"https:\/\/stephenfollows.com\/p\/what-netflixs-patents-reveal\">Netflix reported that personalized previews can increase user engagement by up to 30%<\/a>, highlighting the effectiveness of dynamic, AI-driven content.<\/p>\n<p><a href=\"https:\/\/cloudinary.com\/documentation\/video_effects_and_enhancements#ai_based_video_preview\"><strong>Cloudinary\u2019s AI Video Preview<\/strong><\/a> API makes this an effortless addition:<\/p>\n<ol>\n<li>\n<p><strong>Upload once.<\/strong> Stream or chunk-upload your full video to Cloudinary.<\/p>\n<\/li>\n<li>\n<p><strong>Generate on the fly.<\/strong> Cloudinary\u2019s AI trims out an optimized snippet (e.g., eight seconds), without manual editing.<\/p>\n<\/li>\n<li>\n<p><strong>Deliver globally.<\/strong> The preview is served from Cloudinary\u2019s CDN, complete with adaptive bitrate and format optimizations.<\/p>\n<\/li>\n<\/ol>\n<p>In this guide, you\u2019ll learn how to integrate automatic video previews into a <strong>Next.js 15<\/strong> app with:<\/p>\n<ul>\n<li>\n<p>A shared <strong>Cloudinary URL-Gen<\/strong> client for building preview and thumbnail URLs.<\/p>\n<\/li>\n<li>\n<p>Server-side upload logic that chooses between buffering and chunked uploads.<\/p>\n<\/li>\n<li>\n<p>A simple <strong>video uploader<\/strong> component that drives the whole flow.<\/p>\n<\/li>\n<li>\n<p>Smart caching of transformed media using an LRU (Least Recently Used) strategy, so repeated image or video URLs load faster without reprocessing.<\/p>\n<\/li>\n<\/ul>\n<p>Let\u2019s see how Cloudinary\u2019s transformations handle the heavy lifting, so you can focus on building great experiences.<\/p>\n<h1>Project Setup<\/h1>\n<p>To get started, we\u2019ll clone the starter template, install dependencies, set up environment variables for Cloudinary and Redis, configure Next.js to trust Cloudinary media, and launch the app. You can follow the full code in the <a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\">GitHub repo<\/a>.<\/p>\n<h2>Clone the Starter Template<\/h2>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">git  <span class=\"hljs-keyword\">clone<\/span>  https:<span class=\"hljs-comment\">\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews.git  <\/span>\n\ncd  nextjs-cloudinary-ai-video-previews\n  \ngit  checkout  feat\/starter-scaffold\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><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 branch provides a Next.js 15 App Router scaffold with Tailwind CSS, shadcn\/ui, Motion.dev, and placeholder components ready for Cloudinary integration.<\/p>\n<h2>Install Dependencies<\/h2>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">npm  install\n\n<span class=\"hljs-comment\"># or yarn \/ pnpm install<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><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>Key packages are already in the starter, including React, Next.js, Tailwind CSS, and Motion.dev.<\/p>\n<h2>Configure Environment Variables<\/h2>\n<p>Create a <code>.env.local<\/code> file at the project root:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name\n\nNEXT_PUBLIC_CLOUDINARY_FOLDER=video-previews\n\nCLOUDINARY_API_KEY=your-api-key\n\nCLOUDINARY_API_SECRET=your-api-secret\n\nUPSTASH_REDIS_REST_URL=your-redis-url\n\nUPSTASH_REDIS_REST_TOKEN=your-redis-token\n<\/code><\/pre>\n<p>We use <a href=\"https:\/\/upstash.com\">Upstash Redis<\/a> as a simple JSON store for video metadata, but you can substitute any database you prefer.<\/p>\n<h2>Allow Cloudinary Media in Next.js<\/h2>\n<p>In <code>next.config.ts<\/code>, add Cloudinary as an approved external host:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>import type { NextConfig } from &quot;next&quot;;\n\nconst nextConfig: NextConfig = {\n  images: {\n    remotePatterns: [\n      {\n        protocol: 'https',\n        hostname: 'res.cloudinary.com',\n        pathname: `\/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}\/**`,\n      },\n    ],\n  },\n};\n\nexport default nextConfig;\n<\/code><\/pre>\n<p>This ensures Next.js can optimize video thumbnails and posters served from your Cloudinary account.<\/p>\n<h2>Start the Development Server<\/h2>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm  run  dev\n<\/code><\/span><\/pre>\n<p>Open <a href=\"http:\/\/localhost:3000\/\">http:\/\/localhost:3000<\/a> in your browser\u2014you should see the starter UI.<\/p>\n<h1>Initialize Cloudinary and URL Helpers<\/h1>\n<p>We need two helper files to power our AI-powered video previews: one to configure Cloudinary, and another to build preview and thumbnail URLs. Let\u2019s walk through each step.<\/p>\n<h2>Create the Helper Files<\/h2>\n<p>First, add a <code>lib<\/code> folder and two blank files:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">mkdir  -p  src\/lib  \n\ntouch  src\/lib\/cloudinary.ts  src\/lib\/transforms.ts\n<\/code><\/span><\/pre>\n<p>These will hold our Cloudinary client and URL builders.<\/p>\n<h2>Configure a Shared Cloudinary Client<\/h2>\n<p>In <code>src\/lib\/cloudinary.ts<\/code>, set up a single <code>cld<\/code> instance:<\/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> { 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-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 <code>cld<\/code> object knows your Cloudinary account and can generate signed URLs or apply transformations anywhere in the app.<\/p>\n<p>(See the complete file on GitHub: <a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\/blob\/main\/src\/lib\/cloudinary.ts\">cloudinary.ts<\/a>)<\/p>\n<h2>Build Preview and Thumbnail URLs<\/h2>\n<p>In <code>src\/lib\/transforms.ts<\/code>, add two functions that wrap Cloudinary\u2019s video edit and resize actions:<\/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\">export<\/span>  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>  <span class=\"hljs-title\">previewUrl<\/span>(<span class=\"hljs-params\">id:  string,  duration  =  <span class=\"hljs-number\">8<\/span><\/span>):  <span class=\"hljs-title\">string<\/span>  <\/span>{ \u2026 }\n\n<span class=\"hljs-keyword\">export<\/span>  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>  <span class=\"hljs-title\">thumbnailUrl<\/span>(<span class=\"hljs-params\">id:  string,  width  =  <span class=\"hljs-number\">800<\/span><\/span>):  <span class=\"hljs-title\">string<\/span>  <\/span>{ \u2026 }\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><strong>previewUrl<\/strong> takes a video ID and duration, then asks Cloudinary to trim an AI-generated snippet of that exact length. The result is a single MP4 URL, cached for instant reuse.<\/p>\n<\/li>\n<li>\n<p><strong>thumbnailUrl<\/strong> takes a video ID and desired width, then requests a JPG poster frame at that size, which is perfect for the <code>&lt;video poster=&quot;\u2026&quot;&gt;<\/code> attribute.<\/p>\n<\/li>\n<\/ul>\n<p>These helpers let your UI or API simply call <code>previewUrl('my-video-id', 8)<\/code> or <code>thumbnailUrl('my-video-id', 400)<\/code> and get back fully optimized, AI-driven media.<\/p>\n<p>(See the full implementations: <a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\/blob\/main\/src\/lib\/transforms.ts\">transforms.ts<\/a>)<\/p>\n<p>With the Cloudinary client and URL helpers in place, you\u2019re ready to upload videos and trigger automatic preview generation.<\/p>\n<h1>Upload a Video and Triggering the Preview<\/h1>\n<p>To generate AI-powered video previews, we first need a way for users to upload their videos. This step involves both UI and server logic, but it all starts with a simple drag-and-drop.<\/p>\n<h2>Build the Upload Experience<\/h2>\n<p>Start by creating the uploader component:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">touch  src\/components\/VideoUploader.tsx\n<\/code><\/span><\/pre>\n<p>In <a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\/blob\/main\/src\/components\/VideoUploader.tsx\"><code>VideoUploader.tsx<\/code><\/a>, users:<\/p>\n<ul>\n<li>\n<p>Pick a video from their device.<\/p>\n<\/li>\n<li>\n<p>Choose the preview duration (e.g., eight seconds).<\/p>\n<\/li>\n<li>\n<p>Click <strong>Upload &amp; Preview<\/strong>.<\/p>\n<\/li>\n<\/ul>\n<p>We use <code>FormData<\/code> to collect everything:<\/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\">form.append(<span class=\"hljs-string\">'file'<\/span>,  file);\n\nform.append(<span class=\"hljs-string\">'duration'<\/span>,  <span class=\"hljs-built_in\">String<\/span>(duration));\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>Then submit it to our API route using XHR:<\/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\">xhr.open(<span class=\"hljs-string\">'POST'<\/span>,  <span class=\"hljs-string\">'\/api\/upload\/video'<\/span>);\n\nxhr.send(form);\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<h2>Handle the Upload on the Server<\/h2>\n<p>Create the backend handler:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">mkdir  -p  src\/app\/api\/upload\/video\n\ntouch  src\/app\/api\/upload\/video\/route.ts\n<\/code><\/span><\/pre>\n<p>The logic in <a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\/blob\/main\/src\/app\/api\/upload\/video\/route.ts\"><code>route.ts<\/code><\/a> does the rest:<\/p>\n<ol>\n<li>\n<p>It parses the incoming video and preview duration.<\/p>\n<\/li>\n<li>\n<p>It checks the file size:<\/p>\n<\/li>\n<\/ol>\n<ul>\n<li>\n<p>Large videos use Cloudinary\u2019s chunked upload.<\/p>\n<\/li>\n<li>\n<p>Smaller ones are streamed directly.<\/p>\n<\/li>\n<\/ul>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">buffer.byteLength  &gt;  100_000_000\n\n?  uploadLargeVideo(tmpPath,  {...})\n\n:  uploadVideoBuffer(buffer,  {...});\n<\/code><\/span><\/pre>\n<p>Once the video is uploaded, we generate a smart preview:<\/p>\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\">const<\/span>  preview  =  previewUrl(publicId,  duration);\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<p>We\u2019ll save both the original and preview URLs using <a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\/blob\/main\/src\/lib\/videoDb.ts\"><code>videoDb.ts<\/code><\/a>:<\/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\">await<\/span>  writeVideos(&#91;{  id, <span class=\"hljs-attr\">originalUrl<\/span>:  result.secure_url, <span class=\"hljs-attr\">previewUrl<\/span>:  preview  }]);\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>And respond to the frontend with everything it needs:<\/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\"><span class=\"hljs-keyword\">return<\/span>  NextResponse.json({  id, <span class=\"hljs-attr\">url<\/span>:  result.secure_url,  preview  });\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>With this in place, every video upload instantly results in:<\/p>\n<ul>\n<li>\n<p>A full Cloudinary-hosted video.<\/p>\n<\/li>\n<li>\n<p>An eight-second smart preview.<\/p>\n<\/li>\n<li>\n<p>A database record for future display.<\/p>\n<\/li>\n<\/ul>\n<p>All handled in one roundtrip.<\/p>\n<h1>Rendering AI Previews in the UI<\/h1>\n<p>Once your video is uploaded to Cloudinary and the preview URL is generated, your job shifts to showing that content to users.<\/p>\n<p>Think of this like a personal media gallery. Every time someone uploads a video, it should appear right away with:<\/p>\n<ul>\n<li>\n<p>A full-length playable version.<\/p>\n<\/li>\n<li>\n<p>An eight-second AI-generated preview (autoplaying and muted).<\/p>\n<\/li>\n<li>\n<p>A crisp thumbnail while the preview loads.<\/p>\n<\/li>\n<\/ul>\n<p>Let\u2019s build that experience from the ground up.<\/p>\n<h2>Create the Video Players<\/h2>\n<p>We\u2019ll start with two lightweight components: one for the original video, and one for the AI-generated preview.<\/p>\n<h3>Create the File:<\/h3>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">mkdir  -p  src\/components\n\ntouch  src\/components\/VideoPlayer.tsx\n\ntouch  src\/components\/PreviewPlayer.tsx\n<\/code><\/span><\/pre>\n<h3><code>VideoPlayer.tsx<\/code> (full video)<\/h3>\n<p>This handles standard playback with browser controls and a Cloudinary poster image.<\/p>\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\">video<\/span>\n  <span class=\"hljs-attr\">controls<\/span>\n  <span class=\"hljs-attr\">poster<\/span>=<span class=\"hljs-string\">{poster}<\/span>\n  <span class=\"hljs-attr\">preload<\/span>=<span class=\"hljs-string\">\"metadata\"<\/span>\n  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full rounded-lg shadow\"<\/span>\n&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{src}<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/mp4\"<\/span> \/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/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<h3><code>PreviewPlayer.tsx<\/code> (AI preview)<\/h3>\n<p>This version is autoplaying, muted, and loops. Perfect for silent previews.<\/p>\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\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span>\n  <span class=\"hljs-attr\">muted<\/span>\n  <span class=\"hljs-attr\">loop<\/span>\n  <span class=\"hljs-attr\">autoPlay<\/span>\n  <span class=\"hljs-attr\">playsInline<\/span>\n  <span class=\"hljs-attr\">poster<\/span>=<span class=\"hljs-string\">{poster}<\/span>\n  <span class=\"hljs-attr\">preload<\/span>=<span class=\"hljs-string\">\"auto\"<\/span>\n  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full rounded-lg shadow\"<\/span>\n&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{src}<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/mp4\"<\/span> \/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span>\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>Both players use the Cloudinary URLs from <code>previewUrl()<\/code> and <code>thumbnailUrl()<\/code> defined earlier in <code>transforms.ts<\/code>.<\/p>\n<h2>Combine Players into a Video Card<\/h2>\n<p>Now that the video components are ready, we\u2019ll group them into a single unit: a <strong>VideoCard<\/strong>.<\/p>\n<h3>Create the File<\/h3>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">touch  src\/components\/VideoCard.tsx\n<\/code><\/span><\/pre>\n<p>In this card:<\/p>\n<ul>\n<li>\n<p>You accept a single <code>video<\/code> object.<\/p>\n<\/li>\n<li>\n<p>Render both the full video and preview.<\/p>\n<\/li>\n<li>\n<p>Add titles, layout, and light UI.<\/p>\n<\/li>\n<\/ul>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" 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\">VideoPlayer<\/span>  <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{video.originalUrl}<\/span>  <span class=\"hljs-attr\">poster<\/span>=<span class=\"hljs-string\">{video.originalPoster}<\/span>  \/&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">PreviewPlayer<\/span>  <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{video.previewUrl}<\/span>  <span class=\"hljs-attr\">poster<\/span>=<span class=\"hljs-string\">{video.previewPoster}<\/span>  \/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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>This becomes the individual building block of your grid.<\/p>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\/blob\/main\/src\/components\/VideoCard.tsx\">View full component \u203a <code>VideoCard.tsx<\/code><\/a><\/p>\n<h2>Build a Video Grid<\/h2>\n<p>We now render multiple cards in a responsive grid.<\/p>\n<h3>Create the File<\/h3>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">touch  src\/components\/VideoGrid.tsx\n<\/code><\/span><\/pre>\n<p>Inside the grid:<\/p>\n<ul>\n<li>\n<p>Videos are sorted from newest to oldest.<\/p>\n<\/li>\n<li>\n<p>Each one is rendered as a <code>VideoCard<\/code>.<\/p>\n<\/li>\n<li>\n<p>Animations are added with <code>motion.div<\/code>.<\/p>\n<\/li>\n<\/ul>\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\">videos.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">video, index<\/span>) =&gt;<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">VideoCard<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{video.id}<\/span> <span class=\"hljs-attr\">video<\/span>=<span class=\"hljs-string\">{video}<\/span> <span class=\"hljs-attr\">index<\/span>=<span class=\"hljs-string\">{index}<\/span> \/&gt;<\/span><\/span>\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><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\/blob\/main\/src\/components\/VideoGrid.tsx\">View full component \u203a <code>VideoGrid.tsx<\/code><\/a><\/p>\n<h2>Track Uploads and State in a Client Component<\/h2>\n<p>This top-level component handles <strong>upload events<\/strong> and tracks uploaded videos.<\/p>\n<h3>Create the File<\/h3>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">touch  src\/components\/HomeClient.tsx\n<\/code><\/span><\/pre>\n<p>This component:<\/p>\n<ul>\n<li>\n<p>Accepts <code>initialVideos<\/code> as a prop<\/p>\n<\/li>\n<li>\n<p>Uses <code>useState()<\/code> to track updates<\/p>\n<\/li>\n<li>\n<p>Updates the list after every successful upload<\/p>\n<\/li>\n<li>\n<p>Calls <code>thumbnailUrl()<\/code> for Cloudinary posters<\/p>\n<\/li>\n<\/ul>\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\">setVideos(<span class=\"hljs-function\">(<span class=\"hljs-params\">prev<\/span>)  =&gt;<\/span>  &#91;newVideo,  ...prev]);\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>It also renders the <strong>upload modal<\/strong>, which we\u2019ll build in the next step.<\/p>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\/blob\/main\/src\/components\/HomeClient.tsx\">View full component \u203a <code>HomeClient.tsx<\/code><\/a><\/p>\n<h2>Wire It All Into the Homepage<\/h2>\n<p>Now we connect everything in the root page of the app.<\/p>\n<h3>Create the file:<\/h3>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">touch  src\/app\/page.tsx\n<\/code><\/span><\/pre>\n<p>In this file:<\/p>\n<ul>\n<li>\n<p>Fetch video records from Redis<\/p>\n<\/li>\n<li>\n<p>Generate thumbnail URLs using Cloudinary<\/p>\n<\/li>\n<li>\n<p>Pass it all into <code>HomeClient<\/code><\/p>\n<\/li>\n<\/ul>\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\">const<\/span> rawVideos = <span class=\"hljs-keyword\">await<\/span> readVideos();\n\n<span class=\"hljs-keyword\">const<\/span> videos = rawVideos.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">v<\/span>) =&gt;<\/span> ({\n  ...v,\n  <span class=\"hljs-attr\">originalPoster<\/span>: thumbnailUrl(v.id, <span class=\"hljs-number\">400<\/span>),\n  <span class=\"hljs-attr\">previewPoster<\/span>: thumbnailUrl(v.id, <span class=\"hljs-number\">400<\/span>),\n}));\n\n<span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">HomeClient<\/span> <span class=\"hljs-attr\">initialVideos<\/span>=<span class=\"hljs-string\">{videos}<\/span> \/&gt;<\/span><\/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><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\/blob\/main\/src\/app\/page.tsx\">View full file \u203a <code>page.tsx<\/code><\/a><\/p>\n<h2>What We Just Built<\/h2>\n<p>In this step, we built a complete front-end experience around Cloudinary AI previews:<\/p>\n<ul>\n<li>\n<p>A full video player and looping preview for each upload.<\/p>\n<\/li>\n<li>\n<p>A card layout to display them side by side.<\/p>\n<\/li>\n<li>\n<p>A dynamic grid that updates instantly on upload.<\/p>\n<\/li>\n<li>\n<p>A homepage wired to fetch and display media from Cloudinary.<\/p>\n<\/li>\n<\/ul>\n<p>This brings your AI video preview workflow full circle \u2014 from upload to display, all powered by Cloudinary and updated in real time.<\/p>\n<h1>Creating the Individual Video Detail Page<\/h1>\n<p>The homepage shows your full gallery \u2014 but sometimes, users want to focus on a single video. That\u2019s where the <strong><code>\/videos\/[id]<\/code><\/strong> route comes in.<\/p>\n<p>This page displays:<\/p>\n<ul>\n<li>\n<p>The <strong>original video<\/strong> (hosted on Cloudinary)<\/p>\n<\/li>\n<li>\n<p>The <strong>AI-generated preview<\/strong><\/p>\n<\/li>\n<li>\n<p>A clean layout for watching and comparing the two<\/p>\n<\/li>\n<\/ul>\n<p>Let\u2019s walk through how to build it.<\/p>\n<h2>Create the Dynamic Route File<\/h2>\n<p>In Next.js 15 App Router, dynamic routes go in the <code>app\/<\/code> directory using square brackets.<\/p>\n<p>Create the file:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">mkdir  -p  src\/app\/videos\/&#91;id]  \n\ntouch  src\/app\/videos\/&#91;id]\/page.tsx\n<\/code><\/span><\/pre>\n<h2>Fetch Cloudinary URLs with Helpers<\/h2>\n<p>Inside the page, we extract the video <code>id<\/code> from the route, then build URLs using our existing Cloudinary helpers.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-16\" 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> publicId = <span class=\"hljs-string\">`video-previews\/<span class=\"hljs-subst\">${id}<\/span>`<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> originalUrl = cld.video(publicId).format(<span class=\"hljs-string\">'mp4'<\/span>).quality(<span class=\"hljs-string\">'auto'<\/span>).toURL();\n<span class=\"hljs-keyword\">const<\/span> aiPreview = previewUrl(id, <span class=\"hljs-number\">8<\/span>);\n\n<span class=\"hljs-keyword\">const<\/span> originalPoster = thumbnailUrl(id, <span class=\"hljs-number\">800<\/span>);\n<span class=\"hljs-keyword\">const<\/span> previewPoster = thumbnailUrl(id, <span class=\"hljs-number\">800<\/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\">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>We reuse the same tools from <code>transforms.ts<\/code>, so all our thumbnails and previews stay consistent across the app.<\/p>\n<h2>Render the Layout<\/h2>\n<p>Finally, we build the UI:<\/p>\n<ul>\n<li>\n<p>Add headers and sections for each video<\/p>\n<\/li>\n<li>\n<p>Use <code>&lt;VideoPlayer \/&gt;<\/code> and <code>&lt;PreviewPlayer \/&gt;<\/code> again<\/p>\n<\/li>\n<li>\n<p>Layout both players side-by-side<\/p>\n<\/li>\n<\/ul>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-17\" 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\">VideoPlayer<\/span>  <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{originalUrl}<\/span>  <span class=\"hljs-attr\">poster<\/span>=<span class=\"hljs-string\">{originalPoster}<\/span>  \/&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">PreviewPlayer<\/span>  <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{aiPreview}<\/span>  <span class=\"hljs-attr\">poster<\/span>=<span class=\"hljs-string\">{previewPoster}<\/span>  \/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><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>This gives us a clean side-by-side comparison between full-length and AI-trimmed versions.<\/p>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\/blob\/main\/src\/app\/videos\/%5Bid%5D\/page.tsx\">View full file \u203a <code>page.tsx<\/code><\/a><\/p>\n<h2>What This Unlocks<\/h2>\n<p>With this page added, your app now supports:<\/p>\n<ul>\n<li>\n<p>Dedicated links to each video (<code>\/videos\/abc123<\/code>).<\/p>\n<\/li>\n<li>\n<p>Full-size Cloudinary video playback.<\/p>\n<\/li>\n<li>\n<p>Direct preview comparisons for marketing, tutorials, or demo content.<\/p>\n<\/li>\n<\/ul>\n<h1>Conclusion<\/h1>\n<p>With <strong>Cloudinary\u2019s AI Video Preview<\/strong>, you turned a full-length video into a dynamic preview without manual editing or complex tooling, automatically. It\u2019s a flexible, scalable way to enhance video experiences in modern web apps \u2014 ideal for product demos, online courses, and content platforms.<\/p>\n<p>Want to explore more? Cloudinary also offers:<\/p>\n<ul>\n<li>\n<p><strong>Scene-aware cropping.<\/strong> Auto-detect the most relevant content.<\/p>\n<\/li>\n<li>\n<p><strong>AI-based highlight reels.<\/strong> Generate the best moments.<\/p>\n<\/li>\n<li>\n<p><strong>Background removal and blur.<\/strong> Clean up noisy footage instantly.<\/p>\n<\/li>\n<\/ul>\n<p>Check the docs for what\u2019s possible:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/cloudinary.com\/documentation\/video_effects_and_enhancements#ai_based_video_preview\">AI-Based Video Preview Docs<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/cloudinary.com\/blog\/auto_generate_video_previews_with_great_results_every_time\">How to Auto-Generate Previews (Blog)<\/a><\/p>\n<\/li>\n<\/ul>\n<p><strong>Full code<\/strong>: <a href=\"https:\/\/github.com\/musebe\/nextjs-cloudinary-ai-video-previews\">github.com\/musebe\/nextjs-cloudinary-ai-video-previews<\/a><\/p>\n<p><strong>Live demo<\/strong>: <a href=\"https:\/\/nextjs-cloudinary-ai-video-previews.vercel.app\">https:\/\/nextjs-cloudinary-ai-video-previews.vercel.app<\/a><\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":37598,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[336,212,303,305],"class_list":["post-37591","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-ai","tag-next-js","tag-video","tag-video-api"],"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>Automatic Video Previews in Next.js With Cloudinary AI<\/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\/automatic-video-previews-next-js-cloudinary-ai\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Automatic Video Previews in Next.js With Cloudinary AI\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-05-13T14:00:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746646750\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI.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\/automatic-video-previews-next-js-cloudinary-ai#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Automatic Video Previews in Next.js With Cloudinary AI\",\"datePublished\":\"2025-05-13T14:00:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai\"},\"wordCount\":9,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746646750\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI.jpg?_i=AA\",\"keywords\":[\"AI\",\"Next.js\",\"Video\",\"Video API\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai\",\"url\":\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai\",\"name\":\"Automatic Video Previews in Next.js With Cloudinary AI\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746646750\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI.jpg?_i=AA\",\"datePublished\":\"2025-05-13T14:00:00+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746646750\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746646750\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Automatic Video Previews in Next.js With Cloudinary AI\"}]},{\"@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":"Automatic Video Previews in Next.js With Cloudinary AI","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\/automatic-video-previews-next-js-cloudinary-ai","og_locale":"en_US","og_type":"article","og_title":"Automatic Video Previews in Next.js With Cloudinary AI","og_url":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai","og_site_name":"Cloudinary Blog","article_published_time":"2025-05-13T14:00:00+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746646750\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI.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\/automatic-video-previews-next-js-cloudinary-ai#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Automatic Video Previews in Next.js With Cloudinary AI","datePublished":"2025-05-13T14:00:00+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai"},"wordCount":9,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746646750\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI.jpg?_i=AA","keywords":["AI","Next.js","Video","Video API"],"inLanguage":"en-US","copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai","url":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai","name":"Automatic Video Previews in Next.js With Cloudinary AI","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746646750\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI.jpg?_i=AA","datePublished":"2025-05-13T14:00:00+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746646750\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1746646750\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/automatic-video-previews-next-js-cloudinary-ai#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Automatic Video Previews in Next.js With Cloudinary AI"}]},{"@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\/v1746646750\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI\/Blog_How_to_Generate_Automatic_Video_Previews_in_Next.js_with_Cloudinary_AI.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37591","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=37591"}],"version-history":[{"count":2,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37591\/revisions"}],"predecessor-version":[{"id":37607,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/37591\/revisions\/37607"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/37598"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=37591"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=37591"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=37591"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}