To integrate Vimeo with V0 by Vercel, generate a video gallery component with V0, create a Next.js API route that calls the Vimeo API using your access token, and add your credentials to Vercel's environment variables. This lets you embed Vimeo videos with the Vimeo Player SDK and fetch video metadata, analytics, and thumbnails — all without exposing your API token in the browser.
Why Integrate Vimeo with Your V0 Next.js App
Vimeo is the go-to video platform for professionals who need ad-free embeds, privacy controls (password protection, domain restrictions), and detailed analytics. Unlike YouTube, Vimeo targets brands, filmmakers, and businesses that want clean video presentation without third-party ads or distractions. If you're building a portfolio site, course platform, product demo page, or media library, Vimeo offers a significantly better viewer experience than consumer video platforms.
The Vimeo API gives you programmatic access to your video library — including upload, metadata retrieval, analytics (views, plays, likes), folder organization, and privacy settings. By combining V0's UI generation with a Next.js API route proxy, you can build rich video experiences: searchable galleries, personalized playlists, analytics dashboards, or gated video content requiring authentication. The Vimeo Player SDK handles embedding and gives you JavaScript control over playback, chapters, and events.
This integration covers two main scenarios: embedding individual Vimeo videos using the Player SDK (which works entirely client-side with no API key required for public videos), and fetching private video libraries and analytics via the Vimeo API (which requires a server-side token kept secret in Vercel environment variables). Most production use cases need both.
Integration method
V0 generates a Next.js video gallery component that embeds Vimeo videos via the Vimeo Player SDK. A Next.js API route proxies requests to the Vimeo API using your server-side access token, fetching video metadata, thumbnails, and analytics. Your Vimeo credentials never reach the browser.
Prerequisites
- A Vimeo account — Basic (free) works for public video embeds; Pro/Business required for analytics API access and private video management
- A Vimeo API access token — create one at developer.vimeo.com by registering an app and generating a Personal Access Token with 'public', 'private', and 'video_files' scopes
- A V0 account and an existing Next.js project, or create a new one at v0.dev
- A Vercel account to deploy your app and store environment variables
- Basic familiarity with how Next.js API routes work (app/api directory)
Step-by-step guide
Generate the Video Gallery UI with V0
Generate the Video Gallery UI with V0
Start by using V0 to generate the front-end video gallery component. Open your V0 project and describe the layout you want — a grid of video cards, a featured video section, or a list layout with thumbnails. Be specific about what data each card should show: thumbnail image, title, duration, view count, and an embed trigger. V0 will generate a React component with placeholder data that you'll connect to the real Vimeo API in the next step. A good V0 prompt describes the visual layout, the data shape, and the interactive behavior. For example, tell V0 you want a responsive grid of video cards where each card displays a thumbnail loaded from a URL prop, a title, a duration badge, and a view count. Specify that clicking a card should open a modal with an iframe-based Vimeo embed. V0 generates clean Tailwind-styled components — you can refine colors, spacing, and typography in V0's Design Mode without writing CSS. The generated component will use hardcoded placeholder data like thumbnail URLs and titles. That's exactly what you want at this stage — you're designing the structure first, then wiring up live data. Once V0 generates the component, review it in the preview pane and use follow-up prompts to adjust any layout details before moving to the API route step.
Create a responsive video gallery page with a grid of video cards. Each card shows a thumbnail image, video title, duration badge, view count, and a play button overlay. Clicking a card opens a modal with an embedded Vimeo player using an iframe with the video ID. Include a search input at the top to filter videos by title. Use placeholder data with 6 example videos for now — I'll connect it to a real API later.
Paste this in V0 chat
Pro tip: Ask V0 to use a 16:9 aspect ratio wrapper div for the thumbnail and embed areas — this prevents layout shifts when the actual video loads.
Expected result: A responsive video gallery page with 6 placeholder video cards, a search input, and a working modal that shows a placeholder Vimeo embed when any card is clicked.
Create the Vimeo API Route
Create the Vimeo API Route
Now create a Next.js API route that proxies requests to the Vimeo API. This route uses your server-side access token (stored as an environment variable) to fetch video data, and returns it to the client component without ever exposing your credentials. Create a file at app/api/vimeo/videos/route.ts. The Vimeo API base URL is https://api.vimeo.com. To fetch all videos from your account, call GET /me/videos with query parameters for fields (to select only the data you need), per_page (default 25, max 100), and sort. The response includes an array of video objects, each with id, name, description, duration, created_time, statistics (plays, likes, comments), and pictures (thumbnails at various sizes). For the embed URL, Vimeo videos use the format https://player.vimeo.com/video/{video_id}. You can extract the numeric video ID from the video's uri field (e.g., /videos/123456789). The thumbnail URL comes from the pictures.sizes array — pick the size closest to your display dimensions (640x360 is a good default). Always specify a fields parameter in your Vimeo API requests to avoid fetching unnecessary data — the full video object can be several kilobytes, but you typically only need 8-10 fields for a gallery view. This significantly improves response times.
1import { NextResponse } from 'next/server';23const VIMEO_API_BASE = 'https://api.vimeo.com';4const ACCESS_TOKEN = process.env.VIMEO_ACCESS_TOKEN;56export async function GET(request: Request) {7 if (!ACCESS_TOKEN) {8 return NextResponse.json(9 { error: 'Vimeo access token not configured' },10 { status: 500 }11 );12 }1314 const { searchParams } = new URL(request.url);15 const query = searchParams.get('query') || '';16 const page = searchParams.get('page') || '1';1718 const params = new URLSearchParams({19 fields: 'uri,name,duration,created_time,statistics,pictures',20 per_page: '25',21 page,22 sort: 'date',23 direction: 'desc',24 ...(query && { query }),25 });2627 const response = await fetch(28 `${VIMEO_API_BASE}/me/videos?${params}`,29 {30 headers: {31 Authorization: `Bearer ${ACCESS_TOKEN}`,32 Accept: 'application/vnd.vimeo.*+json;version=3.4',33 },34 next: { revalidate: 300 }, // Cache for 5 minutes35 }36 );3738 if (!response.ok) {39 const error = await response.json();40 return NextResponse.json(41 { error: error.error || 'Vimeo API error' },42 { status: response.status }43 );44 }4546 const data = await response.json();4748 // Normalize the response for the frontend49 const videos = data.data.map((video: any) => ({50 id: video.uri.split('/').pop(),51 title: video.name,52 duration: video.duration,53 createdAt: video.created_time,54 plays: video.statistics?.plays || 0,55 thumbnail: video.pictures?.sizes?.find(56 (s: any) => s.width >= 64057 )?.link || video.pictures?.sizes?.[0]?.link || '',58 embedUrl: `https://player.vimeo.com/video/${video.uri.split('/').pop()}`,59 }));6061 return NextResponse.json({62 videos,63 total: data.total,64 page: data.page,65 perPage: data.per_page,66 });67}Pro tip: Add next: { revalidate: 300 } to the fetch call to cache Vimeo API responses for 5 minutes — Vimeo has rate limits (1,000 requests per minute for most endpoints), and caching prevents hitting them during traffic spikes.
Expected result: Visiting /api/vimeo/videos in your browser returns a JSON object with a 'videos' array containing normalized video data from your Vimeo account.
Add the Vimeo Access Token to Vercel Environment Variables
Add the Vimeo Access Token to Vercel Environment Variables
Your Vimeo access token must be stored as a server-side environment variable in Vercel — never hardcoded in your source code or committed to Git. Go to your Vercel Dashboard, select your project, click 'Settings' in the top navigation, then click 'Environment Variables' in the left sidebar. Create a new variable named VIMEO_ACCESS_TOKEN and paste your Personal Access Token as the value. Make sure to select all three environment scopes — Production, Preview, and Development — so your app works in all deployment contexts. Click 'Save'. To get your access token, visit developer.vimeo.com, click 'My Apps', then either create a new app or select an existing one. Under 'Authentication', click 'Generate an Access Token'. Select the scopes your app needs: 'public' and 'private' are required for fetching videos; add 'stats' if you need analytics data. Copy the generated token — Vimeo shows it only once. After adding the environment variable in Vercel, you need to redeploy your application for the change to take effect. Vercel environment variables are injected at build time and runtime — changing them in the Dashboard does not automatically update a running deployment. Go to your project's Deployments tab, click the three-dot menu on your latest deployment, and select 'Redeploy'. For local development, create a .env.local file in your project root (make sure it's in .gitignore) and add VIMEO_ACCESS_TOKEN=your_token_here. Next.js automatically loads .env.local during local development.
1# .env.local (never commit this file)2VIMEO_ACCESS_TOKEN=your_personal_access_token_herePro tip: Create separate Vimeo apps for development and production — this lets you use different access tokens per environment and makes it easy to revoke production credentials if needed without affecting your dev workflow.
Expected result: The VIMEO_ACCESS_TOKEN environment variable appears in your Vercel project settings, and calling /api/vimeo/videos returns real video data from your Vimeo account.
Install the Vimeo Player SDK and Connect the UI
Install the Vimeo Player SDK and Connect the UI
Now connect your V0-generated gallery component to the API route. Install the @vimeo/player npm package, which provides a JavaScript API for controlling Vimeo embeds — play, pause, seek, get duration, listen for events like 'play', 'pause', 'ended', and 'timeupdate'. Run npm install @vimeo/player in your terminal, or add it to your package.json. In your gallery component, replace the placeholder data with a useEffect hook that fetches from /api/vimeo/videos when the component mounts. Use React state to store the video list, loading state, and any search query. For the video modal, replace the plain iframe with a div ref that the Vimeo Player SDK attaches to. This gives you JavaScript control over playback — you can auto-play when the modal opens and stop playback when it closes. Use the Player constructor with options like autoplay: true, responsive: true, and dnt: true (do not track — Vimeo's privacy option). The responsive option makes the player fill its container while maintaining the 16:9 aspect ratio, which is essential for a good mobile experience. Pass the video ID as the id option to the Player constructor. When the modal closes, call player.destroy() to remove the player and stop video playback — this prevents audio from continuing after the modal is dismissed. For the search feature, debounce the search input and pass the query parameter to the API route. Add a loading skeleton state so the UI doesn't jump when video data is being fetched.
Update the video gallery component to fetch real data from /api/vimeo/videos on mount. Show a loading skeleton while fetching. Store videos in state and re-fetch when the search input changes (debounce by 300ms, pass as ?query= param). In the video modal, use an iframe with src set to the video's embedUrl property. Auto-pause when the modal closes by clearing the src.
Paste this in V0 chat
1'use client';2import { useState, useEffect, useRef } from 'react';3import Player from '@vimeo/player';45interface VimeoVideo {6 id: string;7 title: string;8 duration: number;9 plays: number;10 thumbnail: string;11 embedUrl: string;12}1314export function VimeoGallery() {15 const [videos, setVideos] = useState<VimeoVideo[]>([]);16 const [loading, setLoading] = useState(true);17 const [selectedVideo, setSelectedVideo] = useState<VimeoVideo | null>(null);18 const [searchQuery, setSearchQuery] = useState('');19 const playerRef = useRef<Player | null>(null);20 const embedRef = useRef<HTMLDivElement | null>(null);2122 useEffect(() => {23 const timer = setTimeout(() => {24 fetchVideos(searchQuery);25 }, 300);26 return () => clearTimeout(timer);27 }, [searchQuery]);2829 async function fetchVideos(query: string) {30 setLoading(true);31 try {32 const params = query ? `?query=${encodeURIComponent(query)}` : '';33 const res = await fetch(`/api/vimeo/videos${params}`);34 const data = await res.json();35 setVideos(data.videos || []);36 } catch (err) {37 console.error('Failed to fetch Vimeo videos:', err);38 } finally {39 setLoading(false);40 }41 }4243 useEffect(() => {44 if (selectedVideo && embedRef.current) {45 playerRef.current = new Player(embedRef.current, {46 id: parseInt(selectedVideo.id),47 autoplay: true,48 responsive: true,49 dnt: true,50 });51 }52 return () => {53 playerRef.current?.destroy();54 playerRef.current = null;55 };56 }, [selectedVideo]);5758 function formatDuration(seconds: number) {59 const m = Math.floor(seconds / 60);60 const s = seconds % 60;61 return `${m}:${s.toString().padStart(2, '0')}`;62 }6364 return (65 <div className="max-w-6xl mx-auto px-4 py-8">66 <input67 type="text"68 placeholder="Search videos..."69 value={searchQuery}70 onChange={(e) => setSearchQuery(e.target.value)}71 className="w-full mb-6 px-4 py-2 border rounded-lg"72 />73 {loading ? (74 <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">75 {[...Array(6)].map((_, i) => (76 <div key={i} className="animate-pulse bg-gray-200 rounded-lg h-48" />77 ))}78 </div>79 ) : (80 <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">81 {videos.map((video) => (82 <div83 key={video.id}84 className="cursor-pointer rounded-lg overflow-hidden shadow hover:shadow-lg transition"85 onClick={() => setSelectedVideo(video)}86 >87 <img src={video.thumbnail} alt={video.title} className="w-full aspect-video object-cover" />88 <div className="p-3">89 <h3 className="font-semibold truncate">{video.title}</h3>90 <p className="text-sm text-gray-500">{formatDuration(video.duration)} · {video.plays.toLocaleString()} plays</p>91 </div>92 </div>93 ))}94 </div>95 )}96 {selectedVideo && (97 <div className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4">98 <div className="w-full max-w-3xl bg-black rounded-lg overflow-hidden">99 <div ref={embedRef} className="aspect-video" />100 <button101 onClick={() => setSelectedVideo(null)}102 className="mt-2 px-4 py-2 text-white hover:text-gray-300"103 >Close</button>104 </div>105 </div>106 )}107 </div>108 );109}Pro tip: Pass dnt: true to the Vimeo Player constructor — this enables Vimeo's privacy-enhanced mode, which doesn't set tracking cookies. This matters if your site is GDPR-compliant and you want to avoid needing cookie consent for video embeds.
Expected result: The video gallery loads real videos from your Vimeo account, the search input filters results, and clicking a video card opens the Vimeo Player with autoplay in a modal.
Deploy to Vercel and Test the Integration
Deploy to Vercel and Test the Integration
With the API route and component connected, deploy your application to Vercel to test the full integration in a production environment. If you haven't already connected your V0 project to GitHub, go to V0's Git panel, click 'Connect to GitHub', authorize the GitHub integration, and push your code. Vercel automatically detects new commits and triggers deployments. After deploying, visit your production URL and test the video gallery. Click a video card to verify the Vimeo Player loads and plays correctly. Test the search functionality to confirm the API route receives the query parameter. Check your browser's Network tab to ensure no Vimeo access token appears in client-side requests — all API calls should go to /api/vimeo/videos (your internal API route), not directly to api.vimeo.com. For debugging, check Vercel's function logs in the Dashboard under your deployment's Functions tab. If the API route returns an error, the logs will show the full error response from the Vimeo API, which makes diagnosing authentication and permission issues much easier than inspecting the browser console alone. Vercel Preview Deployments are useful here — every pull request gets a unique preview URL with its own deployment, and since you set the environment variable for all scopes (Production, Preview, Development), the Vimeo integration works in preview deployments too. Share preview URLs with clients or teammates to get feedback on video layout before merging to production.
Pro tip: For RapidDev clients building Vimeo-heavy applications (large video libraries, advanced analytics, or custom player controls), consider implementing cursor-based pagination using Vimeo's paging.next URL from the API response — it's more reliable than offset-based pagination for large catalogs.
Expected result: Your V0 app is live on a Vercel production URL, the video gallery displays your Vimeo videos, and the Vimeo Player SDK plays them correctly in the modal. No API credentials are visible in browser network requests.
Common use cases
Video Portfolio Gallery
Display your Vimeo video library in a searchable, filterable gallery on your portfolio or agency site. Each card shows the video thumbnail, title, duration, and view count pulled from the Vimeo API, with click-to-play embedding using the Vimeo Player SDK.
Create a video portfolio gallery page that fetches video data from /api/vimeo/videos and displays cards with thumbnail images, video titles, durations, and view counts. Clicking a card opens the video in a modal with an embedded Vimeo player. Include a search input to filter videos by title.
Copy this prompt to try it in V0
Course Platform with Gated Video
Build a course or training platform where enrolled users can access Vimeo videos. The Next.js API route fetches video embed codes for authenticated users only, taking advantage of Vimeo's domain restriction and privacy settings to prevent unauthorized access.
Build a course lesson page that shows a list of lessons in a sidebar and plays the selected video in a main content area. The video URL comes from /api/vimeo/lesson/[id]. Add a completion tracker that marks lessons as watched using localStorage.
Copy this prompt to try it in V0
Video Analytics Dashboard
Create an internal analytics dashboard that displays Vimeo video performance metrics — total plays, unique viewers, finish rate, and geographic data — fetched from the Vimeo API and visualized with charts.
Create a video analytics dashboard that displays data from /api/vimeo/analytics. Show a table of videos with columns for title, total plays, unique viewers, and average percentage watched. Add a bar chart showing daily plays for the selected video over the past 30 days.
Copy this prompt to try it in V0
Troubleshooting
API route returns 401 Unauthorized — '{"error":"You must provide a valid authenticated URL or OAuth token."}'
Cause: The VIMEO_ACCESS_TOKEN environment variable is missing, empty, or the token has been revoked in your Vimeo developer account.
Solution: Verify the token is set in Vercel Dashboard under Settings → Environment Variables. Confirm the variable name is exactly VIMEO_ACCESS_TOKEN (no extra spaces). Go to developer.vimeo.com → Your Apps → Authentication to confirm the token is still valid and hasn't been revoked. After updating the variable in Vercel, redeploy the application.
API route returns 403 Forbidden — videos are fetched but private videos return errors or show as missing
Cause: Your Vimeo access token was generated with insufficient scopes. Fetching private videos requires the 'private' scope; analytics data requires the 'stats' scope.
Solution: Go to developer.vimeo.com → Your App → Authentication → Generate Access Token. Select all required scopes: 'public', 'private', 'video_files', and 'stats'. Generate a new token, update it in Vercel environment variables, and redeploy.
Vimeo Player SDK throws 'Cannot find module @vimeo/player' or player fails to initialize in production
Cause: The @vimeo/player package is not installed, or the Player class is being instantiated on the server side (SSR) before the DOM is available.
Solution: Run npm install @vimeo/player. Ensure the component using the Player is a client component with 'use client' directive, and only initialize the Player inside a useEffect hook (which runs client-side only). Never instantiate Player at the module level.
1// Correct: initialize inside useEffect2useEffect(() => {3 if (embedRef.current) {4 playerRef.current = new Player(embedRef.current, { id: videoId });5 }6 return () => playerRef.current?.destroy();7}, [videoId]);Video thumbnails fail to load, showing broken image icons in the gallery
Cause: Vimeo thumbnail URLs from the pictures.sizes array use Vimeo's image CDN (i.vimeocdn.com), which may require signed URLs for private videos or may have expired.
Solution: Ensure your thumbnail extraction logic falls back gracefully — always check that the pictures object exists and has a sizes array before accessing it. For private videos, the thumbnail URL is still accessible if your access token has the 'private' scope. Use optional chaining in your normalization code.
1thumbnail: video.pictures?.sizes?.find((s: any) => s.width >= 640)?.link 2 || video.pictures?.sizes?.[0]?.link 3 || '/placeholder-video.jpg',Best practices
- Always cache Vimeo API responses in your Next.js API route using next: { revalidate: N } — Vimeo enforces rate limits and caching reduces both latency and API usage costs
- Specify the 'fields' query parameter in every Vimeo API request to fetch only the fields your UI needs — the full video object is large and returning unused data wastes bandwidth and increases response time
- Use the @vimeo/player SDK instead of raw iframes when you need JavaScript control over playback — always call player.destroy() in the useEffect cleanup function to prevent memory leaks and ghost audio
- Store your Vimeo access token as a server-only environment variable (no NEXT_PUBLIC_ prefix) — it must never appear in client-side code or browser network requests
- Implement pagination using Vimeo's paging.next URL from the API response rather than constructing page offset URLs manually — this handles edge cases around deleted videos correctly
- Use Vimeo's privacy settings (domain restrictions, password protection) for sensitive videos instead of trying to implement access control purely in your Next.js app — Vimeo's own restrictions are enforced at the CDN level
- Test your Vimeo integration in a private/incognito browser window to verify that private videos are not accessible without the correct token or embed permissions
Alternatives
Choose YouTube if your audience expects social video features like comments, subscriptions, and algorithmic discovery — YouTube's Data API v3 is free, while Vimeo's analytics API requires a paid plan.
Choose Pixabay if you need free stock videos and images for your app's content rather than hosting your own video library.
Frequently asked questions
Do I need a paid Vimeo plan to use the Vimeo API?
Basic (free) Vimeo accounts can embed public videos and use the Vimeo Player SDK. However, the Vimeo API for fetching video metadata, analytics, and managing private videos requires a Vimeo Pro, Business, or Premium plan. Check developer.vimeo.com for the current API access tier requirements.
Can I embed Vimeo videos without the API or an access token?
Yes — if your videos are public, you can embed them using a plain iframe with the URL https://player.vimeo.com/video/{video_id} without any API credentials. The Vimeo Player SDK also works for public videos without authentication. You only need an API access token when fetching video lists, metadata, or analytics programmatically from your server.
Why are my Vimeo thumbnails not loading in production but work locally?
Vimeo thumbnail URLs from the API point to i.vimeocdn.com and are generally publicly accessible. Check that your thumbnail URL extraction correctly handles the pictures.sizes array — use optional chaining and a fallback value. Also verify that your Vercel deployment has network access to external URLs (Vercel serverless functions can make outbound requests by default).
How do I prevent the Vimeo video from continuing to play when the modal is closed?
Use the Vimeo Player SDK's destroy() method in your modal's close handler or in the useEffect cleanup function. This completely removes the player and stops all media. Alternatively, you can call player.pause() to pause without destroying the player instance, which is useful if you want users to resume from where they left off.
What is the Vimeo API rate limit and how do I avoid hitting it?
Vimeo's rate limit is typically 1,000 API requests per minute per access token for most endpoints. Add next: { revalidate: 300 } to your fetch calls to cache responses for 5 minutes — this drastically reduces API calls on pages that many users visit simultaneously. For video metadata that changes infrequently, you can use longer cache durations.
Can I use Vimeo's privacy and access control features with this integration?
Yes — Vimeo's domain restrictions, password protection, and privacy settings all work independently of your API integration. Set these directly in your Vimeo account settings. Your Next.js app can also check video privacy settings from the API response and conditionally show/hide videos based on user authentication in your app.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation