Next.js는 create-next-app
을 이용하여 어플리케이션을 생성하는 것을 권장합니다. create-next-app
은 set up에 필요한 것들을 자동적으로 준비해줍니다. 만약 타입스크립트를 사용하고 싶다면 --typescript
플래그를 사용하세요.
npx create-next-app
# 혹은
yarn create next-app
npx create-next-app --typescript
# 혹은
yarn create next-app --typescript
만약 기존 프로젝트에 next를 추가한다면 다음과 같이 할 수 있습니다. next, react, react-dom을 설치해주세요.
npm install next react react-dom
# 혹은
yarn add next react react-dom
package.json
을 열고 scripts
를 수정해주세요.
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
Next.js는 **pages**
개념을 기반으로 구축되었습니다. 어플리케이션의 페이지들은 pages
디렉토리 안의 js
, jsx
, ts
, tsx
파일 안의 리액트 컴포넌트를 렌더링합니다. Next는 pages
디렉토리 안의 파일들을 코드 스플리팅된 컴포넌트로 만들어주므로 디렉토리 이름은 항상 pages
여야 합니다.
각각의 페이지들의 라우트 이름은 파일 이름과 연관이 있습니다. 예를 들어, pages
디렉토리 안에 about.js
파일이 있다면 라우터 이름은 /about
이 됩니다. 동적으로 바뀌는 라우트 또한 사용할 수 있습니다. pages/posts/[id].js
파일 이름을 이런식으로 작성하면, 동적으로 변하는 라우터를 만들 수 있습니다. posts/1
, posts/2
.. 등이 가능합니다.
어플리케이션을 구동하기 위해 npm run dev
혹은 yarn dev
를 실행합니다. http://localhost:3000
에 들어가면 보이는 화면은 pages/index.js
파일입니다.
기본적으로 Next.js는 모든 페이지를 미리 렌더링(pre-rendering)합니다. react는 최초에 비어있는 HTML 하나만 서버에서 받은 후 모든 것을 클라이언트 사이드에서 렌더링하는 반면, Next.js는 미리 각 페이지들의 HTML을 생성해둡니다. 이렇게 되면, Pre-rendering으로 인해 검색 최적화와 더 나은 성능을 가져올 수 있습니다.
Next는 Static Generation과 Server-side Rendering 두 가지 형태의 pre-rendering이 있습니다. 이 둘은 페이지들의 HTML을 언제 생성하느냐에 따라 다릅니다.
HTML을 build 할 때 생성하고 요청할때 이를 재사용합니다.
요청할 때마다 HTML을 생성합니다.
중요한 점은, 여러분이 어떤 방식으로 pre-rendering 할 것인지 정할 수 있습니다. 대부분의 페이지는 Static Generation 방식을 사용하고 다른 것들은 서버 사이드 렌더링 방식을 사용해서 하이브리드 next app을 만들 수 있습니다.
static generation 방식은 추가적인 작업 없이 CDN에서 캐시할 수 있기 때문에 next는 이 방식을 추천합니다. 이 두 가지 pre-rendering 방식과 함께 react처럼 클라이언트 사이드 렌더링을 사용할 수도 있습니다.
CDN이란?
CDN은 서버와 사용자 사이의 물리적 거리를 줄여 웹 페이지의 로드 지연을 최소화하는, 촘촘히 분산된 서버로 이루어진 플랫폼입니다.
Static Generation은 어플리케이션을 build 할 때 생성한다고 했습니다. next에서는 데이터가 있을때도, 그리고 데이터가 없을 때도 정적으로 페이지를 만들 수 있습니다. 각각의 경우를 통해 알아봅시다.
기본적으로 next는 데이터를 가져오지 않고 static generation을 사용하여 페이지를 미리 렌더링합니다.
function About() {
return <div>About</div>
}
export default About
data fetching을 하지 않는다면 문제될 게 없어보입니다. data fetching이 있는 경우는 어떨까요?
어떤 페이지들은 pre-rendering을 위해 데이터를 가져와야 합니다. 데이터를 가져오는 경우는 크게 두 가지가 있고 각각의 경우에, next가 제공해주는 함수를 사용할 수 있습니다.
페이지의 내용이 외부 데이터에 의존한다 => getStaticProps
사용
페이지의 경로가 외부 데이터에 의존한다 => getStaticPaths
사용
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
export default Blog
props로 정보를 받아 렌더링 해주는 컴포넌트가 있다고 할때, pre-render를 하기 전에 데이터를 받아와야 합니다. 이때 getStaticProps
를 사용할 수 있는데 이 함수는 build 타임에 호출되고 받아온 데이터를 Pre-render 할 때 전달해줍니다.
function Blog({ posts }) {
// posts 렌더링
}
// 이 함수는 build time에 호출됩니다.
export async function getStaticProps() {
// 외부 api 호출
const res = await fetch('https://.../posts')
const posts = await res.json()
// { props: { posts } }를 반환함으로써,
// `posts`를 build time에 prop으로 받을 수 있습니다.
return {
props: {
posts,
},
}
}
export default Blog
앞서 말했듯이 next에서는 동적 라우트를 사용해서 페이지를 만들 수 있습니다. 예를 들어, pages/posts/[id].js
라는 파일을 생성하고 posts/1
주소로 접근한다면 id를 1로 인식하여 페이지가 렌더링됩니다.
이 paths가 동적으로 받아온다면 다음과 같이 쓸 수 있습니다.
// getStaticPaths도 build 타임에 호출됩니다.
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// { fallback: false}는 다른 라우터는 404여야 한다는 것을 뜻합니다.
return { paths, fallback: false }
}
pages/posts/[id].js
에서 getStaticProps
를 사용하여 특정 id 값을 props로 받도록 합니다.
function Post({ post }) {
// posts 렌더링
}
export async function getStaticPaths() {
// ...
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
return { props: { post } }
}
export default Post
만약 Server-side Rendering을 사용한 페이지라면, 매 요청마다 HTML이 생성됩니다. server-side rendering을 사용하고 싶은 페이지에서 getServerSideProps
라는 비동기 함수를 export하면, 서버는 이 함수를 요청이 올때마다 호출합니다.
function Page({ data }) {
// 데이터 렌더링
}
// 이 함수는 매 request마다 호출됩니다
export async function getServerSideProps() {
// 외부 API로부터 데이터를 가져옵니다
const res = await fetch(`https://.../data`)
const data = await res.json()
// props를 통해 데이터를 넘겨줍니다(getStaticProps와 유사)
return { props: { data } }
}
export default Page
Static Generation 방식과 비슷하지만 다른 점은 getStaticProps
는 빌드 타임에 호출되고 getServerSideProps
는 매 요청마다 호출된다는 것입니다.
next는 가능하면 Static Generation을 사용하는 것을 권장합니다. CDN으로부터 받은 데이터를 한 번만 빌드할 수 있다면 모든 요청마다 서버에서 렌더링하는 것보다 훨씬 빠르고 적은 리소스를 사용할 수 있기 때문입니다.
마케팅 페이지, 블로그 포스트, 이커머스 상품의 리스트, 도큐먼트 페이지 등 많은 타입의 페이지에서 Static Generation을 사용할 수 있습니다.
스스로에게 물어봅시다. "사용자의 요청 전에 이 페이지가 pre-render 될 수 있는가?"
이 질문의 대답이 YES라면, Static Generation을 통해 퍼포먼스와 SEO 측면으로 많은 이점이 생길 것입니다.
반면, 사용자 요청 이전에 pre-render 할 수 없다면 Static generation은 좋은 생각이 아닙니다. 여러분의 페이지의 데이터가 자주 업데이트 되고 페이지의 컨텐트가 매 요청마다 바뀌는 경우가 여기에 해당됩니다. 이런 경우, 다음 두 가지 방법이 있습니다.
Static Generation과 Client-side Rendering 함께 사용 : 페이지의 일부분을 pre-rendering 하지 않고 그 부분을 client-side에서 자바스크립트 코드를 동작시켜 구성할 수 있습니다.
Server-side Rendering 사용: 모든 요청마다 페이지를 pre-render 할 수 있습니다. 이 방법은 CDN에 의해 페이지가 캐싱될 수 없기 때문에 느리지만 항상 최신 상태를 유지합니다.