Skip to content

RESOURCES / BLOG

Automatic Video Previews in Next.js With Cloudinary AI

GitHub Repository | Live Demo

You’re 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’s the power of AI-powered previews: Giving users a sneak peek of your content boosts engagement and reduces bounce rates. Netflix reported that personalized previews can increase user engagement by up to 30%, highlighting the effectiveness of dynamic, AI-driven content.

Cloudinary’s AI Video Preview API makes this an effortless addition:

  1. Upload once. Stream or chunk-upload your full video to Cloudinary.

  2. Generate on the fly. Cloudinary’s AI trims out an optimized snippet (e.g., eight seconds), without manual editing.

  3. Deliver globally. The preview is served from Cloudinary’s CDN, complete with adaptive bitrate and format optimizations.

In this guide, you’ll learn how to integrate automatic video previews into a Next.js 15 app with:

  • A shared Cloudinary URL-Gen client for building preview and thumbnail URLs.

  • Server-side upload logic that chooses between buffering and chunked uploads.

  • A simple video uploader component that drives the whole flow.

  • Smart caching of transformed media using an LRU (Least Recently Used) strategy, so repeated image or video URLs load faster without reprocessing.

Let’s see how Cloudinary’s transformations handle the heavy lifting, so you can focus on building great experiences.

To get started, we’ll 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 GitHub repo.

git  clone  https://github.com/musebe/nextjs-cloudinary-ai-video-previews.git  

cd  nextjs-cloudinary-ai-video-previews
  
git  checkout  feat/starter-scaffold
Code language: PHP (php)

This branch provides a Next.js 15 App Router scaffold with Tailwind CSS, shadcn/ui, Motion.dev, and placeholder components ready for Cloudinary integration.

npm  install

# or yarn / pnpm install
Code language: PHP (php)

Key packages are already in the starter, including React, Next.js, Tailwind CSS, and Motion.dev.

Create a .env.local file at the project root:

NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name

NEXT_PUBLIC_CLOUDINARY_FOLDER=video-previews

CLOUDINARY_API_KEY=your-api-key

CLOUDINARY_API_SECRET=your-api-secret

UPSTASH_REDIS_REST_URL=your-redis-url

UPSTASH_REDIS_REST_TOKEN=your-redis-token

We use Upstash Redis as a simple JSON store for video metadata, but you can substitute any database you prefer.

In next.config.ts, add Cloudinary as an approved external host:

import type { NextConfig } from "next";

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

export default nextConfig;

This ensures Next.js can optimize video thumbnails and posters served from your Cloudinary account.

npm  run  dev

Open http://localhost:3000 in your browser—you should see the starter UI.

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’s walk through each step.

First, add a lib folder and two blank files:

mkdir  -p  src/lib  

touch  src/lib/cloudinary.ts  src/lib/transforms.ts

These will hold our Cloudinary client and URL builders.

In src/lib/cloudinary.ts, set up a single cld instance:

import { Cloudinary } from '@cloudinary/url-gen';

export const cld = new Cloudinary({
  cloud:  { cloudName: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME! },
  url:    { secure: true },
});
Code language: JavaScript (javascript)

This cld object knows your Cloudinary account and can generate signed URLs or apply transformations anywhere in the app.

(See the complete file on GitHub: cloudinary.ts)

In src/lib/transforms.ts, add two functions that wrap Cloudinary’s video edit and resize actions:

export  function  previewUrl(id:  string,  duration  =  8):  string  { … }

export  function  thumbnailUrl(id:  string,  width  =  800):  string  { … }
Code language: JavaScript (javascript)
  • previewUrl 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.

  • thumbnailUrl takes a video ID and desired width, then requests a JPG poster frame at that size, which is perfect for the <video poster="…"> attribute.

These helpers let your UI or API simply call previewUrl('my-video-id', 8) or thumbnailUrl('my-video-id', 400) and get back fully optimized, AI-driven media.

(See the full implementations: transforms.ts)

With the Cloudinary client and URL helpers in place, you’re ready to upload videos and trigger automatic preview generation.

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.

Start by creating the uploader component:

touch  src/components/VideoUploader.tsx

In VideoUploader.tsx, users:

  • Pick a video from their device.

  • Choose the preview duration (e.g., eight seconds).

  • Click Upload & Preview.

We use FormData to collect everything:

form.append('file',  file);

form.append('duration',  String(duration));
Code language: JavaScript (javascript)

Then submit it to our API route using XHR:

xhr.open('POST',  '/api/upload/video');

xhr.send(form);
Code language: JavaScript (javascript)

Create the backend handler:

mkdir  -p  src/app/api/upload/video

touch  src/app/api/upload/video/route.ts

The logic in route.ts does the rest:

  1. It parses the incoming video and preview duration.

  2. It checks the file size:

  • Large videos use Cloudinary’s chunked upload.

  • Smaller ones are streamed directly.

buffer.byteLength  >  100_000_000

?  uploadLargeVideo(tmpPath,  {...})

:  uploadVideoBuffer(buffer,  {...});

Once the video is uploaded, we generate a smart preview:

const  preview  =  previewUrl(publicId,  duration);
Code language: JavaScript (javascript)

We’ll save both the original and preview URLs using videoDb.ts:

await  writeVideos([{  id, originalUrl:  result.secure_url, previewUrl:  preview  }]);
Code language: JavaScript (javascript)

And respond to the frontend with everything it needs:

return  NextResponse.json({  id, url:  result.secure_url,  preview  });
Code language: JavaScript (javascript)

With this in place, every video upload instantly results in:

  • A full Cloudinary-hosted video.

  • An eight-second smart preview.

  • A database record for future display.

All handled in one roundtrip.

Once your video is uploaded to Cloudinary and the preview URL is generated, your job shifts to showing that content to users.

