본문 바로가기
Frontend/Next.js

[Next.js] Image를 사용한 콘텐츠 리스트 구성

by 오이가지아빠 2021. 5. 23.

이전글 - [Next.js] API 호출을 통한 네비게이션 메뉴 생성

#1. 네비게이션 메뉴에 클릭이벤트 작성하기

네비게이션 메뉴로 만들었던 각 영화의 장르를 클릭하면 해당 장르의 영화들을 썸네일과 함께 보여주도록 만들어 봅시다.

 

먼저 next/router 를 임포트하고 Nav onclick 이벤트에 라우팅을 등록해줍니다.

import { useRouter } from "next/router"
import useSWR from 'swr'

const BASE_URL = 'https://api.themoviedb.org/3/genre/movie/list';
const API_KEY = process.env.API_KEY;

const fetcher = async (url) => {
    const res = await fetch(url)
    const data = await res.json()
  
    if (res.status !== 200) {
      throw new Error(data.message)
    }
    return data
}

function Nav() {
    const router = useRouter();

    const { data, error } = useSWR(
        () => `${BASE_URL}?api_key=${API_KEY}&language=ko`, fetcher
    )
    if (error) return <div>{error.message}</div>
    if (!data) return <div>Loading...</div>

    return (
        <nav className="relative">
            <div className="flex px-10 sm:px-20 text-2xl whitespace-nowrap
            space-x-10 sm:space-x-20">
                {data.genres.map((menu, index) => index < 5 && (
                    <h2 key={menu.id}
                        onClick={
                            () => router.push(`/?menu=${menu.id}`, '/')
                        }
                        className="last:pr-24 cursor-pointer transition 
                        duration-100 transform hover:scale-125 hover:text-white 
                        active:text-red-500"
                    >{menu.name}
                    </h2>
                ))}
            </div>
        </nav>
    )
}

export default Nav

router.push의 사용법은 다음과 같습니다.

router.push(url, as, options)

url 뒤에 붙는 as 파라미터를 통해 주소표시줄을 변경할 수 있습니다. 생략하게 되면 localhost:3000/?menu=3 이런식으로 표현되지만 예제체럼 '/' 로 옵션을 주면 실제 주소표시줄에서 나타나게 되는 주소는 변함없이 localhost:3000/ 로 보여지게 됩니다.

 

이제 index.js에서 getServerSideProps를 등록하여 router를 처리해봅시다.

 

import Head from 'next/head'
import Header from '../components/Header'
import Nav from '../components/Nav'

export default function Home({ contents }) {
  return (
    <div className=''>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      {/* 헤더 */}
      <Header />

      {/* 메뉴 */}
      <Nav />

      {/* 컨텐츠 */}

    </div>
  )
}

export async function getServerSideProps(context) {

  const menu = context.query.menu;

  const BASE_URL = 'https://api.themoviedb.org';
  const API_KEY = process.env.API_KEY;

  const res = await fetch(`${BASE_URL}/3/discover/movie?api_key=${API_KEY}&language=kr&with_genres=${menu}`)
  const data = await res.json()
  return {
      props: {
          contents: data.results
      },
  }
}

index페이지가 렌더링 될 때마다 getServerSideProps를 통해 API request를 호출합니다.

사용할 API는 themoviedb API에서 특정 장르의 영화목록을 가져오는 

https://api.themoviedb.org/3/discover/movie?api_key=${API_KEY}&language=kr&with_genres=${menu} 를 사용합니다.

 

장르 메뉴를 클릭하게 되면, localhost:3000/?menu=123 로 라우팅이 되는데, 이를 getServerSideProps(context) 에서

context.query.menu 로 123 값을 가져 올 수 있습니다. 저 menu id는 장르의 id이기 때문에 해당 API에 파라미터로 넣어서 호출하게 되면 각 장르에 맞는 영화 목록을 얻을 수 있습니다.

 

영화목록 API를 호출한 결과에는 각 영화의 제목, 릴리즈 일자, 포스터 이미지 등등의 영화정보가 들어 있습니다.

이를 사용해서 영화 포스터 목록과 영화정보를 보여주도록 해봅시다.

 

 

