Skip to main content
RapidDev - Software Development Agency
v0-integrationsNext.js API Route

How to Integrate YouTube API with V0

To integrate the YouTube Data API v3 with a V0 by Vercel app, create a Next.js API route that calls the YouTube API using your Google API key. Generate your video gallery UI with V0, then connect it to an API route that fetches channel videos, playlists, and video statistics. Store your API key in Vercel environment variables. Users can browse and watch YouTube videos embedded directly in your V0-generated interface.

What you'll learn

  • How to generate a video gallery UI with thumbnails and video embeds using V0
  • How to create a Google Cloud project and obtain a YouTube Data API v3 key
  • How to create a Next.js API route that fetches channel videos and playlists from YouTube
  • How to embed YouTube videos using the iframe API in a Next.js component
  • How to cache YouTube API responses to stay within daily quota limits
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read30 minutesSocialMarch 2026RapidDev Engineering Team
TL;DR

To integrate the YouTube Data API v3 with a V0 by Vercel app, create a Next.js API route that calls the YouTube API using your Google API key. Generate your video gallery UI with V0, then connect it to an API route that fetches channel videos, playlists, and video statistics. Store your API key in Vercel environment variables. Users can browse and watch YouTube videos embedded directly in your V0-generated interface.

Build Custom Video Galleries and Portals with YouTube Data API v3

YouTube has 800 million videos and processes billions of views daily. The YouTube Data API v3 gives developers programmatic access to this enormous video catalog — including your own channel's videos, playlist contents, video metadata, and search results. For creators, educators, and businesses with a YouTube presence, embedding a custom video gallery in your V0 app lets you showcase content without sending visitors away to YouTube.

The integration pattern for V0 is clean: use V0 to generate the video gallery UI (thumbnail grids, video players, playlist navigation), and a Next.js API route handles all YouTube API queries server-side with your API key. The API route normalizes YouTube's verbose response format into a simpler shape for your frontend components. Video playback itself uses YouTube's standard iframe embed — you don't stream video through your server.

The YouTube Data API v3 uses a quota system rather than rate limits. Each API operation costs a certain number of quota units (list operations cost 1-100 units), and you get 10,000 units per day for free. A typical video gallery fetching 50 videos costs about 1-3 units — you'd need to serve millions of page loads to exhaust the free quota. For high-traffic applications, quota increases are available through Google Cloud.

Integration method

Next.js API Route

YouTube Data API v3 integrates with V0 apps through a Next.js API route that authenticates with a Google API key and queries YouTube's REST API. The API route fetches video metadata, channel information, and playlist contents, returning normalized data to V0-generated video gallery components. Video playback uses YouTube's iframe embed rather than the API itself.

Prerequisites

  • A Google account with access to Google Cloud Console (console.cloud.google.com)
  • YouTube Data API v3 enabled in your Google Cloud project with an API key created
  • Your YouTube channel ID (visible in YouTube Studio → Settings → Channel → Advanced settings)
  • A V0 project exported to GitHub and deployed on Vercel
  • Basic understanding of Next.js API routes and React components

Step-by-step guide

1

Get a YouTube Data API v3 Key from Google Cloud

