티스토리 뷰

create-react-app 에서는 router를 react-router-dom 으로 처리하고

url parameter 를 "movies/:id" 와 같이 해서 movies/123 movies/124 를 넣어 dynamic url을 만들었습니다.

 

하지만 Next js 에서는 router를 기본적으로 지원을 해주고 그 속에서 dynamic routes를 구현할 수 있습니다.

 

Routes

간단한 라우팅의 예시로

/movies/all 페이지를 만들고 싶다고 하면,

pages 폴더에 movies 폴더를 만들고 all.js 페이지 파일을 생성해주어 만들면 됩니다.

폴더의 트리 구조로 url이 만들어집니다.

 

// movies / all.js
export default function ALL() {
  return "all";
}

/movies/all 페이지에 접속하게 되면 다음과 같이 "all" 을 보여줍니다.

 

 

Nested Routing

 

그럼 /movies 페이지에 접속하게 되면 어떻게 할까요?

이또한 index.js 를 만들었던것처럼 만들어주면 됩니다.

pages 폴더에 movies 폴더에 index.js 파일을 생성해주어 페이지를 만들면

자동적으로 /movies 페이지에 접근하면 Next js는 index.js 파일을 읽습니다.

 

// movies / index.js
export default function Home() {
  return "movies index";
}

이것을 Nested Routing , 즉 중첩 라우팅이라고 말합니다.

라우팅의 구조를 알아봤으니 테스트했던 movies/index.js 와 movies/all.js 는 삭제합니다.

 

 

URL 에 변수를 넣는 방법 [ ]

이 방법 또한 Next js는 직관적이고 쉬운 방법을 제공하고 있습니다.

다음 사진과 같이 파일 이름에 [ ] 대괄호 안에 사용할 변수 명을 넣어주면 됩니다.

영화 아이디를 해당 파일에서는 id 로 사용하겠습니다.

 

 

그리고 코드는 다음과 같이 작성해봅니다.

import { useRouter } from "next/router";
export default function Detail() {
  const router = useRouter();
  console.log(router);
  return "detail";
}

 

그리고 브라우저에서 다음과 같은 주소로 접속해 봅니다.

 

 

console에 찍힌 router 을 살펴보면 query 키를 통해 접속한 페이지의 id 변수값을 받아올 수 있습니다.

 

 

Movie Detail page 만들기

 

그럼 기존의 index page의 코드를 수정해봅니다.

영화의 썸네일 포스터 카드들을 클릭할 수 있는 링크로 바꿔줍니다.

 

링크를 하는 방법으로 다양한 방법이 있는데

여기서는 두가지 방법을 소개해주고 있습니다.

 

Link href

첫번째로 Next js의 Link 모듈을 사용해서 링크하는 방법이 있습니다.

href에 링크를 넣어주면 리프레쉬가 되지않고 페이지 이동이 가능합니다.

<Link href={`/movies/${movie.id}`}>
  {movie.original_title}
