스터디/React

[Next.js]노마드코더 NextJS 시작하기 강의

_leezoee_ 2022. 5. 11. 23:37

 

npx create-next-app@latest

타입스크립트를 사용하고 싶다면 npx create-next-app@latest --typescript (타입스크립트가 좋긴함)

 

jsx를 쓰기 위해 별도로 리액트를 import 필요는 없지만, useState나 useEffect 같은 react method(hooks)를 쓰고 싶으면 react.js를 import 해야함

import { useState } from "react";

 

[노드버전 에러발생]

노드 버전 안맞는 에러 발생

v16.15.0 노드로 재 다운로드

 

npm run dev로 실행


* Library vs Framework

: 라이브러리는 개발자로서 내가 가져다 사용하는 것. => 자유도가 높음

: 프레임워크는 나의 코드를 불러오는 것 => 해당 프레임워크의 특정한 규칙을 따라야함


 

* pages에 파일을 만들면 NextJS가 알아서 그 이름으로 URL을 만들어 줌 (대박적.. 쌩 리액트 쓰면 라우터 설계부터 컴포넌트 설계까지 단계적으로 생각하고 구현할 게 많았는데 시간절약이 많이 되는듯!!)

+ 없는 url 접근시 404페이지도 제공...(리액트에선 그냥 만들어야 됨ㅠㅠ)

//pages>about.js
export default function Potato(){//컴포넌트 이름은 아무거나 상관없음. 중요한건 export default
	return "Hello";
}

 

* next.js의 장점 중 하나는 앱에 로드되는 페이지들이 미리 렌더링 되어있다는 점 (static 상태로 생성) => SSR

 

* 라우터에 next/link 사용 (일반 a태그 사용시 ESLint 에러 발생, 페이지 새로고침 됨) => 페이지 새로고침X

   class나 style을 사용하기 위해서는 Link 안에 anchor(a태그)에 적용해주어야함

//conponents>NavBar.js
import Link from "next/link";
export default function NavBar(){
	return(
    	<nav>
            <Link href="/">
            	<a className="hello">Home</a>
            </Link>
            <Link href="/about">
            	<a style={{color:red}}>About</a>
            </Link>
        </nav>
    )
}

라우터에 hook 적용하기 => next/router 사용

//conponents>NavBar.js
import Link from "next/link";
import { useRouter } from "next/router";

export default function NavBar(){
	const router = useRouter();
	return(
    	<nav>
            <Link href="/">
            	<a style={{color : router.pathname === "/" ? "red":"blue"}}>Home</a>
            </Link>
            <Link href="/about">
            	<a style={{color : router.pathname === "/about" ? "red":"blue"}}>About</a>
            </Link>
        </nav>
    )
}

 

* NextJS에 CSS 추가해보기

 1. css를 모듈로 만들어서 import 하는 방식 사용 : 클래스 이름을 다 알아야하는 단점이 있움.

//components>NavBar.module.css
.nav{
	display:flex;
    justify-content : space-between;
    backgroud-color : tonato;
}

//components>NavBar.js
import { useRouter } from "next/router";
import styles from "./NavBar.module.css";

export default function NavBar(){
	const router = useRouter();
    
    return(
    	<nav className={styles.nav}>
        	<Link href="/">
            	<a>Home</a>
            </Link>
            <Link href="/about">
            	<a>About</a>
            </Link>
        </nav>
    )
}

 2. styles JSX 사용 : 사용컴포넌트 안에서만 css 적용됨, js로써 props를 갖다쓸 수 있음

//conponents>NavBar.js
import Link from "next/link";
import { useRouter } from "next/router";

export default function NavBar(){
	const router = useRouter();
	return(
    	<nav>
            <Link href="/">
            	<a className={{color : router.pathname === "/" ? "active":""}}>Home</a>
            </Link>
            <Link href="/about">
            	<a className={{color : router.pathname === "/about" ? "active":""}}>About</a>
            </Link>
            <style jsx>{`
            	nav{
                	backgroud-color:tomato;
                }
                a{
                	text-decoration:none;
                }
                .active{
                	color:yellow;
                    //color: ${props.color]
                }
            `}</style>
        </nav>
    )
}

 3. 전역 css 적용해보기

    style jsx global 사용 => 페이지 단위로 적용