Navigate to console.cloud.google.com and create a new Google Cloud project (or use an existing one). In the left sidebar, go to APIs & Services → Library and search for 'YouTube Data API v3'. Click on it and then click 'Enable' to activate the API for your project. Next, go to APIs & Services → Credentials and click 'Create Credentials' → 'API Key'. Google creates an API key immediately. Click 'Edit API Key' to configure restrictions — this is important for security. Under Application Restrictions, select 'HTTP referrers' and add your Vercel deployment URL (e.g., `your-app.vercel.app/*`) plus `localhost:3000/*` for local development. Under API Restrictions, select 'Restrict key' and choose 'YouTube Data API v3' from the dropdown. Click 'Save'. Restricting your API key prevents it from being abused even if someone discovers it. While the YouTube API key can be used client-side (it's not as sensitive as a secret key), proxying it through a Next.js API route is still best practice — it lets you add caching, rate limiting, and keeps you from having to expose the key in your JavaScript bundle. Note your API key — you'll need it for the Vercel environment variable in the next step. Also note your daily quota: 10,000 units/day on the free tier. The `search.list` endpoint costs 100 units per call, while `videos.list` and `playlistItems.list` cost just 1 unit each. Design your API routes to use the cheaper endpoints where possible.

V0 Prompt

Create a video gallery page for a YouTube channel. Show a grid of video thumbnails with title, view count, and duration. Include a featured video player at the top that plays the most recent video. Add a search bar that filters the displayed videos by title. The page should work on mobile with a single-column layout.

Paste this in V0 chat

Pro tip: Use `playlistItems.list` with your channel's uploads playlist ID instead of `search.list` for fetching your own channel's videos — it costs 1 unit instead of 100 units, making it 100x more quota-efficient.

Expected result: A YouTube Data API v3 key created in Google Cloud Console with proper domain and API restrictions configured.

2

Create the YouTube API Route

Create a Next.js API route at `app/api/youtube/route.ts` that queries the YouTube Data API v3. The API base URL is `https://www.googleapis.com/youtube/v3/` and authentication uses your API key as a query parameter (`key={API_KEY}`). For fetching your channel's videos efficiently, use the uploads playlist pattern: every YouTube channel has an automatically generated 'uploads' playlist whose ID is your channel ID with 'UC' replaced by 'UU'. For example, if your channel ID is `UCxxxxxx`, the uploads playlist ID is `UUxxxxxx`. Fetch this playlist's items using `playlistItems.list` (1 unit) rather than `search.list` (100 units). The `playlistItems.list` endpoint returns video IDs but not full video details. Make a second call to `videos.list` with the video IDs to get statistics (view count, like count, duration). Batch the IDs comma-separated in a single `videos.list` call — you can fetch up to 50 videos per call. Key YouTube API parameters: - `part=snippet,contentDetails,statistics` on videos.list for full metadata - `maxResults=50` for pagination (YouTube caps at 50 per request) - `pageToken` for cursor-based pagination through large channels - `order=date` for chronological ordering Normalize the response before returning to the frontend: format view counts, extract video duration from ISO 8601 format (PT4M20S → '4:20'), calculate relative publish dates ('3 days ago'), and include the embed URL (`https://www.youtube.com/embed/{videoId}`) for the iframe player.

app/api/youtube/route.ts
1// app/api/youtube/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4const API_KEY = process.env.YOUTUBE_API_KEY!;
5const BASE = 'https://www.googleapis.com/youtube/v3';
6const CHANNEL_ID = process.env.YOUTUBE_CHANNEL_ID!;
7
8function formatDuration(iso: string): string {
9 const match = iso.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);
10 if (!match) return '0:00';
11 const h = parseInt(match[1] || '0');
12 const m = parseInt(match[2] || '0');
13 const s = parseInt(match[3] || '0');
14 const mm = m.toString().padStart(h > 0 ? 2 : 1, '0');
15 const ss = s.toString().padStart(2, '0');
16 return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
17}
18
19function formatViews(count: string): string {
20 const n = parseInt(count);
21 if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M views`;
22 if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K views`;
23 return `${n} views`;
24}
25
26export async function GET(request: NextRequest) {
27 const { searchParams } = new URL(request.url);
28 const resource = searchParams.get('resource') || 'videos';
29 const playlistId = searchParams.get('playlistId') || '';
30 const pageToken = searchParams.get('pageToken') || '';
31 const videoId = searchParams.get('videoId') || '';
32
33 try {
34 if (resource === 'videos') {
35 // Use uploads playlist for efficiency (1 unit vs 100 for search)
36 const uploadsPlaylistId = CHANNEL_ID.replace(/^UC/, 'UU');
37 const itemsUrl = new URL(`${BASE}/playlistItems`);
38 itemsUrl.searchParams.set('part', 'snippet,contentDetails');
39 itemsUrl.searchParams.set('playlistId', playlistId || uploadsPlaylistId);
40 itemsUrl.searchParams.set('maxResults', '24');
41 itemsUrl.searchParams.set('key', API_KEY);
42 if (pageToken) itemsUrl.searchParams.set('pageToken', pageToken);
43
44 const itemsRes = await fetch(itemsUrl.toString(), { next: { revalidate: 600 } });
45 const itemsData = await itemsRes.json();
46
47 if (!itemsData.items?.length) return NextResponse.json({ videos: [], nextPageToken: null });
48
49 // Fetch video details in batch
50 const ids = itemsData.items.map((i: any) => i.contentDetails.videoId).join(',');
51 const detailsUrl = new URL(`${BASE}/videos`);
52 detailsUrl.searchParams.set('part', 'snippet,contentDetails,statistics');
53 detailsUrl.searchParams.set('id', ids);
54 detailsUrl.searchParams.set('key', API_KEY);
55
56 const detailsRes = await fetch(detailsUrl.toString(), { next: { revalidate: 600 } });
57 const detailsData = await detailsRes.json();
58
59 const videos = (detailsData.items || []).map((v: any) => ({
60 id: v.id,
61 title: v.snippet.title,
62 description: v.snippet.description?.slice(0, 200),
63 publishedAt: v.snippet.publishedAt,
64 thumbnail: v.snippet.thumbnails?.maxres?.url || v.snippet.thumbnails?.high?.url,
65 duration: formatDuration(v.contentDetails.duration),
66 views: formatViews(v.statistics.viewCount || '0'),
67 likes: v.statistics.likeCount || '0',
68 embedUrl: `https://www.youtube.com/embed/${v.id}`,
69 }));
70
71 return NextResponse.json({
72 videos,
73 nextPageToken: itemsData.nextPageToken || null,
74 });
75 }
76
77 if (resource === 'channel') {
78 const channelUrl = new URL(`${BASE}/channels`);
79 channelUrl.searchParams.set('part', 'snippet,statistics,brandingSettings');
80 channelUrl.searchParams.set('id', CHANNEL_ID);
81 channelUrl.searchParams.set('key', API_KEY);
82 const res = await fetch(channelUrl.toString(), { next: { revalidate: 3600 } });
83 return NextResponse.json(await res.json());
84 }
85
86 return NextResponse.json({ error: 'Invalid resource' }, { status: 400 });
87 } catch (err: any) {
88 return NextResponse.json({ error: err.message }, { status: 500 });
89 }
90}

