매일 땡기는 마라 코딩

[노마드 코더] NextJS 시작하기(2) 본문

카테고리 없음

[노마드 코더] NextJS 시작하기(2)

cmkoi1 2023. 12. 28. 14:49
 

NextJS 시작하기 – 노마드 코더 Nomad Coders

The React Framework for Production

nomadcoders.co

 

GitHub - nomadcoders/nextjs-fundamentals: Learning NextJS by Building a Tiny Movie Website.

Learning NextJS by Building a Tiny Movie Website. Contribute to nomadcoders/nextjs-fundamentals development by creating an account on GitHub.

github.com

 

 

#2 PRACTICE PROJECT

#2.1 Patterns

  • children은 react.js가 사용하는 prop로, 하나의 component를 다른 component 안에 넣을 때 쓸 수 있다.
  • _app.js 파일의 크기를 키우지 않게 하기 위해 아래 코드와 같이 Layout 컴포넌트를 만들어 childern을 사용할 수 있다.

app.js

import Layout from "@/components/Layout";

export default function App({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}

 

Layout.js

import NavBar from "./NavBar";

export default function Layout({children}) {
  return <>
    <NavBar/>
    <div>{children}</div>
  </>
}

 

  • next.js에서는 head component 패키지를 제공해 준다. 다음과 같이 사용할 수 있다.

Heads.js

import Head from "next/head";

export default function Heads({title}){
  return <Head>
    <title>{title} | Next Movies</title>
  </Head>
}

 

 

#2.1 Fetching Data

  • api key 연동 및 데이터 불러오기
  • map, async await에 대한 선수 지식이 필요하다.

index.js

import { useEffect, useState } from "react";
import Heads from "@/components/Heads";

const API_KEY = "<key>";

export default function Home() {
  const [movies, setMovies] = useState([]);
  useEffect(() => {
    (async () => {
      const { results } = await (
        await fetch(
          `https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`
        )
      ).json();
      setMovies(results);
    })();
  }, []);
  return (
    <div>
      <Heads title="Home" />
      {!movies && <h4>Loading...</h4>}
      {movies?.map((movie) => (
        <div key={movie.id}>
          <h4>{movie.original_title}</h4>
        </div>
      ))}
    </div>
  );
}

 

 

#2.2 Redirect and Rewrite

  • API가 유료거나 사용량이 정해져 있는 경우에 API를 소스코드에서 숨기는 것이 좋다.
  • next.js에는 유저가 접속한 경로를 변경하는 redirect와 reauest에 마스크를 씌우는 것 같은 rewrite라는 기능이 있다.
  • redirects를 하는 방법은 다음과 같다.
    1. 유저가 특정 sorce로 방문할 경우,
    2. 특정 destination으로 보낸다.
    3. permanet가 영구적인지 아닌지 설정한다.
  • redirects를 할 때 :로 소스의 특정 주소를 유지하게 하거나, *로 특정 위치의 뒷 부분 주소를 모두 감지하여 유지하게 할 수 있다.
  • rewrites를 하는 방법은 다음과 같다.
    1. sorce에 해당하는 request 경로를,
    2. destination 경로에 매핑한다.

next.config.js

//const API_KEY = "<key>";
const API_KEY = process.env.API_KEY;

module.exports = {
  reactStrictMode: true,
  async redirects(){
    return [
      {
        source:"/old-blog/:path",
        destination:"/new-sexy-blog/:path",
        permanent: false,
      },
      // {
      //   다른 내용을 작성할 수 있다.
      // },
    ];
  },
  async rewrites() {
    return [
      {
        source: "/api/movies",
        destination: `https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`,
      },
    ];
  }
}

 

index.js

//생략
useEffect(() => {
    (async () => {
      const { results } = await (await fetch(`/api/movies`)).json();
      setMovies(results);
    })();
  }, []);
//생략

 

.env

# github 연동 안 되도록 해야 한다.
API_KEY=<key>

 

.gitignore

//생략
# vercel
.vercel
.env
//생략

 

 

#2.3 Server Side Rendering

  • 로딩 화면을 보여 주지 않고, fetch나 데이터 작업을 모두 마치고 API 로딩이 완료되었을 때 렌더링을 하도록 하고 싶다면?
  • 페이지가 오직 server side render만 할 수 있게 선택하게 하는 함수 getServerSideProps를 사용하면 된다.
  • 함수 안에 적은 코드는 client 쪽이 아닌, server(백엔드) 쪽에서 작동한다.
  • client에게 보여지지 않기 때문에 함수 안에 Key와 같은 중요 코드를 적어 숨길 수도 있다.
  • 함수 내에서 return하는 값을 props로써 page에게 줄 수 있다.
  • API load가 느릴 경우 유저가 아무것도 보지 못한 채로 오래 기다릴 수 있다는 단점이 있다.
  • 소스 코드가 HTML로 이루어지게 되기 때문에, JS가 비활성화되어도 사이트 내용을 볼 수 있다.
  • search engine에 유리하다.

index.js

import Heads from "@/components/Heads";

export default function Home({results}) {
  return (
    <div>
      <div className="container">
        <Heads title="Home"/>
        {results?.map((movie) => (
          <div className="movie" key={movie.id}>
            <img src={`https://image.tmdb.org/t/p/w500/${movie.poster_path}`} />
            <h4>{movie.original_title}</h4>
          </div>
        ))}
      </div>
	//중략
    </div>
  );
}

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

 

 

#2.5 Dynamic Routes

  • URL에 변수를 넣는 방법.
  • /movies/all을 URL을 만들고 싶다면? -> movies 폴더를 만든 뒤, 폴더 안에 all.js 파일 생성.
  • /movies URL도 만들고 싶다면? -> movies 폴더 안에 index.js 파일 생성.
  • /movies/12와 같이 id 값으로 URL을 만들고 싶다면? -> movies 폴더 안에 [아이디명].js 파일 생성. 예를 들어 [id].js

/movies/[id].js

import { useRouter } from "next/router";

export default function Detail(){
  const router = useRouter();
  console.log(router);
  return "deail";
}

/movies/2에 접속했을 때 콘솔에 찍힌 값.

 

 

#2.6 Movie Detail

  • 이전에 만든 URL과 영화를 매핑해 영화 컴포넌트를 클릭하면 영화 id가 포함된 URL로 이동하게 하는 방법.
  • 기본적인 방법.

index.js

//생략
          <Link href={`/movies/${movie.id}`} key={movie.id}>
          <div className="movie" key={movie.id}>
            <img src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`} />
            <h4>{movie.original_title}</h4>
          </div>
          </Link>
//생략
  • router hook을 사용한 방법.

index.js

//생략
const router = useRouter();
  const onClick = (id) =>{
    router.push(`/movies/${id}`)
  }
  return (
    <div>
      <div className="container">
        <Heads title="Home"/>
        {results?.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>{movie.original_title}</h4>
          </div>
  
        ))}
      </div>
//생략

 

next.config.js

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

 

  • URL에서 URL로 state를 넘겨 주는 방법과 유저로부터 숨기는 방법.

index.js

//생략
  const onClick = (id, title) => {
    router.push(
      {
        pathname: `/movies/${id}`,
        query: {
          id,
          title,
        },
      },
      `/movies/${id}`
    );
  }
  return (
    <div>
      <div className="container">
        <Heads 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>
              <Link href={{
                  pathname: `/movies/${movie.id}`,
                  query: {
                    title: movie.original_title,
                  },
                }}
                as = {`/movies/${movie.id}`}
              >
                {movie.original_title}
              </Link>
            </h4>
          </div>
        ))}
      </div>
//생략

콘솔에는 보이지만, URL에서는 가려지는 내용.

[id].js

import { useRouter } from "next/router";

export default function Detail(){
  const router = useRouter();
  return <div>
    <h4>{router.query.title || "Loding..."}</h4>
  </div>
}
  • 여기서, router.query.title은 유저가 홈페이지에서 상세페이지로 넘어올 때만 존재한다. 시크릿 모드에서 바로 URL에 접속할 경우, Loading...이 출력된다.

 

 

#2.7 Catch All URL

  • 홈페이지에서 배너를 클릭해서 접근하지 않고 URL 타이핑으로 접근해도 내용을 확인할 수 있도록 만들기 위한 방법.
    • 먼저, [id].js의 이름을 [...id].js로 바꿔 준다. query 확인 시 배열을 받아오는 것을 볼 수 있다.

index.js

//생략
  const onClick = (id, title) => {
    router.push(`/movies/${title}/${id}`)
  };
  return (
    <div>
      <div className="container">
        <Heads 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>
              <Link href={`/movies/${movie.original_title}/${movie.id}`}>
                {movie.original_title}
              </Link>
            </h4>
          </div>
        ))}
      </div>
//생략

 

[...id].js

import Heads from "@/components/Heads";
import { useRouter } from "next/router";

export default function Detail() {
  const router = useRouter();
  const [title, id] = router.query.params || []; //SSR 방식이기 때문에 js가 다운로드 되기 전이라, 빈 배열을 만들고, 값을 가져와 뿌려 주는 것.
  // console.log(router)
  return (
    <div>
      <Heads title={title}/>
      <h4>{title}</h4>
    </div>
  )
}

export function getServerSideProps({ params: { params } }) {
  return {
    props: {
      params
    },
  }
}

 

 

 

#2.8 404 Pages

  • 404 페이지를 커스텀하려면 pages 폴더에 404.js 파일을 만들어 코드를 작성하면 된다.

404.js

export default function NotFound(){
  return "What are u doing here?";
}

 

 

 

 

 


강의 꾿... 이긴 한데, 모르거나 이해가 안 되는 내용이 너무 많아 복습이 필요할 것 같다.

이후 강의는 캐럿마켓 클론코딩이라고 하는데 가격이 후덜덜해서 고민 좀 ;-;


 

 

 

 

참고

https://velog.io/@deli-ght/nextrewrite%EC%99%80-redirect

 

next.js의 rewrite와 redirect

tldr; 유저가 어떤 path로 접근하는 경우, 특정 사이트로 옮겨주는 next 자체 기능 2가지

velog.io

 

 

728x90