[Next] useRouter로 url 정보를 이용해 디테일 페이지 만들기(feat. getServerSideProps) & 렌더링 시 html과 js의 차이 및 해결

쿼카쿼카·2022년 10월 18일
0

React / Next

목록 보기
11/34
// index.js

import Link from "next/link";
import { useRouter } from "next/router";
import Seo from "./Seo"

export default function Home({results}) {
  const router = useRouter();
  function onClick(id, title) {
    // 내용 지우고 push(`/movies/${title}/${id})로 변경
    router.push(`/movies/${title}/${id}`);
  }
  return (
    <div className="container">
      <Seo title='Home' />
      {results?.map(movie => (
        <div onClick={() => onClick(movie.id, movie.original_title)} className="movie" key={movie.id}>
          <img src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`} />
          <h4>
            {/* 링크도 router와 같이 변경 */}
            <Link href={`/movies/${movie.original_title}/${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')).json();

  return {
    props: {
      results,
    }
  }
}
// [...params].js

// 기존 [id].js는 변수를 한 개만 받아서 url을 막 입력하면 404 페이지 뜸
// [...id].js는 변수 여러 개 받을 수 있어 url 막 입력해도 'Loading...'이 뜸
// 여기서는 [...params].js로 사용

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

// getServerSideProps로 바꾼 후 매개변수 넣어주기
export default function Detail({params}) {
  const router = useRouter();
  // title, id를 비구조 할당
  // 백엔드에서 pre-render되어 router.query.params가 아직 존재하지 않음
  // 그래서 먼저 []를 지정해줘 오류를 막고 JS가 다운될 때 다시 채워줌
  // console.log(router)가 두번 뜨는 이유임

  // router.query.params를 params로 변경
  const [title, id] = params || [];
  console.log(router);
  return (
    <div>
      {/* SEO 추가 */}
      <Seo title={title} />
      {/* 링크 여부에 따라 Loading이 뜨던 기존 내용 지우고  title로 변경 */}
      <h4>{title}</h4>
    </div>
  );
}

// 유저에게 절대 Loading을 보여주지 않고,SEO에 최적하기 위해 getServerSideProps 작성
// fetch로 데이터를 가져오는 것 보단 url에서 더 빠르게 데이터 가져오기 위함
// request 정보를 얻을 수 있어 영화 제목 및 id 얻기 가능
// ctx(Next.js가 제공하는 server-side context)를 console 찍어보면 server에 params가 있음

// ctx에 {params: {params: []}}를 활용할 거라 ctx를 {params: {params}}로 비구조할당
export function getServerSideProps({params: {params}}) {
  return {
    // ctx 확인할 땐 빈 객체로 둠
    // params로 바꾼 후 params로 변경
    props: {
      params,
    },
  }
}

index.js

  • 기존 router와 Link의 경로를 title을 포함하여 변경해 줌
  • 결과 화면 및 개발자 도구 상황(아직 [...id].js 파일 안 고쳐서 Loading으로 뜸)

[...params].js

  • Deatail

    • 기존 [...id].js에서 의미를 위해 ...params로 변경
    • router.query.params에 있는 title과 id를 받기 위해 비구조 할당
    • 연결 여부에 따라 Loading이 뜨던 h4를 지우고 {title}로 변경
      • 이제 url로 정보를 받아오기 때문에 이전에 직접 링크를 타고 들어왔을 때 Loading 뜨던 오류도 사라짐
    • 여기서 내 페이지에서 잘 뜨다 시크릿모드로 접속하면 오류가 뜨는 현상이 있다고 함(난 안 그러던데....)

      • 렌더링 시 html과 js의 차이 및 해결 방법

      • 위 오류를 해결하기 위해 [title, id] = router.query.params || [] 로 변경
      • 기본적으로 렌더링 시 html 파일이 먼저 내려옴
      • 아직 js가 다운로드가 안 된 시점에 title, id 정보를 가져올 수 없음
      • 오류 방지를 위해 처음에 빈 배열을 넣어주고 js 다운로드 후 재렌더링 시 router.query.params 값을 줌
      • 해당 이유로 router를 콘솔 찍어보면 두 개가 찍힘(빈 배열, 정상 쿼리 배열)
  • getServerSideProps

    • SEO 최적화를 위해 작성
    • pre-render를 이용해 더 빠르게 데이터를 받아옴
    • ctx(Next.js가 제공하는 server-side context)
      • ctx로 매개변수 설정했을 때 반환되는 props는 빈 배열로 둬도 됨
      • ctx를 console.log 해보면 서버 콘솔에 parmas 정보 뜸
      • ctx의 params: {params: []}}를 이용할 거라 매개변수를 params: {params}로 변경
    • 비구조할당 시 객체 안 객체를 지정하고 싶을 땐 위처럼 params: {params}로 한다

    • props에 params를 반환
  • 다시 Deatil

    • 매개변수에 {params} 작성
    • router.query.params를 params로 변경
    • SEO도 추가해서 탭에 제목 달아주기

오늘의 교훈

  • 렌더링 할 때 html이 먼저 렌더링
  • 따라서 서버에서 pre-rendering을 통해 html을 만들어 준다면 html 모두 만들어져야 화면이 뜨지만 완료 속도는 빠름(SSR)
  • js는 html 이후에 다운로드 되기 때문에 이 점 고려하여 연산자 등을 통해 오류 없애기
  • CSR은 Loading과 함께 html 먼저 띄우고 js 기능 첨가(암튼 html오고 js 다운로드 되는 시간 차이 있다고 함)

getServerSideProps에 대한 이야기

import { GetServerSideProps } from 'next'

export const getServerSideProps: GetServerSideProps = async (context) => {
// ...
}

function Page({ data }: InferGetServerSidePropsType< typeof getServerSideProps>)

https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props#getserversideprops-with-typescript

  • router.query.params 타입 지정 (타입스크립트)
type MovieDetailParams = [string, string] | [];

const router: NextRouter = useRouter();
const [title, id] = (router.query.params || []) as MovieDetailParams;
profile
쿼카에요

0개의 댓글