Pro tip: The uploads playlist ID for any YouTube channel is the channel ID with 'UC' replaced by 'UU'. For example channel ID UCBcRF18a7Qf58cVbTVChMuA becomes uploads playlist UUBcRF18a7Qf58cVbTVChMuA. This avoids the expensive search.list API call.

Expected result: The API route returns a list of normalized video objects with formatted view counts, durations, thumbnail URLs, and embed URLs.

3

Add Vercel Environment Variables

Navigate to your Vercel project → Settings → Environment Variables. Add two variables that your YouTube API route needs. Add `YOUTUBE_API_KEY` — this is the API key you created in Google Cloud Console. Set it as a server-only variable (no `NEXT_PUBLIC_` prefix). While YouTube API keys are less sensitive than database credentials, keeping them server-side prevents malicious users from discovering your key and burning through your quota. Add `YOUTUBE_CHANNEL_ID` — your YouTube channel ID. Find this in YouTube Studio → Settings → Channel → Advanced settings, or look at your channel's URL on YouTube. Channel IDs start with 'UC' followed by 22 characters (e.g., `UCBcRF18a7Qf58cVbTVChMuA`). This is not sensitive information but storing it as an environment variable makes it easy to change without redeploying code. For public-facing video galleries where the same channel is always shown, these two variables are all you need. If you're building a multi-creator platform where different channels are shown to different users, store channel IDs in your database and pass them as query parameters rather than as environment variables. After saving, redeploy your application. Test the API route by visiting `/api/youtube?resource=videos` in your browser — you should see a JSON array of your channel's latest videos. If you're building for multiple environments (Production, Preview, Development), you can use the same API key for all environments since YouTube API quota is shared. For high-traffic production apps, consider creating separate API keys per environment so quota issues in one environment don't affect others.