</Link>
import { useEffect, useState } from "react";
import Link from "next/link";
import Seo from "../components/Seo";
export default function Home({ results: movies }) {
  return (
    <div className="container">
      <Seo title="Home" />
      {movies?.map((movie) => (
        <div className="movie" key={movie.id}>
          <img src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`} />
          <h4>
            <Link href={`/movies/${movie.id}`}>
              <a>{movie.original_title}</a>
            </Link>
          </h4>
        </div>
      ))}
      <style jsx>{`
        .container {
          display: grid;
          grid-template-columns: 1fr 1fr;
          padding: 20px;
          gap: 20px;
        }
        .movie {
          cursor: pointer;
        }
        .movie img {
          max-width: 100%;
          border-radius: 12px;
          transition: transform 0.2s ease-in-out;
          box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
        }
        .movie:hover img {
          transform: scale(1.05) translateY(-10px);
        }
        .movie h4 {
          font-size: 18px;
          text-align: center;
        }
      `}</style>
    </div>
  );
}

export async function getServerSideProps() {
  const { results } = await (
    await fetch(`http://localhost:3000/api/movies/popular`)
  ).json();
  return {
    props: {
      results,
    },
  };
}

 

Router Push

useRouter 모듈의 push를 사용하여 url을 이동할수도 있습니다.

push는 react-router-dom의 history.push와 거의 유사합니다.

// import 
import { useRouter } from "next/router";

// 선언
const router = useRouter();

// 실행
router.push("url");
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import Seo from "../components/Seo";
export default function Home({ results: movies }) {
  const router = useRouter();

  const onClick = (id) => {
    router.push(`/movies/${id}`);
  };
  return (
    <div className="container">
      <Seo title="Home" />
      {movies?.map((movie) => (
        <div onClick={() => onClick(movie.id)} className="movie" key={movie.id}>
          <img src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`} />
          <h4>
            <Link href={`/movies/${movie.id}`}>
              <a>{movie.original_title}</a>
            </Link>
          </h4>
        </div>
      ))}
      <style jsx>{`
        .container {
          display: grid;
          grid-template-columns: 1fr 1fr;
          padding: 20px;
          gap: 20px;
        }
        .movie {
          cursor: pointer;
        }
        .movie img {
          max-width: 100%;
          border-radius: 12px;
          transition: transform 0.2s ease-in-out;
          box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
        }
        .movie:hover img {
          transform: scale(1.05) translateY(-10px);
        }
        .movie h4 {
          font-size: 18px;
          text-align: center;
        }
      `}</style>
    </div>
  );
}

export async function getServerSideProps() {
  const { results } = await (
    await fetch(`http://localhost:3000/api/movies/popular`)
  ).json();
  return {
    props: {
      results,
    },
  };
}

 

이제 영화의 한 카드를 클릭하게되면 해당 영화의 id를 가지고 url 이동을 하게됩니다.

 

Detail page Fetch data

이제 디테일 페이지에서 해당 id를 가지고 영화 정보를 받아오는 코드를 작성합니다.

 

이전의 유명한 영화 데이터를 가져오는 api 말고

이번엔 영화 상세정보 api를 사용하여

rewrites를 추가 수정해줍니다.

 

rewirte 추가

baseUrl/movie/영화id 가 영화 상세정보를 받아오는 api 주소입니다.

source에 /api/movies/detail/ 다음에 영화의 id 를 변수로 받기위해 :id 로 작성해주고

destination에도 해당하는 부분에 :id를 똑같이 작성해줍니다.

( source 부분의 url 변수값과 destination url 변수값은 동일해야 원하는 로직으로 동작합니다. )

// next.config.js
async rewrites() {
    return [
      {
        source: "/api/movies/popular",
        destination: `https://api.themoviedb.org/3/movie/popular?api_key=${process.env.API_KEY}`,
      },
      {
        source: "/api/movies/detail/:id",
        destination: `https://api.themoviedb.org/3/movie/:id?api_key=${process.env.API_KEY}`,
      },
    ];
  },

next.config.js 파일 수정후에는 적용된 코드를 확인하기위해서는 서버를 종료하고 다시 실행시킵니다.

 

 

Router state 전달

상세 페이지를 만들기 앞서 이미 API를 통해 가져온 데이터가 존재하는데

그 데이터까지 또다시 불러 올 필요가 있을까라는 생각을 하게됩니다.

아래의 화면으로 보면 영화의 포스터 이미지, 영화의 제목이 있겠죠

react-router-dom에도 해당 부분을 처리할 수 있습니다.

Next js에도 router 모듈에서 제공하고 있죠

 

다음과 같이 작성하면 됩니다.

객체형태로

pathname : 라우팅할 주소

query: 보낼 state 데이터값

// 기존 router push 코드 
router.push(`/movies/${id}`);

// state 전달 수정 코드
router.push({
  pathname: `/movies/${id}`,
  query:{
    // 보낼 데이터
    title:'test'
  }
});

 

그리고 다시 해당 라우팅 부분을 클릭해 실행해 보면

???? 데이터는 잘 넘어왔다만 url에 파라미터값으로 보이고 있습니다.

 

 

저 url 을 감추고 데이터만 보내고 싶을땐 url masking을 사용합니다.

 

Router as, URL masking || 데이터를 url에서 숨겨서 보내기

라우터를 통해 데이터는 보내지만 url의 값은 마스킹을 해서 숨길수 있습니다.

다음과 같이 push의 두번째 인자값으로 url을 string 형식으로 보내면 됩니다.

// as의 위치
router.push(
  {
    pathname: `/movies/${id}`,
    query: {
      // 보낼 데이터
      title: "test",
    },
  },
  // as
);

// 사용법
router.push(
  {
    pathname: `/movies/${id}`,
    query: {
      // 보낼 데이터
      title: "test",
    },
  },
  `/movies/${id}`
);

 

이제 그럼 영화의 타이틀과 이미지를 실어서 보내도록 합니다.

객체 재할당을 통해 공통으로 쓰는 코드의 중첩을 제거해주었습니다.

마찬가지로 Link 에도 적용할 수 있습니다.

대신 조금 문법이 달라지게됩니다.

import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import Seo from "../components/Seo";
export default function Home({ results: movies }) {
  const router = useRouter();

  const onClick = ({ id, original_title, poster_path }) => {
    router.push(
      {
        pathname: `/movies/${id}`,
        query: {
          // 보낼 데이터
          id,
          original_title,
          poster_path,
        },
      },
      `/movies/${id}`
    );
  };
  return (
    <div className="container">
      <Seo title="Home" />
      {movies?.map((movie) => {
        const { id, original_title, poster_path } = movie || {};
        return (
          <div
            onClick={() =>
              onClick({
                id,
                original_title,
                poster_path,
              })
            }
            className="movie"
            key={id}
          >
            <img src={`https://image.tmdb.org/t/p/w500${poster_path}`} />
            <h4>
              <Link
                href={{
                  pathname: `/movies/${id}`,
                  query: {
                    // 보낼 데이터
                    id,
                    original_title,
                    poster_path,
                  },
                }}
                as={`/movies/${id}`}
              >
                <a>{original_title}</a>
              </Link>
            </h4>
          </div>
        );
      })}
      <style jsx>{`
        .container {
          display: grid;
          grid-template-columns: 1fr 1fr;
          padding: 20px;
          gap: 20px;
        }
        .movie {
          cursor: pointer;
        }
        .movie img {
          max-width: 100%;
          border-radius: 12px;
          transition: transform 0.2s ease-in-out;
          box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
        }
        .movie:hover img {
          transform: scale(1.05) translateY(-10px);
        }
        .movie h4 {
          font-size: 18px;
          text-align: center;
        }
      `}</style>
    </div>
  );
}

export async function getServerSideProps() {
  const { results } = await (
    await fetch(`http://localhost:3000/api/movies/popular`)
  ).json();
  return {
    props: {
      results,
    },
  };
}

 

detail page 데이터 바인딩

다음과 같이 상세페이지에서는 router의 query를 통해 데이터를 받아옵니다.

original_title이 있으면 데이터를 붙이고 없다면 loading을 보여줍니다.

// movies / [id].js
import { useRouter } from "next/router";
export default function Detail() {
  const router = useRouter();
  const { query } = router || {};
  return (
    <div>
      <h4>{query.original_title || "Loading..."}</h4>
    </div>
  );
}

 

? 데이터를 실어서 보내는건데 왜 로딩이 필요하지라는 의문점이 생길수도 있습니다.

loading이 보이는 시점은 home 메인에서 영화를 클릭하지 않고 해당 링크로 접속했을때 나오게됩니다.

왜냐면 home 메인에서 클릭을 통해 타고 들어오지않으면 데이터를 받아올곳이 없기 때문이죠.

 

다음 강의 공부를 통해 이를 해결해보도록 하겠습니다.

댓글
최근에 올라온 글