- [노마드코더] NextJS 시작하기 #1
- [노마드코더] NextJS 시작하기 #2
- [노마드코더] NextJS 시작하기 #3
- [노마드코더] NextJS 시작하기 #4
- [노마드코더] NextJS 시작하기 #5
지난 강의 요약
Next에서는 pre-rendering된 html 페이지에 데이터가 포함되지 않는다. 컴포넌트의 초기 state를 미리 export하기 때문에 React.js가 처리를 마치기 전까지는 실제 html 소스코드에는 Loading(개발자가 데이터가 없을때 지정한 모습)페이지의 모습을 볼 수 있다. React.js의 처리가 완료되면, ReactJS는 useEffect, useState, fetch를 하고 난 뒤, 데이터의 모습을 볼 수 있다. 즉, 데이터를 온전히 보기 위해선 React.js의 처리가 완료될 때까지 기다려야하고, 그 전까지는 "Loading"페이지를 봐야한다.
그러나 이런 상태가 아닌, html에 데이터를 다 불러온 상태였으면 한다면, 즉 유저가 접속했을때 모든 데이터가 페이지에 들어있고 로딩 상태 페이지를 보지 않도록 하려면 getServerSideProps함수를 사용한다. 이 함수에서는 API를 사용할 수도 있고, 데이터를 가져오는 등 여러가지 기능을 구현할 수 있다. 해당 함수에 있는 코드는 프론트가 아닌 백엔드에서만 작동한다. 그렇기 때문에 해당 함수가 완료되기 전까지 유저는 페이지에서 아무것도 볼 수 없다.
Dynamic Routes
React에서는 url에 변수를 사용할 때 /movies/:id 와 같이 사용한다.
그러나 Next에서는 router가 없기 때문에 폴더와 파일이름을 가지고 설정한다.
만약 /movies/all이라는 url를 만들고 싶다면 pages폴더안에 movies폴더를 생성하고,all.js파일을 만들어준다.
pages/movies/all.js
그러면 해당 url로 접속했을때 보여주고 싶은 모습을 이 파일안에 작성해주면된다. 그러면 다른 설정 필요없이 해당 url에 접속하면 페이지가 보이는 것을 확인할 수 있다.
그렇다면 만약 /movies 경로를 사용하고 싶다면 어떻게 해야할까?
전에 했던것 처럼 pages폴더 안에 movies.js파일을 만들어서 사용해도 가능하다. 하지만 만약 /movies, /movies/all, movies/popular 등 /movies 뿐만 아니라 더 추가된 경로를 사용하려면
page폴더 안에 movies폴더를 생성하고, index.js파일을 만들어 코드를 작성하면 /movies 경로로 접속했을때 해당 페이지가 나타나게 된다.
/movies =>pages/movies/index.js
/movies/all =>pages/movies/all.js
/movies/popular =>pages/movies/popular.js
직접 해보면
pages폴더 안에 movies 폴더를 생성해주고, 안에 두개의 파일을 만들어준다.
각 파일의 내용은 다음과 같다.
movies/index.js
export default function All() {
return "movies index";
}
movies/all.js
export default function All() {
return "all";
}
그리고 나서 각 경로로 접속해보면 경로에 맞는 페이지가 보인다.
만약 페이지가 하나라면 굳이 폴더를 만들 필요는 없다.
우리는 영화 아이디에 맞는 페이지를 만들어야하기 때문에 all.js와 index.js파일을 지워준다.
그리고 영화 아이디에 따라서 url이 바뀌는 것을 만들기 위해서 movies폴더 안에 [id].js 파일을 생성한다. 여기서 id는 변수명이기 때문에 개발자가 원하는 변수명으로 적어주면 된다.
파일안에 다음과 같이 작성한다.
import { useRouter } from "next/router";
export default function Detail() {
const router = useRouter();
console.log(router);
return "detail";
}
그리고 http://localhost:3000/movies/숫자아무거나 로 접속해보면
detail이라는 문구가 나오고 콘솔창을 확인하면 현재 router정보가 담겨있는 것을 확인할 수 있다.
그중에서도 query에 담긴 내용을 보면
변수명은 아까 파일 이름에서 [] 안에 적었던 변수명으로 되어있고, 변수의 값은 url에 적었던 숫자로 되어있는 것을 확인할 수 있다.
Dynamic URL을 사용하려면 파일명에 []을 사용하면 된다.
영화 상세페이지로 이동
index.js파일에서 map부분을 다음과 같이 수정한다.
{results?.map((movie) => (
<Link href={`/movies/${movie.id}`} key={movie.id}>
<a>
<div className="movie">
<img src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`} />
<h4>{movie.original_title}</h4>
</div>
</a>
</Link>
))}
그러면 영화 포스터를 클릭했을 때 상세페이지로 넘어가는 것을 볼 수 있다.
그러나 a태그 안에는 div태그를 넣지 않기 때문에 우선 Link는 영화 제목에만 감싸준다.
{results?.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>
))}
Navigating하는 다른 방법이 존재하는데 바로 router hook을 사용하는 방법이다.
useRouter를 사용해서 router변수를 하나 만들어주고 onClick 함수를 만들어준다. push함수를 사용해서 url을 변경할 수 있다.
const router = useRouter();
const onClick = (id) => {
router.push(`/movies/${id}`);
};
그리고 div에다가 onClick 함수를 지정해주는데 함수에 movie id를 인자로 넘긴다.
<div onClick={() => onClick(movie.id)} className="movie" key={movie.id}>
그리고 next.config파일로 가서 rewrites에 다음 코드를 추가해준다.
{
source: "/api/movies/:id",
destination: `https://api.themoviedb.org/3/movie/:id?api_key=${API_KEY}`,
},
:id 부분은 source와 destination에 동일하게 작성해줘야한다. src가 :page면 dest도 :page로 통일시켜줘야한다.
테스트해보려면 서버를 재시작 한 후 에 http://localhost:3000/api/movies/영화id아무거나 입력 후 이동하면 json파일이 보여진다면 잘 작동되는 것이다.
url을 이동하면서 state도 보내기
push 함수안에는 객체를 넣을 수 도 있다. 이렇게
router.push({
pathname: `/movies/${id}`,
query: {
id,
title: "potatos",
},
});
그러면 영화를 클릭해보면 url에 쿼리가 id와 title이 있는것을 확인할 수 있다. 현재는 title을 우리가 하드코딩으로 직접 입력해줬기 때문에 어느 영화를 눌러도 항상 title은 potatos라고 나올 것이다.
우선 id는 url에 존재하기 때문에 지워주고, title만 사용할 건데 이 쿼리 스트링이 사용자에게 보여주는 것이 싫다면 "as" 라는 옵션을 사용해주면 된다. (masking)우리는 쿼리스트링이 없는 url을 사용자에게 보여줄 것이다. push의 두번째 인자에 유저에게 보여줄 url을 적어준다.
router.push(
{
pathname: `/movies/${id}`,
query: {
id,
title: "potatos",
},
},
`/movies/${id}`
);
그리고 영화 제목은 유저가 클릭한 영화의 제목을 넘겨줘야하기 때문에 onClick함수 파라미터에 title도 추가해준다.
const onClick = (id, title) => {
router.push(
{
pathname: `/movies/${id}`,
query: {
title,
},
},
`/movies/${id}`
);
};
onClick={() => onClick(movie.id, movie.original_title)}
title에 걸어놨던 Link에도 동일하게 변경해준다.
index.js 전체코드
import Seo from "../components/Seo";
import Link from "next/link";
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
href={{
pathname: `/movies/${movie.id}`,
query: {
title: movie.original_title,
},
}}
as={`/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")).json();
return {
props: {
results,
},
};
}
[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
영화 상세페이지에 영화제목을 넣으려고 한다. 그러면 상세페이지에 URL만 치고 들어올 때도 영화제목을 표시할 수 있고, SEO에도 좋다.
[id].js 의 파일명을 [...id].js로 변경하고 router의 콘솔을 찍어보면 아까와 다르게 배열로 되어있는 것을 볼 수 있다.
만약 url 뒤에를 아무렇게나 더 추가해보면
쿼리에는 뒤에 나온 모든 경로가 담겨있다.
이것을 활용하면 아래와 같이 URL안에 영화 제목이 들어가있다면 query 에 값이 담길 것이고, URL을 직접 입력해서 들어왔더라도 영화제목을 보여줄 수 있다.
http://localhost:3000/movies/Spider-Man:%20No%20Way%20Home/634649
index.js에서 router.push 경로를 다음과 같이 변경한다.
router.push(`/movies/${title}/${id}`);
div쪽도 변경해준다.
<Link href={`/movies/${movie.original_title}/${movie.id}`}>
[...params].js에서도 이번엔 router에서 query를 가져와서 사용해준다.
import { useRouter } from "next/router";
export default function Detail() {
const router = useRouter();
const [title, id] = router.query.params;
return (
<div>
<h4>{title}</h4>
</div>
);
}
그러나 이렇게 작성하면 주소를 직접 입력해서 url에 들어가면 에러가 발생한다. 백엔드에서 pre-rendering 할 때 router.query.params에 값이 없기 때문이다.
페이지 소스코드를 봐도 title이 비어있는 것을 볼 수 있다.
여기서 getServerSideProps를 사용해서 해결 할 수 있다.
export function getServerSideProps(ctx) {
console.log(ctx);
return {
props: {},
};
}
을 아래에 작성해주고 vscode의 터미널 창을 보면
이런식으로 정보가 담겨있다.
이제 저 params를 사용하기 위해서 다음과 같이 수정해준다.
import { useRouter } from "next/router";
export default function Detail({ params }) {
const router = useRouter();
const [title, id] = params || [];
return (
<div>
<h4>{title}</h4>
</div>
);
}
export function getServerSideProps({ params: { params } }) {
return {
props: { params },
};
}
그러면 이제 url를 직접 치고 상세 페이지로 접속해도 영화 제목이 나오고, 페이지소스보기에서도 영화 제목이 있는걸 확인할 수 있다.
404 page
page폴더 안에 404.js파일을 만들어준다. 그리고 다음과 같이 입력해준다.
export default function NotFound() {
return "What are u doing here?";
}
이제 url에 아무렇게나 치고 들어가보면 이렇게 나오면 끝이다. 이 컴포넌트에 에러페이지를 만들면 된다.
'개발 공부 > Next JS' 카테고리의 다른 글
[노마드코더] NextJS 시작하기 #4 (0) | 2022.04.09 |
---|---|
[노마드코더] NextJS 시작하기 #3 (0) | 2022.04.08 |
[노마드코더] NextJS 시작하기 #2 (0) | 2022.04.07 |
[노마드코더] NextJS 시작하기 #1 (0) | 2022.03.22 |
[Next] NextJS의 getInitialProps, getStaticProps, getStaticPaths, getServerSideProps 함수 (0) | 2022.03.20 |