Pro tip: Monitor your YouTube API quota usage in Google Cloud Console → APIs & Services → YouTube Data API v3 → Metrics. If you're approaching the 10,000 unit daily limit, increase your cache duration from 600 seconds to 3600 seconds.

Expected result: YOUTUBE_API_KEY and YOUTUBE_CHANNEL_ID set in Vercel, with /api/youtube?resource=videos returning your channel's actual videos.

4

Render the Video Gallery and Embed Player

Connect the V0-generated video gallery components to your YouTube API route and implement the video player. The gallery fetches from `/api/youtube?resource=videos` and maps the response to your thumbnail card components. The video player uses YouTube's iframe embed. For the thumbnail grid, use Next.js's `<Image>` component for YouTube thumbnails — configure `i.ytimg.com` in your next.config.js image domains. YouTube thumbnail URLs follow the pattern `https://i.ytimg.com/vi/{videoId}/maxresdefault.jpg` for max resolution. Always include a fallback since not all videos have maxres thumbnails — check `thumbnail` from the API which already selects the best available quality. For video playback, use an iframe with the `embedUrl` from the API response. Add these parameters to the embed URL for a better experience: `?autoplay=1` when opening from a click, `&rel=0` to hide related videos from other channels, `&modestbranding=1` to minimize YouTube branding. The iframe should have `allow='autoplay; encrypted-media'` and `allowFullScreen` attributes. For the load more / pagination pattern, store the `nextPageToken` from the API response and append it to the next fetch request. Show a 'Load More Videos' button when `nextPageToken` is non-null. Append new videos to the existing array rather than replacing it. Implement a simple video selection state: when a user clicks a thumbnail, set `selectedVideoId` in component state and show the iframe embed for that video. Either in a modal or inline player replacing the clicked card. Keep the video playing by only changing the `src` of the existing iframe rather than unmounting and remounting it.

V0 Prompt

Connect the video gallery to fetch from /api/youtube?resource=videos on load. Render thumbnails using the thumbnail URL in an img tag. When a thumbnail is clicked, show a modal with an iframe player using the embedUrl with ?autoplay=1&rel=0 appended. Show the video title, views, and duration in the modal below the player. Add a 'Load More' button at the bottom that fetches the next page using the nextPageToken. Close the modal with an X button or Escape key.

Paste this in V0 chat

components/VideoPlayer.tsx
1// components/VideoPlayer.tsx — YouTube iframe embed
2'use client';
3import { useEffect, useRef } from 'react';
4
5interface VideoPlayerProps {
6 embedUrl: string;
7 title: string;
8 onClose: () => void;
9}
10
11export function VideoPlayer({ embedUrl, title, onClose }: VideoPlayerProps) {
12 const playerUrl = `${embedUrl}?autoplay=1&rel=0&modestbranding=1`;
13
14 useEffect(() => {
15 const handler = (e: KeyboardEvent) => e.key === 'Escape' && onClose();
16 window.addEventListener('keydown', handler);
17 return () => window.removeEventListener('keydown', handler);
18 }, [onClose]);
19
20 return (
21 <div className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4"
22 onClick={onClose}>
23 <div className="w-full max-w-4xl" onClick={e => e.stopPropagation()}>
24 <div className="flex justify-between items-center mb-2">
25 <h2 className="text-white font-semibold truncate">{title}</h2>
26 <button onClick={onClose} className="text-white hover:text-gray-300 text-2xl leading-none">&times;</button>
27 </div>
28 <div className="relative pt-[56.25%]">
29 <iframe
30 className="absolute inset-0 w-full h-full rounded-lg"
31 src={playerUrl}
32 title={title}
33 allow="autoplay; encrypted-media; picture-in-picture"
34 allowFullScreen
35 />
36 </div>
37 </div>
38 </div>
39 );
40}