#2. 영화 이미지 가져오기

API에서 backdrop_path(배경), poster_path(포스터) 를 가져 왔고 이를 https://image.tmdb.org/t/p/original/ 뒤에 붙여서 호출하게 되면 해당 이미지를 얻을 수 있습니다.

 

components/Contents.js 파일을 만들고 아래와 같이 작성합니다.

import Image from "next/image"

function Contents({ contents }) {

    const BASE_URL = "https://image.tmdb.org/t/p/original/";

    return (
        <div>
            This is contents
            
            {contents.map(result => (
                <Image
                layout="responsive"
                src={
                    `${BASE_URL}${result.backdrop_path || result.poster_path}`
                }
                height={360}
                width={640}
            />
            ))}

        </div>
    )
}

export default Contents

또, 외부 이미지 최적화를 위해 next.config.js 파일에도 다음과 같이 이미지 호출을 위한 외부 도메인 주소를 작성합니다.

// next.config.js
module.exports = {
    env: {
        API_KEY: `7cxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
    }, 
    images: {
        domains: ["image.tmdb.org"]
    }
};

이제 index.js 에서 작성한 Contents 파일을 임포트 해줍니다.  getServerSideProps에서 return 된 영화목록정보를 contents에 담아 contents에 넘겨줍니다.

export default function Home({ contents }) {
  return (
    <div className=''>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      {/* 헤더 */}
      <Header />

      {/* 메뉴 */}
      <Nav />

      {/* 컨텐츠 */}
      <Contents contents={contents}/>

    </div>
  )
}

자 이제 서버를 실행해서 중간 결과를 확인해 봅시다.

#3. 카드 썸네일 만들기

영화 이미지와 함께 영화의 정보를 카드 썸네일 형식으로 배열해서 UI를 변경해봅시다.

 

components/Thumnail.js 파일을 만들고 Contents에서 호출했던 이미지 정보를 Thumnail파일로 옮기고 영화의 제목을 함께 보여주고, 썸네일에 마우스를 올리면 개봉일, 좋아요 수 등의 정보를 함께 보여줍시다.

import Thumbnail from "./Thumbnail"

function Contents({ contents }) {

    return (
        <div className="px-5 my-10 sm:grid md:grid-cols-2 xl:grid-cols-3 
        3xl:flex flex-wrap justify-center">
            {contents.map(content => (
                <Thumbnail key={content.id} content={content} />
            ))}
        </div>
    )
}

export default Contents

해상도가 변경되면 한번에 보여주는 카드의 숫자를 유동적으로 변경하기 위해, sm, md, xl 등의 옵션도 함께 지정합니다.

import { ThumbUpIcon } from "@heroicons/react/outline";
import Image from "next/image"

function Thumbnail({ content }) {

    const BASE_URL = "https://image.tmdb.org/t/p/original/";
    
    return (
        <div className="p-2 group cursor-pointer transition duration-200
        ease-in transform sm:hover:scale-105 hover:z-50">
            <Image
                layout="responsive"
                src={
                    `${BASE_URL}${content.backdrop_path || content.poster_path}`
                }
                height={360}
                width={640}
            />

            <div className="p-2">
                <p className="truncate max-w-md">{content.overview}</p>
                <h2 className="mt-1 text-2xl text-white transition-all 
                duration-100 ease-in-out group-hover:font-bold">
                    {content.title || content.original_name}
                </h2>
                <p className="flex items-center opacity-0 group-hover:opacity-100">
                    {content.media_type && `${content.media_type} ㆍ`}{" "}
                    {content.release_date || content.first_air_date}ㆍ{" "}
                    <ThumbUpIcon className="h-5 mx-2" /> {content.vote_count}
                </p>
            </div>
        </div>
    )
}

export default Thumbnail

최종적으로 만들어진 사이트의 모습입니다.

 

메뉴 클릭시 새로운 목록을 보여주며, 각 썸네일에 마우스를 올리면 가벼운 효과와 함께 영화의 상세 정보를 보여주고, 해상도에 따라 카드배열의 숫자로 플렉서블하게 변경되는 사이트가 완성되었습니다.

 

fin.

 

반응형

댓글