06 · Developer Guide · 레시피
유튜브 영상 카드 + 모달Sermon Card
유튜브 영상을 사이트 안에서 라이트박스(전체 화면 팝업)로 재생합니다.
썸네일 자동 교체 + 영상 자동 재생 + ESC 키로 닫기까지 한 컴포넌트에서 처리합니다.
언제 쓰는가
- 설교·찬양·예배 영상 카드 (주일·새벽·수요·청년 예배)
- 외부 유튜브 사이트로 이탈하지 않고 사이트 안에서 시청
- 한 페이지에 여러 영상이 카드 그리드로 노출되는 경우
전체 코드
components/preview/SermonCard.tsxtsx
"use client";
import { useEffect, useState } from "react";
import { Play, X } from "lucide-react";
export type Sermon = {
title: string;
pastor: string;
bible?: string;
date: string;
youtubeId: string;
};
export function SermonCard({ sermon }: { sermon: Sermon }) {
const [open, setOpen] = useState(false);
// YouTube 썸네일 — 고화질(maxresdefault) 우선, 로드 실패 시 저화질(hqdefault)로 자동 교체
const thumb = `https://i.ytimg.com/vi/${sermon.youtubeId}/maxresdefault.jpg`;
const fallback = `https://i.ytimg.com/vi/${sermon.youtubeId}/hqdefault.jpg`;
useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpen(false);
};
document.body.style.overflow = "hidden";
document.addEventListener("keydown", onKey);
return () => {
document.body.style.overflow = "";
document.removeEventListener("keydown", onKey);
};
}, [open]);
return (
<>
<button type="button" onClick={() => setOpen(true)} className="group block w-full text-left">
<div className="relative aspect-[16/9] overflow-hidden rounded-lg bg-black">
<img
src={thumb}
alt={sermon.title}
className="h-full w-full object-cover"
onError={(e) => {
const img = e.currentTarget as HTMLImageElement;
if (img.src !== fallback) img.src = fallback;
}}
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/30">
<span className="flex h-14 w-14 items-center justify-center rounded-full bg-white/90">
<Play size={22} strokeWidth={2.2} />
</span>
</div>
</div>
<p className="mt-3 text-[14.5px] font-semibold">{sermon.title}</p>
<p className="mt-1 text-[12px] text-[color:var(--color-text-muted)]">
{sermon.date} · {sermon.bible && `${sermon.bible} · `}{sermon.pastor}
</p>
</button>
{open && (
<div
role="dialog"
aria-modal="true"
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/90 px-4 py-6"
onClick={() => setOpen(false)}
>
<button onClick={() => setOpen(false)} aria-label="닫기" className="absolute right-5 top-5">
<X size={20} />
</button>
<div className="aspect-video w-full max-w-[1180px]" onClick={(e) => e.stopPropagation()}>
<iframe
src={`https://www.youtube.com/embed/${sermon.youtubeId}?autoplay=1&rel=0`}
title={sermon.title}
allow="autoplay; encrypted-media; picture-in-picture; fullscreen"
allowFullScreen
className="h-full w-full"
/>
</div>
</div>
)}
</>
);
}사용 예
tsx
<SermonCard
sermon={{
title: "시간은 다시 돌아오지 않습니다",
pastor: "김화수 담임목사",
bible: "사도행전 9:1~9",
date: "2026.05.10",
youtubeId: "i6yHKs5bcfM",
}}
/>