NextJS 14 시작하기 – 노마드 코더 Nomad Coders
NextJS 14 For Beginners
nomadcoders.co
TMDB(The Movie Database)란?
영화, TV 프로그램 관련 데이터를 수집하여 API 형태로 제공해주는 사이트이다. 회원가입만 하면 쉽게 API 키를 발급받을 수 있다.
The Movie Database (TMDB)
환영합니다 수백만 개의 영화, TV 프로그램 및 인물을 발견하세요. 지금 살펴보세요.
www.themoviedb.org
데이터를 페칭하는 데에는 아래 공식 문서를 참고할 수 있다.
Getting Started
Welcome to version 3 of The Movie Database (TMDB) API. This is where you will find the definitive list of currently available methods for our movie, tv, actor and image API.
developer.themoviedb.org
TMDB에서 영화 정보를 받아오기
// utilities.tsx
export const API_KEY = // 자신의 API 키
export const BASE_PATH = "https://api.themoviedb.org/3";
// 최근에 상영중인 영화 정보를 한국어로 받아온다.
export function getMovies() {
return fetch(`${BASE_PATH}/movie/now_playing?api_key=${API_KEY}&language=ko-KR`)
.then((response) => response.json())
.then((data) => data.results);
}
// 장르 별 영화 데이터
export function getMoviesByGenre(genreId) {
return fetch(`${BASE_PATH}/discover/movie?api_key=${API_KEY}&with_genres=${genreId}&language=ko-KR`)
.then((response) => response.json())
.then((data) => data.results);
}
// TV 프로그램 데이터
export function getTvShows() {
return fetch(`${BASE_PATH}/tv/popular?api_key=${API_KEY}&language=ko-KR`)
.then((response) => response.json())
.then((data) => data.results);
}
받아온 정보에 동영상이나 이미지 정보는 포함되어 있지 않은데, 포스터 등의 이미지나 트레일러를 불러오기 위해서 아래와 같이 함수를 작성한다.
// utilities.tsx
export function makeImagePath(id: string, format?: string) {
return `https://image.tmdb.org/t/p/${format ? format : "original"}/${id}`;
}
export function makeVideoPath(id: string) {
return `http://api.themoviedb.org/3/movie/${id}/videos`;
}
홈 화면
// (home)/page.tsx
export default async function HomePage() {
const latestMovies = (await getMovies()).slice(0, 6);
const MusicMovies = (await getMoviesByGenre(10402)).slice(0, 6);
const RomanceMovies = (await getMoviesByGenre(10749)).slice(0, 6);
const DramaMovies = (await getMoviesByGenre(18)).slice(0, 6);
return (
<>
<MovieSet
title="최신 영화 둘러보기"
movies={latestMovies}
/>
<MovieSet
title="신나는 음악 영화"
movies={MusicMovies}
/>
<MovieSet
title="설레는 로맨스 영화"
movies={RomanceMovies}
/>
<MovieSet
title="박진감 넘치는 드라마"
movies={DramaMovies}
/>
</>
);
}
MovieSet 함수를 별도로 분리하여 장르별 영화 목록을 불러올 수 있도록 하였다.
// components/movie-set.tsx
interface MovieProps {
id: string;
poster_path: string;
title: string;
}
interface MovieSetProps {
title: string;
movies: MovieProps[];
}
export const MovieSet = ({ title, movies }: MovieSetProps) => {
return (
<div className={styles.movieSet}>
<div className={styles.title}>{title}</div>
<div className={styles.container}>
{movies.map((movie) => (
<Movie
key={movie.id}
id={movie.id}
poster_path={makeImagePath(movie?.poster_path || "")}
title={movie.title}
/>
))}
</div>
</div>
);
};
home page에서 영화 정보를 받아온 후 MovieSet 함수로 넘기면 받아온 데이터를 map하여 포스터와 타이틀을 화면에 보여준다.
영화 상세 페이지
영화 포스터 이미지를 클릭하면 상세 페이지로 이동한다. 상세 페이지에서 줄거리, 별점 등 영화 상세 내용과 출연진, 트레일러를 볼 수 있다. 하단에는 클릭한 영화와 비슷한 영화 목록을 보여준다.
// components/movie-info.tsx
export default async function MovieInfo({ id }: { id: string }) {
const movie = await getMovie(id);
const casts = await getCast(id);
return (
<div>
<div className={styles.container}>
<img
src={makeImagePath(movie?.poster_path || "")}
className={styles.poster}
alt={movie.title}
/>
<div className={styles.info}>
<h1 className={styles.title}>{movie.title}</h1>
<p>
{movie.release_date.substring(0, 4)} · ⭐{movie.vote_average.toFixed(1)} · {movie.runtime} minutes
</p>
<p className={styles.overview}> {movie.overview}</p>
<div className={styles.imgList}>
<img
className={styles.flag}
src={`https://flagsapi.com/${movie.origin_country[0]}/flat/32.png`}
/>
<img
className={styles.production}
src={makeImagePath(movie.production_companies[0].logo_path || "")}
/>
</div>
<a
href={`https://www.themoviedb.org/movie/${id}`}
target={"_blank"}
>
Show more →
</a>
</div>
</div>
<div className={styles.cast}>
{casts.map((cast) => (
<div
key={cast.cast_id}
className={styles.profile}
>
<img
src={makeImagePath(cast.profile_path || "")}
alt={cast.name}
/>
{cast.name}
<p className={styles.name}>{cast.character}</p>
</div>
))}
</div>
</div>
);
}
이 때 영화의 overview가 너무 길면 화면이 길어지는 문제가 있었다. 이를 해결하기 위해서 module.css 파일에서 15 라인을 넘으면 '...'와 같은 말줄임표로 나타내도록 처리하였다.
-webkit-line-clamp는 특정 라인 수를 넘으면 나머지를 말줄임표로 표시한다. 이 때 -webkit-box-orient 값은 vertical로 설정되어 있어야 한다. 또 overflow를 hidden으로 설정해야 나머지 글자가 제대로 생략된다.
.overview {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 15;
overflow: hidden;
text-overflow: ellipsis;
}
// components/movie-video.tsx
useEffect(() => {
const fetchTrailer = async () => {
try {
const response = await axios.get(`https://api.themoviedb.org/3/movie/${movieId}/videos`, {
params: {
api_key: API_KEY,
language: "ko-KR",
},
});
const trailers = response.data.results;
if (trailers.length > 0) {
const trailer = trailers.find((trailer: any) => trailer.type === "Trailer");
if (trailer) {
setTrailerUrl(`https://www.youtube.com/embed/${trailer.key}`);
}
}
} catch (error) {
console.error("Failed to fetch trailer", error);
}
};
영화, TV 프로그램 탭
검색 페이지
// utilities.tsx
export async function searchMovies(query) {
const response = await fetch(
`${BASE_PATH}/search/movie?api_key=${API_KEY}&query=${encodeURIComponent(query)}&language=ko-KR`
);
const json = await response.json();
return json.results || [];
}
'넥스트' 카테고리의 다른 글
[Next.js] react-slick 라이브러리를 이용한 이미지 슬라이더 (0) | 2025.01.20 |
---|