지난 챕터에서는 Hello World 부터 Next의 라우팅까지 관련 기능들을 살펴보았다.
이번 챕터에서는 Next
의 가장 중요한 Pre-rendering과 관련된 기능들을 살펴보자 🔥
전통적인? React에서의 방식과 Next의 방식을 비교하면서 알아보자.
우선 CRA 방식으로 React 프로젝트를 하나 생성해보자.
npx create-react-app react-demo
yarn start
app을 실행시키고 우클릭해서 페이지 소스보기를 클릭해보면 <div id="root"></div>
만 존재하는 것을 확인할 수 있다. 이는 CSR의 전통적인 방식을 따르고 있음을 확인할 수 있다.
필요한 데이터를 모두 로드시키고 스크립트를 통해 필요한 데이터와 DOM을 동적으로 생성시키는 것이다.
이번에는 Next방식을 살펴보자.
npx create-next-app next-demo
yarn dev
똑같이 우클릭하고 페이지 소스보기를 클릭한다.
코드가 minify되어 있어서 보기가 힘들지만 자세히보면 div
외에도 main
,h2
등 많은 태그가 이미 생성되어져 있는 것을 확인할 수 있다.
이게 바로 리액트와 next의 큰 차이점이다.
Next.js
는 디폴트로 모든 페이지에 pre-rendering을 지원한다.
Next.js
는 클라이언트 측 자바 스크립트로 모든 작업을 수행하는 대신 각 페이지에 대해 미리 HTML을 생성한다.
plain React 앱에서는 첫 로드 때 하얀 화면만 보이지만 hydration을 거쳐야 앱이 비로소 렌더링되고 interactive 한 동작을 할 수 있다.
반면에 Next
에서는 pre-rendered된 HTML이 먼저 보여지고 htdration을 거치고 나서야 동적으로 동작하게된다.
pre-redering은 HTML을 미리 생성하고 보여주기 때문에 훨씬 좋은 사용자 경험과 성능을 보여준다.
미리 HTML이 렌더링되기 때문에 SEO에 큰 강점을 가지고 있다.
Next.js
는 두 가지의 프리렌더링을 지원한다.
Static Generation은 HTML 페이지가 빌드 시 생성되는 프리 렌더링 방법이다.
웹 페이지의 콘텐츠를 구성하는 모든 데이터가 포함된 HTML은 애플리케이션을 빌드할 때 미리 생성된다.
공식문서에서는 데이터가 있든 없든, 가능하다면 Static Generation을 사용할 것을 추천한다. 왜냐면 build time에 한 번 만들어지고나서 CDN에 의해 served 되는데, 이 경우는 매 요청시마다 페이지를 렌더링하는 SSR보다 더 빠르기 때문이다.
Static Generation : HTML이 build time에 생성이 되고, 매 요청시마다 재사용된다.
유저의 요청이 있기 전에 사전 렌더링이 될 수 있는 페이지들에 적절한 방식이다. 추가적으로 데이터를 가져오기 위해 Client Side Rendering을 사용할 수도 있음!
Next.js
는 기본적으로 우리의 앱의 모든 페이지들에 pre-render를 지원해준다.
즉 빌드시에 자동으로 모든 페이지 HTML을 생성해준다는 것이다.
그림처럼 데이터가 있는 경우에 HTML은 데이터를 가져온 이후에만 생성될 수 있는데 실습을 진행하면서 알아보자.
npx create-next-app next-pre-rendering
새로 프로젝트를 생성해보자.
index.js
import React from 'react';
function Home() {
return <h1>Home</h1>;
}
export default Home;
data-fetching을 테스트해보기 위해서 컴포넌트를 하나 생성한다.
users.js
import React from 'react';
function UserList() {
return <h1>List of Users</h1>;
}
export default UserList;
Static Generation으로 구현하기 위해 Next.js
는 메서드를 제공한다. getStaticProps
이다.
하단에 다음 함수를 추가해보자.
export async function getStaticProps() {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await response.json();
console.log(data);
return {
props: {
users: data,
},
};
}
중간에 콘솔을 찍었는데 브라우저가 아닌 터미널에 데이터가 찍히는 것을 확인할 수 있다.
데이터를 컴포넌트로 전달해서 브라우저에 렌더링해야하는데 Next.js
에서는 다음과 같은 포맷으로 데이터를 전달한다.
return {
props : {
users : data
}
}
import React from 'react';
function UserList({ users }) {
return (
<>
<h1>List of Users</h1>
{users.map((user) => {
return (
<div key={user.id}>
<p>{user.name}</p>
<p>{user.email}</p>
</div>
);
})}
</>
);
}
export default UserList;
전달한 데이터는 컴포넌트에 props로 전달되어진다.
위에서 작성한 공통되는 부분을 컴포넌트화 시키고 싶을 때 어떻게 해야할까? 다들 알다시피 간단하다.
Next.js
에서 pages라는 폴더는 매우 스페셜하다. 라우팅을 처리하기 때문에 즉 components라는 폴더를 따로 생성해서 그 곳에 컴포넌트를 위치시키도록하자.
import React from 'react';
function User({ user }) {
return (
<>
<p>{user.name}</p>
<p>{user.email}</p>
</>
);
}
export default User;
getStaticProps
는 page에서만 유효하고 일반적인 컴포넌트에서는 동작하지 않는다.posts > index.js
import React from 'react';
function PostList({ posts }) {
return (
<>
<h1>List of Posts</h1>
{posts.map((post) => {
return (
<div key={post.id}>
<h2>
{post.id} {post.title}
</h2>
</div>
);
})}
</>
);
}
export default PostList;
export async function getStaticProps() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
return {
props: {
posts: data.slice(0, 3),
},
};
}
Static Generation을 위해 새로운 컴포넌트를 생성하고 getStaticProps
로 데이터를 새로 만들어 props로 넘겨주었다.
이번에는 동적 라우팅 그리고 Static Generation을 해주기 위해 컴포넌트를 새로 생성했다.
[postId].js
function Post({ post }) {
return (
<>
<Link href={`posts/${post.id}`} passHref>
<h2>
{post.id} {post.title}
</h2>
</Link>
<p>{post.body}</p>
</>
);
}
export default Post;
export async function getStaticProps(context) {
const { params } = context;
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${params.postId}`,
);
const data = await response.json();
return {
props: {
post: data,
},
};
}
에러가 난다.
일단 기존 리스트 페이지와는 다르게 Dynamic Params로 data-fetching 하는 이 페이지는 단 하나의 페이지가 아님을 알아야한다. 우리는 다른 데이터들로 이루어진 최소 2개 이상의 동적 페이지를 만들 때 이렇게 사용한다.
즉 Next.js
는 id가 1, 2, 3가 될 수도 있고 100000이 될 수 있기 때문에 빌드시에 어떤 파라미터를 허용할지를 결정해줘야 한다.
결론은 이런 상황에는 getStaticPaths를 사용하자
export async function getStaticPaths() {
return {
paths: [
{ params: { postId: '1' } },
{ params: { postId: '2' } },
{ params: { postId: '3' } },
],
fallback: false,
};
}
이렇게 string으로 postId를 직접 넘겨주었더니 제대로 작동한다.
이번에는 하드코딩하지 않고 여러 개의 라우팅을 구현해보자.
export async function getStaticPaths() {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts`);
const data = await response.json();
const paths = data.map((post) => {
return {
params: {
postId: `${post.id}`,
},
};
});
return {
// paths: [
// { params: { postId: '1' } },
// { params: { postId: '2' } },
// { params: { postId: '3' } },
// ],
paths,
fallback: false,
};
}