Pro tip: Use the CSS padding-top trick (padding-top: 56.25% on a relative container with absolute-positioned iframe) to maintain a 16:9 aspect ratio for the video player across all screen sizes.

Expected result: The video gallery shows real YouTube thumbnails in a responsive grid. Clicking a thumbnail opens the video in a modal player with autoplay. Pagination works with the Load More button.

Common use cases

Creator Portfolio Video Gallery

Build a custom video gallery page showing your YouTube channel's latest uploads in a branded grid layout. V0 generates the responsive thumbnail grid with custom styling that matches your website's design, while the API route fetches the most recent videos from your channel.

V0 Prompt

Create a video gallery page with a responsive grid of video cards (3 columns on desktop, 2 on tablet, 1 on mobile). Each card: YouTube thumbnail image with a play button overlay on hover, video title, view count (formatted as '1.2M views'), upload date (formatted as '3 days ago'), and video duration badge in the bottom-right of the thumbnail. Clicking a card opens a modal with an embedded YouTube player.

Copy this prompt to try it in V0

Course or Tutorial Video Library

Build a structured video library using YouTube playlists as course modules. Each playlist becomes a course section with ordered video lessons. Students can track which videos they've watched (stored locally), and the interface shows a playlist sidebar and embedded player.

V0 Prompt

Build a video course player with a left sidebar showing playlist videos as a numbered list with title, duration, and a checkmark for watched videos. Main area: embedded YouTube player at 16:9 ratio, video title as heading, description expanded/collapsed, view count and publish date. 'Next Video' button below the player that advances to the next playlist item.

Copy this prompt to try it in V0

YouTube Channel Analytics Dashboard

Create an internal dashboard displaying channel statistics — total subscribers, total views, view trends, and top-performing videos. The API route fetches channel statistics and video performance data for a visual analytics overview.

V0 Prompt

Create a YouTube analytics dashboard with: top stats row showing subscriber count, total views, total videos, and average views per video in metric cards. Below: a sortable table of videos with columns for title, thumbnail, view count, like count, comment count, and publish date. Add a bar chart showing view counts for the top 10 videos. Make the table sortable by any column.

Copy this prompt to try it in V0

Troubleshooting

API returns 403 with 'The request cannot be completed because you have exceeded your quota'

Cause: You've used all 10,000 daily quota units. This often happens during development when the API is called on every page refresh without caching.

Solution: Add Next.js fetch caching to your API route with `next: { revalidate: 3600 }` for channel data and `next: { revalidate: 600 }` for video lists. Also check if you're accidentally calling `search.list` (100 units each) instead of `playlistItems.list` (1 unit each) for your own channel's videos.

typescript
1// Cache video list for 10 minutes
2const res = await fetch(url, { next: { revalidate: 600 } });
3// Cache channel info for 1 hour
4const channelRes = await fetch(channelUrl, { next: { revalidate: 3600 } });

API returns 400 with 'API key not valid. Please pass a valid API key'

Cause: The YOUTUBE_API_KEY environment variable is missing, has extra whitespace, or the key has domain restrictions that don't match the server-side request origin.

