npx create-next-app@latest 로 터미널에서 설치한다.
설치할때 친절하게도 뭐뭐 설치할지 다 물어봐준다.
설치후엔 리액트처럼 npm run dev 하면 되는데 리액트랑 다르게 브라우저창을 알아서 띄워주지 않는다.
아마 찾아보면 자동으로 띄워주는게 있긴하겠지
localhost:3000으로 들어가면 정상적으로 페이지가 출력된다.
라이브러리와 프레임워크의 주요 차이점은 "Inversion of Control"(통제의 역전)이다.
라이브러리에서 메서드를 호출하면 사용자가 제어할 수 있다.
그러나 프레임워크에서는 제어가 역전되어 프레임워크가 사용자를 호출한다.
라이브러리
사용자가 파일 이름이나 구조 등을 정하고, 모든 결정을 내림
프레임워크
파일 이름이나 구조 등을 정해진 규칙에 따라 만들고 따름
pages 폴더 안에 있는 파일명에 따라 route가 결정된다.
pages/about.js 생성 ->
localhost:3000/about (O)
localhost:3000/about-us(X)
만약 이경우에 about 페이지로 접속하게되면 404페이지가 뜨는데..
react-create-app으로 작업하게되면 404페이지를 직접 만들어줘야한다.
우리가 꼭 지켜야 할 규칙중 하나는 컴포넌트에 export default를 꼭 붙여주자
다만 예외사항으로, index.js의 경우에는
앱이 시작하는 파일이라고 보면 된다.
즉 localhost:3000 그 자체다 뒤에 /index 로 붙이면 안된다.
이 강의를 들을 때는 import react from "react"를 쓸 필요가 없다.
다만 useState,useEffect, lifecycle method 같은 애들을 써야 할 경우에는 꼭 import를 해줘야 한다.
next.js의 가장 좋은 기능 중 하나는, 앱에 있는 페이지들이 미리 렌더링된다는것이다.
create-react-app 은 client-side render 즉
브라우저가 유저가 보는 UI를 만드는 csr로 로딩이 된다.
그럼 NextJs는? SSR 즉 서버에서 UI를 전부 그려서 한번에 로딩시켜준다.
React의 route와 별 반 다를바 없다 다만 차이점이 있다면
nextJS에서는 <a>
로 home페이지로 이동하는데 사용하지 마라 라고 eslint 경고 메시지가 떠
그러면?
import Link from "next/link";
를 추가하여 저번에 사용했던 Link 기능을 사용하자
다만 Link는 href기능에만 사용되고
나머지 className이나 다른 정보들은 여전히 <a>
안에 넣어줘야되
<Link legacyBehavior href="/">
<a className={router.pathname === '/' ? 'active' : ''}>Home</a>
</Link>
위 코드는 예시야
근데 가끔 <a>
에 className을 넣으려고할때 오류가 뜰때가 있음
그럴때에는 Link에 legacyBehavior를 추가해주는 방법을 사용하면 된다.
설치는
import { useRouter } from 'next/router'
사용은
const router = useRouter()
앱의 함수 컴포넌트에서 router객체 내부에 접근하려면 userRouter()훅을 사용할 수 있다.
useRouter는 React Hook입니다. 즉, 클래스와 함께 사용할 수 없다. withRouter를 사용하거나 클래스를 함수 컴포넌트로 래핑할 수 있다.
어디서 많이 본 이름이다.
<a className={router.pathname === '/' ? 'active' : ''}>Home</a>
위는 삼항연산자로 pathname이 홈이면 activ 클래스의 css가 적용되고 아니면 적용되지 않는다는뜻이지
이제 styleJSX를 배워볼건데
''도 아니고 ""도 아니고 ``를 사용한다.
<style jsx>{`
a {
text-decoration: none;
}
.active {
color: tomato;
}
`}</style>
이 스타일들은 오직 컴포넌트 내부로 범위가 한정되는 특징이 있다.
module.css와 같이 classname이 랜덤으로 생성되기도 한다.
위에 말했듯이 컴포넌트 내부로 범위가 한정된다고 했지? 근데 모든 페이지에 다 적용시키고 싶어 그러면 어떻게 한다?
<style jsx global>
{`
a {
color: white;
}
`}
</style>
jsx 다음에 gobal을 붙여줘
다만 이런다고 끝나는게 아니라 nextjs에서는 페이지를 고려해줘야한다.
뭐.. 저 코드를 모든 페이지에 다 박는다고 하면 되겠지만
그러면 얼마나 더러워 지겠냐
그래서 이 글로벌 설정을 한번에 박아둘 수 있는 파일이 필요하다.
_app.js 이 필요하다 꼭 이 이름이어야함!
모든 페이지를 렌더링하기전에 _app.js 파일을 먼저 본다.
import NavBar from "@/components/NavBar";
export default function MyApp({ Component, pageProps }) {
return (
<div>
<NavBar />
<Component {...pageProps} />
<style jsx global>
{`
a {
color: white;
}
`}
</style>
</div>
);
}
저기서 Component는 내가 렌더링하길 원하는 페이지로 첫번째 Prop으로서 넣어준거다.
_app.js는 필수로 만들어야하는 파일이 아니다. custom이 필요할때 templete 설정이 필요할때 생성하는 파일이다.
이렇게 _app.js에 navbar도 때려박고 style jsx global도 때려박을 수도 있다.
이거 좀 좋은듯?
nextJS로도 미니 영화앱을만들어보고자 한다. 그에 앞서 Layout 부분이다.
nextJS를 사용할때 많은 사람들이 사용하는 패턴인 Layout이다.
먼저 components 폴더아래 Layout.js 파일을 만들자
Layout.js
import NavBar from "./NavBar";
export default function Layout({ children }) {
return (
<>
<NavBar />
<div>{children}</div>
</>
);
}
_app.js
import Layout from "@/components/Layout";
export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
앞서 _app.js가 파일이 비대해지면 좋을게 없기때문에 이렇게 작성해준다.
_app.js의 레이아웃 사이에 있는건 전부 children으로 취급한다는 뜻이다.
이렇게하면 footer나 header 또는 navbar가 모든 페이지에서 필요할때 더 깨끗하게 코딩을 할 수 있다.
이제 head를 만들 차례인데 next에 있는 기능으로 head를 import한다.
그래서.. home하고 about 페이지에
<Head>
<title>Home |Next Movies</title>
</Head>
를 넣는게 맞냐? 배웠으면 써먹어야겠지
components폴더 아래에 Seo.js를 만든뒤 다음과 같이 작성해준다.
export default function Seo({ title }) {
return (
<Head>
<title>{title} | Next Movies</title>
</Head>
);
}
그리고 about과 home 페이지에가서
<Seo title="Home"></Seo>
<Seo title="About"></Seo>
이렇게 넣어준다.
ReactJS 영화앱에서도 했던 작업인 Fetching 작업이다.
근데 이번에는 다른 api를 사용했다.
https://developers.themoviedb.org/3/movies/get-popular-movies
여기 가입한다음 api요청을 한뒤
관련 document에 가서 popular api를 사용했다.
import { useState, useEffect } from "react";
import Seo from "../components/Seo";
const API_KEY = "991d8e7aeb3196b5c6a3abd3727942fe";
export default function Home() {
const [movie, setMovies] = useState([]);
useEffect(() => {
(async () => {
const data = await (
await fetch(
`https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`
)
).json();
console.log(data);
})();
}, []);
return (
<div>
<Seo title="Home"></Seo>
<h1 className="active">Hello</h1>
</div>
);
}
useEffect / map() / async / await는 저번에 배웠으니 따로 서술하지 않겠다.
이제 다음에 해볼건 API키를 숨기는 법이다.
왜 숨기냐고? API KEY를 돈주고 사는 경우도 있는데 보여지면
ㅈ같을 수도있고 가끔은 API KEY의 사용량이 제한되는 경우도 있다.
1분에 100번밖에 못불러온다거나 그리고 남들이 남용 할 수도 있고 그러다보면 사용에 제한 걸리겠지.
이걸 숨기기 위한 방법이 redirect와 rewrite다
request에 mask를 씌운다고 생각하면 편하다.
redirect
source: 들어오는 request 경로 패턴 (request 경로)
destination: 라우팅하려는 경로 (redirect할 경로)
permanent: true인 경우 클라이언트와 search 엔진에 redirect를 영구적으로 cache하도록 지시하는 308 status code를 사용하고, false인 경우 일시적이고 cache되지 않은 307 status code를 사용
예시
const nextConfig = {
reactStrictMode: true,
async redirects() {
return [
{
source: "/contact",
destination: "form",
permanent: false,
},
];
},
};
next.Config.js 파일이다.
저기서 sources는 url뒤에 붙는 걸 말하고 destionation은 source에 해당하는 url을 입력했을때 어느 페이지로 이동하는지를 알려준다 이경우엔
/contact를 입력했을때 form페이지로 가게 되는 것이다.
다른예시
const nextConfig = {
reactStrictMode: true,
async redirects() {
return [
{
source: "/old-blog/:path",
destination: "/new-sexy-blog/:path",
permanent: false,
},
];
},
};
이런 경우는 어떨까?
/old-blog/124444을 입력했을때 우리가 이동하는 페이지는?
/new-sexy-blog/124444다
오 이 기능 좋은것 같다
만약 이 뒤에 /new-sexy-blog/:path* 별을 붙이면 모든 주소를 전부 캐치해온다.
/old-blog/124444/comments/1234444
/new-sexy-blog/124444/comments/1234444
요런식으로
자 이제 rewrite 기능이다.
Rewrites (URL변경되지 않음)
Rewrites를 사용하면 들어오는 request 경로를 다른 destination 경로에 매핑할 수 있다.
Rewrites은 URL 프록시 역할을 하고 destination 경로를 mask하여 사용자가 사이트에서 위치를 변경하지 않은 것처럼 보이게 한다.
async rewrites() {
return [
{
source: "/api/movies",
destination: `https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`,
},
];
},
먼저 index 페이지 fetch 부분과 상단에 정의되어있던 api key를 next.Config.js로 옮긴다.
index 페이지의 fetch부분은 다음과 같이 고쳐주었다.
fetch(`/api/movies`)
자 이러면 우리의 network에 우리 api키가 비공개되고 가짜 fetching url을 이용해서 movies를 받아오게 되었다.
rewrites = 실제 URL은 destination 이지만 source라고 구라침
만약 이방법이 맘에 안든다면 다른 방법도 있다.
next.Config.js의 API_KEY 부분을 수정 후
const API_KEY = process.env.API_KEY;
.env파일을 만들어서
API_KEY=""
식으로 작성하면 된다.
다만 git ignore에 env파일 포함하는거 잊지말자.
다음은 SSR이다
Super Secret Rare가 아니라 Server Side Rendering이다.
NextJS에는 우리가 만든 페이지가 오직 SSR만 할지 선택할수 있는 function이 있다.
getServerSideProps는 client 쪽이 아니라 server쪽에서만 작동한다.
그래서 말인데.. 모든 async와 fetch를 백엔드 처리 부분으로 옮기자
이렇게 바꿔주자
export default function Home({ results }) {
return (
<div className="container">
<Seo 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>
))}
<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>
);
}
export async function getServerSideProps() {
const { results } = await (await fetch(`http://localhost:3000/api/movies`)).json();
return {
props: {
results,
},
};
}
useState와 useEffect부분을 지우고 movies로 더이상 정보를 받지 않으니 result로 바꿔준다.
export async function getServerSideProps() {
const { results } = await (await fetch(`http://localhost:3000/api/movies`)).json();
return {
props: {
results,
},
};
}
그리고 object 하나를 return해줄건데 그안에는 props라고 불리는 key 혹은 property를 가진다.
이 props안에 우리의 results가 들어간다.
그런데 저 result는 어디서 가져오는거임?
정답은
export default function Home({ results })
이 부분이다.
자 다시 정리를 해보자
import Layout from "@/components/Layout";
import "../styles/globals.css";
export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
에서 우리가 index page를 들어간다고할때
index페이지의 seversideprops 를 먼저 읽고
pageprops 부분에 serversideprops를 뿌려준다.
그리고 그 serversideprops에 들어가는 데이터가 home 인자에 출력되는것.. 아 진짜 머리아프네
뭔가 다이나믹이라는 단어가 들어가니 엄청나 보이지만 동적 라우트다
nextJS의 동적 라우트는 원하는 이름의 url폴더를 만든뒤 [사용할 변수명].js을 만들어주면 된다.
저번에 ReactJS실습에서는 정보를 가져오기위해 usePram을 사용했으나 이번엔 useRouter를 사용해봤다.
import { useRouter } from "next/router";
export default function Detail() {
const router = useRouter();
console.log(router);
return "detail";
}
잘 가져와진다.
import Link from "next/link";
import Seo from "../components/Seo";
import { useRouter } from "next/router";
export default function Home({ results }) {
const router = useRouter();
const onClick = (id, title) => {
router.push(
{
pathname: `/movies/${id}`,
query: {
title,
},
},
`/movies/${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>
<Link legacyBehavior 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 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>
);
}
메인페이지에서 글이랑 그림을 누르면 해당 영화로 보내주는 기능을 구현했는데 거기서 배운게
여기서 as에 /movies/${id}
를 사용하여 우리는 주소창을 깨끗하게 만들었다.
즉 as에 쓴 주소만 보이게 해주는것