Guide
Quick Start
Working YouTube player in your app in under 5 minutes.
Basic usage
The simplest way to add a YouTube player to your app. Just pass a video ID and ytcn handles everything — thumbnail loading, iframe creation, controls, keyboard shortcuts, and fullscreen.
app/page.tsx
import { YtcnPlayer } from "@/components/ytcn/ytcn-player"
export default function Page() {
return (
<div className="max-w-3xl mx-auto p-8">
<YtcnPlayer videoId="dQw4w9WgXcQ" />
</div>
)
}With Next.js App Router
Server Components are safe
YtcnPlayer is already marked with "use client". You can import it from Server Components — Next.js handles the client boundary automatically.
app/videos/[id]/page.tsx
// This is a Server Component — no "use client" directive needed
import { YtcnPlayer } from "@/components/ytcn/ytcn-player"
interface Props {
params: Promise<{ id: string }>
}
export default async function VideoPage({ params }: Props) {
const { id } = await params
return (
<main className="max-w-4xl mx-auto p-8">
<h1 className="text-2xl font-bold mb-4">Video</h1>
<YtcnPlayer videoId={id} />
</main>
)
}Resume from saved position
Use startAt combined with onTimeUpdate to implement resume-from-where-you-left-off. This pattern is perfect for LMS platforms and course builders.
components/resumable-player.tsx
"use client"
import { YtcnPlayer } from "@/components/ytcn/ytcn-player"
import { useRef, useCallback } from "react"
export function ResumablePlayer({ videoId }: { videoId: string }) {
const key = `ytcn-progress-${videoId}`
const saved = typeof window !== "undefined"
? Number(localStorage.getItem(key) ?? 0)
: 0
const lastSave = useRef(0)
const handleTimeUpdate = useCallback(
(current: number) => {
// Throttle saves to every 5 seconds
if (current - lastSave.current > 5) {
localStorage.setItem(key, String(Math.floor(current)))
lastSave.current = current
}
},
[key]
)
const handleEnd = useCallback(() => {
localStorage.removeItem(key)
console.log("Video complete!")
}, [key])
return (
<YtcnPlayer
videoId={videoId}
startAt={saved}
onTimeUpdate={handleTimeUpdate}
onEnd={handleEnd}
/>
)
}Playlist auto-advance
Use the onEnd callback to advance to the next video in a playlist.
components/playlist-player.tsx
"use client"
import { YtcnPlayer } from "@/components/ytcn/ytcn-player"
import { useState } from "react"
const VIDEOS = [
{ id: "dQw4w9WgXcQ", title: "Never Gonna Give You Up" },
{ id: "jNQXAC9IVRw", title: "Me at the zoo" },
{ id: "9bZkp7q19f0", title: "Gangnam Style" },
]
export function PlaylistPlayer() {
const [index, setIndex] = useState(0)
const video = VIDEOS[index]!
return (
<div>
<YtcnPlayer
videoId={video.id}
onEnd={() => {
if (index < VIDEOS.length - 1) {
setIndex((i) => i + 1)
}
}}
/>
<div className="mt-4 space-y-2">
{VIDEOS.map((v, i) => (
<button
key={v.id}
onClick={() => setIndex(i)}
className={`block w-full text-left px-3 py-2 rounded-md text-sm ${
i === index
? "bg-primary/10 text-primary font-medium"
: "text-muted-foreground hover:bg-accent"
}`}
>
{v.title}
</button>
))}
</div>
</div>
)
}Headless — custom UI
If the default controls don't match your design, use the useYtcnPlayer hook to build a completely custom UI. See the Headless Usage guide for full examples.