Think of this like a personal media gallery. Every time someone uploads a video, it should appear right away with:

  • A full-length playable version.

  • An eight-second AI-generated preview (autoplaying and muted).

  • A crisp thumbnail while the preview loads.

Let’s build that experience from the ground up.

We’ll start with two lightweight components: one for the original video, and one for the AI-generated preview.

mkdir  -p  src/components

touch  src/components/VideoPlayer.tsx

touch  src/components/PreviewPlayer.tsx

This handles standard playback with browser controls and a Cloudinary poster image.

<video
  controls
  poster={poster}
  preload="metadata"
  className="w-full rounded-lg shadow"
>
  <source src={src} type="video/mp4" />
</video>
Code language: HTML, XML (xml)

This version is autoplaying, muted, and loops. Perfect for silent previews.

<video
  muted
  loop
  autoPlay
  playsInline
  poster={poster}
  preload="auto"
  className="w-full rounded-lg shadow"
>
  <source src={src} type="video/mp4" />
</video>
Code language: HTML, XML (xml)

Both players use the Cloudinary URLs from previewUrl() and thumbnailUrl() defined earlier in transforms.ts.

Now that the video components are ready, we’ll group them into a single unit: a VideoCard.

touch  src/components/VideoCard.tsx

In this card:

  • You accept a single video object.

  • Render both the full video and preview.

  • Add titles, layout, and light UI.

<VideoPlayer  src={video.originalUrl}  poster={video.originalPoster}  />

<PreviewPlayer  src={video.previewUrl}  poster={video.previewPoster}  />
Code language: HTML, XML (xml)

This becomes the individual building block of your grid.

View full component › VideoCard.tsx

We now render multiple cards in a responsive grid.

touch  src/components/VideoGrid.tsx

Inside the grid:

  • Videos are sorted from newest to oldest.

  • Each one is rendered as a VideoCard.

  • Animations are added with motion.div.

videos.map((video, index) => (
  <VideoCard key={video.id} video={video} index={index} />
))
Code language: JavaScript (javascript)

View full component › VideoGrid.tsx

This top-level component handles upload events and tracks uploaded videos.

touch  src/components/HomeClient.tsx

This component:

  • Accepts initialVideos as a prop

  • Uses useState() to track updates

  • Updates the list after every successful upload

  • Calls thumbnailUrl() for Cloudinary posters

setVideos((prev)  =>  [newVideo,  ...prev]);
Code language: JavaScript (javascript)

It also renders the upload modal, which we’ll build in the next step.

View full component › HomeClient.tsx

Now we connect everything in the root page of the app.

touch  src/app/page.tsx

In this file:

  • Fetch video records from Redis

  • Generate thumbnail URLs using Cloudinary

  • Pass it all into HomeClient

const rawVideos = await readVideos();

const videos = rawVideos.map((v) => ({
  ...v,
  originalPoster: thumbnailUrl(v.id, 400),
  previewPoster: thumbnailUrl(v.id, 400),
}));

return <HomeClient initialVideos={videos} />;
Code language: JavaScript (javascript)

View full file › page.tsx

In this step, we built a complete front-end experience around Cloudinary AI previews:

  • A full video player and looping preview for each upload.

  • A card layout to display them side by side.

  • A dynamic grid that updates instantly on upload.

  • A homepage wired to fetch and display media from Cloudinary.

This brings your AI video preview workflow full circle — from upload to display, all powered by Cloudinary and updated in real time.

The homepage shows your full gallery — but sometimes, users want to focus on a single video. That’s where the /videos/[id] route comes in.

This page displays:

  • The original video (hosted on Cloudinary)

  • The AI-generated preview

  • A clean layout for watching and comparing the two

Let’s walk through how to build it.

In Next.js 15 App Router, dynamic routes go in the app/ directory using square brackets.

Create the file:

mkdir  -p  src/app/videos/[id]  

touch  src/app/videos/[id]/page.tsx

Inside the page, we extract the video id from the route, then build URLs using our existing Cloudinary helpers.

const publicId = `video-previews/${id}`;

const originalUrl = cld.video(publicId).format('mp4').quality('auto').toURL();
const aiPreview = previewUrl(id, 8);

const originalPoster = thumbnailUrl(id, 800);
const previewPoster = thumbnailUrl(id, 800);
Code language: JavaScript (javascript)

We reuse the same tools from transforms.ts, so all our thumbnails and previews stay consistent across the app.

Finally, we build the UI:

  • Add headers and sections for each video

  • Use <VideoPlayer /> and <PreviewPlayer /> again

  • Layout both players side-by-side

<VideoPlayer  src={originalUrl}  poster={originalPoster}  />

<PreviewPlayer  src={aiPreview}  poster={previewPoster}  />
Code language: HTML, XML (xml)

This gives us a clean side-by-side comparison between full-length and AI-trimmed versions.

View full file › page.tsx

With this page added, your app now supports:

  • Dedicated links to each video (/videos/abc123).

  • Full-size Cloudinary video playback.

  • Direct preview comparisons for marketing, tutorials, or demo content.

With Cloudinary’s AI Video Preview, you turned a full-length video into a dynamic preview without manual editing or complex tooling, automatically. It’s a flexible, scalable way to enhance video experiences in modern web apps — ideal for product demos, online courses, and content platforms.

Want to explore more? Cloudinary also offers:

  • Scene-aware cropping. Auto-detect the most relevant content.

  • AI-based highlight reels. Generate the best moments.

  • Background removal and blur. Clean up noisy footage instantly.

Check the docs for what’s possible:

Full code: github.com/musebe/nextjs-cloudinary-ai-video-previews

Live demo: https://nextjs-cloudinary-ai-video-previews.vercel.app

Start Using Cloudinary

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

Sign Up for Free