07. Home.jsx — 메인 페이지 완성하기
요약
Gemini CLI로 구현하기 — Home 메인 페이지
npx gemini-
1src/components/Home.jsx를 작성해줘. useOutletContext로 now, popular, topRated, loading 데이터를 받아야 해. 상단에는 배경 영상([영상 파일명])과 인기영화 첫 번째 항목을 히어로로 표시하고, 하단에는 현재 상영작/인기 영화/최고 평점 3개의 Section 컴포넌트를 보여줘. 로딩 중에는 Spinner를 표시해줘.
- 사용 가이드:
[영상 파일명]을public폴더에 넣은 동영상 파일 이름으로 바꾼다(예:video.mp4).
1. Home 페이지의 구조
GOFLEX의 메인 페이지는 두 가지 영역으로 나뉜다.
- 히어로(Hero) 영역 — 배경 영상 + 인기 영화 하이라이트 + “자세히 보기” 버튼
- 영화 목록 영역 — 현재 상영작, 인기 영화, 최고 평점 3개 섹션
| 순서 | 만들 기능 | 핵심 개념 |
|---|---|---|
| 1단계 | import + 데이터 받기 | useOutletContext |
| 2단계 | 히어로 영역 | 비디오 배경, 조건부 렌더링 |
| 3단계 | 영화 목록 영역 | Section 재사용, loading 처리 |
2. 1단계 — import + 데이터 받기
src/components/Home.jsx 파일을 열고 임시 코드를 모두 지운 뒤 아래를 작성한다.
1import { useOutletContext, Link } from "react-router";2import { Section } from "./Section.jsx";3import { Spinner } from "./UI.jsx";4
5export function Home() {6 const { now, popular, topRated, loading } = useOutletContext();7
8 // 인기영화 첫 번째를 히어로에 표시9 const hero = popular.length > 0 ? popular[0] : null;| 줄 | 설명 |
|---|---|
| 1 | useOutletContext(유즈아울렛컨텍스트)로 5편에서 App.jsx가 Outlet의 context prop으로 전달한 데이터를 받는다. Link는 히어로의 “자세히 보기” 버튼에 사용한다. |
| 3 | 4편에서 만든 Spinner 컴포넌트를 가져온다. 데이터가 아직 도착하지 않았을 때 “불러오는 중…” 메시지를 표시한다. |
| 6 | 객체 구조 분해(Destructuring, 디스트럭처링) — 객체에서 필요한 프로퍼티를 개별 변수로 추출하는 문법이다. useOutletContext()가 반환한 객체에서 now, popular, topRated, loading을 각각 변수로 꺼낸다. |
| 8 | 삼항 연산자(조건 ? 참 : 거짓)이다. popular 배열에 요소가 있으면 첫 번째 요소(popular[0])를 hero에 저장하고, 없으면 null을 저장한다. |
3. 2단계 — 히어로 영역
같은 Home.jsx 파일에서, 1단계의 hero 변수 선언 바로 아래에 return을 작성한다. 히어로 영역은 세 겹의 레이어(배경 영상 → 오버레이 → 텍스트)로 구성된다. position: absolute 요소를 겹쳐서 레이어를 구성하는 CSS 포지셔닝 기법이다.
- 비디오
- 히어로 텍스트 + 인기영화
1 return (2 <>3 <section className="relative h-screen overflow-hidden">4 <video src="video.mp4" className="absolute top-0 left-0 w-full h-full object-cover" autoPlay muted loop playsInline />5 <div className="absolute bg-black/50 w-full h-full top-0 left-0"></div>6 {/* 다음탭으로 */}| 줄 | 설명 |
|---|---|
| 3 | relative — CSS position: relative이다. 자식 요소 중 position: absolute인 요소의 기준 컨테이너가 된다. h-screen은 뷰포트 전체 높이를 차지한다. overflow-hidden은 자식 요소가 영역을 벗어나면 잘라낸다. |
| 4 | absolute top-0 left-0 — CSS position: absolute이다. 가장 가까운 relative 부모를 기준으로 좌상단에 위치한다. object-cover는 가로세로 비율을 유지하면서 컨테이너를 꽉 채운다. autoPlay muted loop playsInline은 자동 재생, 음소거, 반복, 모바일 인라인 재생 속성이다. |
| 5 | bg-black/50 — 50% 투명한 검정 막을 영상 위에 덮는다. 글자가 잘 보이도록 배경을 어둡게 만든다. |
1 <div className="relative container mx-auto flex flex-col justify-center items-center h-full text-center px-6">2 <h2 className="text-5xl md:text-7xl lg:text-9xl font-bold text-yellow-400">GOFLEX</h2>3 <p className="text-xl md:text-2xl text-white mt-4">최신 영화와 인기 작품을 만나보세요.</p>4 {hero && (5 <div className="mt-8 flex flex-col items-center gap-3">6 <p className="text-gray-300 text-lg">지금 인기 있는 영화</p>7 <h3 className="text-3xl md:text-4xl font-bold text-white">{hero.title}</h3>8 <Link to={`/movie/${hero.id}`} className="mt-2 bg-yellow-400 text-black px-8 py-3 rounded-lg font-bold hover:bg-yellow-300 transition-colors">9 자세히 보기10 </Link>11 </div>12 )}13 </div>14 </section>| 줄 | 설명 |
|---|---|
| 1 | relative — 내부 absolute 요소의 기준 컨테이너이다. container mx-auto — 최대 너비 제한 + 가로 중앙 배치이다. flex flex-col — 자식 요소를 세로 방향으로 배치한다. justify-center items-center — 세로·가로 모두 가운데 정렬한다. |
| 2 | text-5xl md:text-7xl lg:text-9xl — 반응형 글꼴 크기이다. md:는 768px 이상, lg:는 1024px 이상에서 적용된다. |
| 4-12 | hero && (...) — 논리 AND 단축 평가(Short-circuit evaluation)이다. && 연산자는 왼쪽이 null, undefined, false 등 거짓 값이면 오른쪽을 평가하지 않고 건너뛴다. hero에 값이 있을 때만 인기 영화 정보가 렌더링된다. |
| 8 | Link to={...} — 클릭 시 /movie/{hero.id} 경로로 이동한다. hover:bg-yellow-300 — 마우스 오버 시 배경색이 밝아지는 CSS 가상 클래스이다. |
4. 3단계 — 영화 목록 영역
같은 Home.jsx 파일에서, 히어로 </section> 태그 바로 아래에 이어서 작성한다. loading 상태에 따라 스피너 또는 영화 목록을 조건부 렌더링한다.
1 {loading && <Spinner className="text-center py-20 bg-black" />}2
3 {!loading && (4 <>5 <Section title="현재 상영작" items={now} category="now_playing" />6 <Section title="인기 영화" items={popular} category="popular" />7 <Section title="최고 평점" items={topRated} category="top_rated" />8 </>9 )}10 </>11 );12}| 줄 | 설명 |
|---|---|
| 1 | loading이 true이면 Spinner 컴포넌트로 “불러오는 중…” 텍스트를 표시한다. |
| 3-8 | !(NOT 연산자)는 불린 값을 반전시킨다. loading이 true이면 !loading은 false, loading이 false이면 !loading은 true가 된다. 로딩이 끝났을 때(false) 3개의 Section을 표시한다. category prop은 “더보기” 링크에 사용된다. |
| 5 | category="now_playing"은 Section에서 /category/now_playing 링크를 생성한다. |
5. 동작 확인
| 확인 항목 | 기대 결과 |
|---|---|
| 히어로 영역 | 배경 영상 위에 “GOFLEX” + 인기 영화 제목 + “자세히 보기” 버튼 |
| ”자세히 보기” 클릭 | 해당 영화 상세 페이지로 이동 |
| 스크롤 아래 | ”현재 상영작”, “인기 영화”, “최고 평점” 섹션 표시 |
주의
아직 Section과 Card 컴포넌트를 만들지 않았으므로 에러가 발생할 수 있다. 바로 다음 편에서 만든다.
6. 전체 코드
1import { useOutletContext, Link } from "react-router";2import { Section } from "./Section.jsx";3import { Spinner } from "./UI.jsx";4
5export function Home() {6 const { now, popular, topRated, loading } = useOutletContext();7
8 // 인기영화 첫 번째를 히어로에 표시9 const hero = popular.length > 0 ? popular[0] : null;10
11 return (12 <>13 {/* 상단 비디오 영역 */}14 <section className="relative h-screen overflow-hidden">15 <video src="video.mp4" className="absolute top-0 left-0 w-full h-full object-cover" autoPlay muted loop playsInline />16 <div className="absolute bg-black/50 w-full h-full top-0 left-0"></div>17 <div className="relative container mx-auto flex flex-col justify-center items-center h-full text-center px-6">18 <h2 className="text-5xl md:text-7xl lg:text-9xl font-bold text-yellow-400">GOFLEX</h2>19 <p className="text-xl md:text-2xl text-white mt-4">최신 영화와 인기 작품을 만나보세요.</p>20 {hero && (21 <div className="mt-8 flex flex-col items-center gap-3">22 <p className="text-gray-300 text-lg">지금 인기 있는 영화</p>23 <h3 className="text-3xl md:text-4xl font-bold text-white">{hero.title}</h3>24 <Link to={`/movie/${hero.id}`} className="mt-2 bg-yellow-400 text-black px-8 py-3 rounded-lg font-bold hover:bg-yellow-300 transition-colors">25 자세히 보기26 </Link>27 </div>28 )}29 </div>30 </section>31
32 {/* 영화 목록 */}33 {loading && <Spinner className="text-center py-20 bg-black" />}34
35 {!loading && (36 <>37 <Section title="현재 상영작" items={now} category="now_playing" />38 <Section title="인기 TV" items={popular} category="popular" />39 <Section title="최고 평점" items={topRated} category="top_rated" />40 </>41 )}42 </>43 );44}