<style jsx global>
    {`
`		a{
			color : blue;
		}
    `}
</style>

   모든 페이지에 적용하려면...? NavBar랑 font-size 같은거 전체 app에 적용하고 싶으면!!! => app.js활용

   pages에 _app.js 파일 생성 (이름 꼭 _app.js 여야함!!! 그래야 다른 페이지 만들기 전에 NextJS가 여길 먼저 읽어감)

   (+ 페이지, 컴포넌트에 css를 임포트 하기 위해선 반드시 module이어야함, 하지만 _app.js 에서는 모든 global styles을

    임포트 할 수 있음!!)

//pages>_app.js
import NavBar from "../components/NavBar";
import "../styles/globals.css"; //글로벌 스타일 임포트 가능, 여기에 폰트같은거 두면 설정됨!

export default function App({Component, pageProps}){
	return (
    	<>
    	<NavBar />
    	<Component {...pageProps} />
        <style jsx global>
        {`
           a{
            	color : blue;
            }
        `}
        </style>
        </>
    );
}

 


 

미니 무비앱 만들기 실습

 

*NextJS가 제공하는 head 사용해보기

//pages>index.js
import Head from "next/head";

export default function Home(){
	return(
    	<div>
            <Head>
            	<title>Home | Next Movies</title>
            </Head>
            <h1>Home</h1>
        </div>
    );
}

//pages>about.js
import Head from "next/head";

export default function About(){
	return(
    	<div>
            <Head>
            	<title>About | Next Movies</title>
            </Head>
            <h1>About</h1>
        </div>
    );
}

이렇게 하면 모든 페이지에 복붙해야하니까 좀 더 스마트 하게 하려면?...

컴포넌트로 만들기!

//components>Heads.js
import Head from "next/head";

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

이러면 Heads 컴포넌트를 불러와서 쓸수 있음.

//pages>index.js
import Head from "next/head";
import Heads from "../components/Heads";

export default function Home(){
	return(
    	<div>
            <Heads title="Home">
            <h1>Home</h1>
        </div>
    );
}

 

* Fetching Data

 

API key 숨겨보기

 

1. redirect => next.config.js 파일

//next.config.js
module.exports = {
	reactStrictMode  true,
    async redirects(){
    	return[
        	{
            	source:"/test", //여기 url로 들어오면
                destination:"/home", //이리로 리다이렉트해줌(https://www.google.com 외부도됨
                permanent:false //영구적으로 쓸지 브라우저나 검색엔진이 기억하는 여부 결정
            }
        ]
    }
}

config 파일은 수정 후 서버 재시작 필요 npm run dev

source:"/old-blog/:path*" 설정해주고 destination:"/new-blog/:path*" 설정해주면

url 에 old-blog/ 뒤에 뭐라고 치든 그 url 이 보존되어 new-blog url로 이동됨.

 

2. rewrites => next.config.js 파일

//next.config.js
const API_KEY = "1234560";

module.exports = {
	reactStrictMode  true,
    async rewrites(){
    	return[
        	{
            	source:"/api/movies",
                destination:`https://api.themoviedb.org/어쩌고?api_key=${API_KEY}`, 
                permanent:false //영구적으로 쓸지 브라우저나 검색엔진이 기억하는 여부 결정
            }
        ]
    }
}

redirect와 비슷하지만 유저가 url 변화를 볼 수는 없음.

이러한 방식으로 NextJS는 masking을 실행하고, 사용자는 API Key를 볼 수 없도록 숨길 수 있음.

+ .env 파일 만들어서 API_KEY를 빼둘 수 도 있음! .env는 gitignore 필수

 

//pages>index.js
import {useEffect, useState} from "react";
import Heads from "../component/Heads";