Solution: Verify the API key in Vercel Dashboard → Settings → Environment Variables. Check that the API key restrictions in Google Cloud Console allow server-side requests — remove HTTP referrer restrictions if the key is used server-side only (server-to-server calls don't send a Referer header, so referrer restrictions will block them).

YouTube thumbnails fail to load with Next.js Image component

Cause: i.ytimg.com is not in the Next.js allowed image domains configuration.

Solution: Add YouTube's thumbnail CDN domain to your next.config.js image configuration.

typescript
1// next.config.js
2module.exports = {
3 images: {
4 remotePatterns: [
5 { protocol: 'https', hostname: 'i.ytimg.com' },
6 { protocol: 'https', hostname: 'yt3.ggpht.com' }, // channel avatars
7 ],
8 },
9};

Videos load but duration shows 'P0D' or 'PT0S' for live streams

Cause: YouTube live streams and live stream replays return different duration formats — active live streams show 'P0D0T0S' while some replays don't have accurate duration data.

Solution: Add a check for live stream content type and display 'Live' or 'Live Replay' instead of a duration for these videos.

typescript
1function formatDuration(iso: string, liveStatus?: string): string {
2 if (liveStatus === 'live') return '🔴 LIVE';
3 if (!iso || iso === 'P0D') return 'Live Replay';
4 // ... rest of formatting
5}

Best practices

  • Use `playlistItems.list` instead of `search.list` for your own channel's videos — it uses 1 quota unit vs 100, making it 100x more efficient
  • Cache YouTube API responses aggressively — video metadata doesn't change minute to minute; 10-60 minute caches are appropriate
  • Never use `NEXT_PUBLIC_` prefix for your YouTube API key — proxy all YouTube API calls through server-side Next.js API routes
  • Batch video detail requests using comma-separated IDs in a single `videos.list` call rather than making one request per video
  • Always provide a fallback thumbnail URL — not all videos have maxresdefault quality, so check for high quality thumbnail as a fallback
  • Add YouTube's image domain to next.config.js for proper Next.js Image optimization of thumbnails
  • Monitor your daily quota in Google Cloud Console — approaching the 10,000 unit limit triggers 403 errors that are confusing to debug in production

Alternatives

Frequently asked questions

Is the YouTube Data API v3 free to use?

Yes, the YouTube Data API v3 is free with a default quota of 10,000 units per day. Most video gallery use cases stay well within this limit — fetching 24 videos costs 2-3 units. If your app needs more quota, you can request an increase through Google Cloud Console → APIs & Services → YouTube Data API v3 → Quotas → Request an increase. Increases are subject to review and approval by Google.

Can I use the YouTube API to play videos on my website?

Yes, but YouTube videos are played using the YouTube iframe embed player, not streamed directly through the API. The YouTube Data API provides video metadata (title, thumbnail, statistics) and your app embeds videos using the standard YouTube embed URL: `https://www.youtube.com/embed/{videoId}`. You cannot download or re-host YouTube videos — the iframe embed is the only authorized playback method.

How do I find my YouTube channel ID?

Go to YouTube Studio (studio.youtube.com) → Settings → Channel → Advanced settings. Your channel ID starts with 'UC' and is shown there. Alternatively, go to your YouTube channel page, click 'More' → 'Share channel' → 'Copy channel ID'. If your channel has a custom URL (e.g., youtube.com/@username), the actual channel ID is different from the username — you need the 'UC...' format for the API.

How many videos can I fetch per API call?

The YouTube API returns a maximum of 50 items per request (`maxResults=50`). For channels with more than 50 videos, use the `nextPageToken` returned in the response to fetch the next page. Build a 'Load More' button in your UI that appends the next page to the existing video list, or implement infinite scroll that triggers the next fetch when the user scrolls near the bottom.

Can I display videos from multiple YouTube channels?

Yes. Update your API route to accept a `channelId` query parameter instead of reading from a fixed environment variable. Each API call can query a different channel. If you're building a video aggregator or multi-channel platform, store the channel IDs you want to feature in your database or a configuration file and query them dynamically.

How do I handle private or age-restricted videos in the API response?

Private videos won't appear in the public API response for your channel's uploads playlist — they're only visible to authenticated users. Age-restricted videos appear in the list but the thumbnail embed may show a verification screen when embedded. Filter out age-restricted content by checking the `contentDetails.contentRating` field, or just link to YouTube rather than embedding for those videos.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.