export default function Home(){

	const [movies, setMovies] = useState();
    useEffect(() => {
    	(async () => {
          const { results } = await (await fetch(`/api/movies`)).json();
          setMovies(results);
            })();
          }, []);
  
	return(
    	<div className="container">
        	<Heads title="Home" />
            {!movies && <h4>Loading...</h4>}
            {movies?.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>
            )}
            <style jsx>{`
                .container {
                  display: grid;
                  grid-template-columns: 1fr 1fr;
                  padding: 20px;
                  gap: 20px;
                }
                .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>
    );
}

//출처 : https://github.com/nomadcoders/nextjs-fundamentals/blob/ebe7016cb04f5d4dd93e317704b963bc91516c1d/pages/index.js#L1-L45

 

* Server Side Rendering

getServerSideProps()라는 function 을 export (이름 바꾸면 안됨!!!!)

getServerSideProps 는 server에서 실행, 여기에 무엇을 리턴하던지 이걸 props 로써 page에 주게 됨.

//pages>index.js
import { useEffect, useState } from "react";
import Heads from "../component/Heads";

export default function Home({result}){//pageProps 데이터 받음.
	return(
    	<div className="container">
        	<Heads title="Home" />
            { 
            
            }
        </div>
    );
}

export async function getServerSideProps(){//이름 고정, 서버단에서 데이터불러옴.
	const { result } = await (
    	await fetch(`http://localhost:3000/api/movies`)//절대주소 풀로 적어줌
    ).json();
    return{
    	props : {
        	results, //여기서 무엇을 return 하던지 이걸 props로써 _app.js page에 건넴.
            //_app.js보면 <Test {...pageProps}/> 이런식으로 건네줌
        },
    };
}

 

* Dynamic Routes

 

Next.js에는 라우터 개념이 없으니 pages에 적용.

만약 /movies/all이라는 url을 구현하고 싶다면 pages 폴더 안에 movies 폴더 만들고 all.js 파일 생성 하면 됨.

만약 /movies 라고만 구현 하고 싶다면? pages 폴더 안에 movies 폴더 안에 index.js 파일 생성하면 됨.

(movies 안에 다른 하위 url 이 없다면 pages 폴더 안에 그냥 movies.js 파일로 만들면 됨.)

 

영화의 id를 받아서 상세 페이지 구현하기

pages 폴더에 movies 폴더에 [id].js 파일 만들기 => 배열로 변수 파일 이름만들면 됨..신기.... 대괄호를 이용한 다이나믹 라우터 구현.

 

Link를 통한 navigating이 아닌 코드를 통해 자동으로 navigating 하는 방법 => router hook 사용

const onClick = (id) => {
	router.push(`/movies/${id}`);
};

<div onClick={() => onClick(moovie.id)} ></div>

하지만 위와 같이 router.push를 하거나 Link를 사용하는 것 외에도 url 에 정보를 숨겨서 전달 할 수 있음.

url 에서 url로 state를 넘겨주는 방법!!

 

router.push 할때 string이 아닌 as를 사용. (url마스킹)

//pages>index.js
...

const onClick = (id, title) => {
	router.push(
      {
    	pathname : `/movies/${id}`,
        query : {
        	title,
        },
       },`/movies/${id}` //as 사용하면 원하는 url로 마스킹 할 수 있음.
    );
};

<div onClick={() => onClick(moovie.id, movie.original_title)} 
className="movie" key={movie.id}></div>

...


//만약 Link에서 동일하게 url 마스킹을 하고싶다면?
<h4>
	<Link
    	href=({
        	pathname : `/movies/${movie.id}`,
            query : {
            	title : movie.original_title,
            },
        })
        as = {`/moives/${movie.id}`}
    >
     	<a>{movie.original_title}</a>
    </Link>
</h4>
//pages>movies>[id].js
import {useRouter} from "next/router";

export default function Detail(){
	const router = useRouter();
    return <div>
    	<h4>{router.query.title || "Loading..."}</h4> //홈에서 상세감시 페이지로 넘어올때만 보임.
    </div>
}

 

* catch-all url
[...id].js 이런 식으로 파일을 만들면 무한 url을 만들 수 있음

url에 /movies/a/b/c/d/e/f/g 이렇게 치면

console로 query.params 에 배열로 ["a","b","c"...] 이렇게 들어있는걸 확인 가능.

//pages>movies>[...params.js]
import Heads from "../../components/Heads";
import {useRouter} from "next/router";

export default function Detail({params}){
    const router = useRouter();
    const [title, id] = params || [];
    return(
    	<div>
        	<Heads title={title}/>
            <h4>{title}</h4>
        </div>
    );
}

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

 

* 404 커스텀

 

pages 에 404.js 만들면 됨