Next.js 공식 문서를 따라가는데 확실하게 이해가 안되는 부분이 있어 알아보기로 했다. 모른다고 그냥 넘어가기엔... Next.js에서 가장 중요한 부분 중 하나라고 생각돼서 정리까지 하기로 함. 아자자. 💨
기본적으로 Next.js는 모든 페이지를 Pre-Rendering 한다. 이 방식 덕에 Next.js가 더 나은 성능과 SEO를 가능하게 된 것이다.
Next.js는 Client-side의 JavaScript에 의해 실행하는 것 대신 사전에 각 페이지의 HTML을 생성한다. 이러한 방식을 Pre-Rendering이라고 한다. 그러니까 한마디로 Pre-Rendering이란? 미리 HTML을 만드는 방식 쯤으로 이해해두면 된다!
차이 알아보기 전에 단어 하나 알고 가자. Hydration이라는 단어는 직역하면 수분 공급인데, 브라우저에서 하나의 페이지가 로드될 때 그 페이지의 JavaScript code가 실행(로드)되고 페이지를 interacitve 있게 만드는 것을 뜻한다.
기존의 퓨어한 React에서는 초기 로드 시 빈 화면이 나온다. 그래서 퓨어한 React로 제작한 App은 초기에 들어갈 때 페이지가 모두 로드되는 속도가 느린 편이다.
Next.js에서의 Pre-Rendering은 미리 생성한 HTML 파일을 초기 로드 시에도 표시한다. 그래서 브라우저 버전이 낮아서 React를 실행시킬 수 없는 사용자도 Next.js가 미리 생성한 HTML 화면을 볼 수 있다.
Next.js는 Static Generation과 Server-side Rendering이라는 두가지 방식의 Pre-Rendering 방식을 가지고 있다. 이 둘의 가장 큰 차이점으로 페이지의 HTML이 생성되는 방식이 있다.
그리고 중요하게도, Next.js는 각 페이지에 사용하고자 하는 pre-rendering 방식을 선택할 수 있게 해준다. 즉 Static Generation과 Server-side Rendering의 두 가지 방식을 혼용한 "Hybrid" Next.js 앱을 만들 수 있게 해준다.
Next.js에서는 성능상의 이유로 Static Generation 방식을 추천한다. Static Generation 페이지는 CDN에서 캐싱을 하여 미리 생성해 놓은 HTML을 재사용한다. 그러나 페이지에서 최신의 데이터를 fetching 해야 하는 경우, Server-side Rendering 방식이 유일한 선택일 수 있다.
Static Generation의 HTML은 build-time에 생성된다고 했다. 하지만 외부 데이터를 가져오는 경우와 아닌 경우 Static Generation은 조금 다르게 사용된다.
function ErrorPage() {
return <div>404 Page</div>;
}
데이터가 없는 Static Generation은 매우 심플하다. 이 경우는 당연히 build-time에 HTML을 생성한다.
Pre-Rendering을 위해 외부 데이터를 가져와야하는 경우가 있다. 두 가지 시나리오가 있으며 각각의 경우에 Next.js가 제공하는 다음 기능을 사용할 수 있다.
getStaticProps
getStaticPaths
(일반적으로 getStaticProps
와 함께 사용)그럼 각각의 시나리오를 살펴보자.
getStaticProps
이 경우는 페이지의 contents가 외부 데이터에 따라 다른 경우이다. 아래는 블로그에 올라와 있는 모든 post들을 렌더링하는 예제이다.
function Blog({ posts }) {
// Blod의 post들 렌더링 하는 코드
}
// build-time 시 실행
export async function getStaticProps() {
const res = await fetch('BASE_URL/post');
const posts = await res.json();
return {
props: { posts, }
}
}
export default Blog;
데이터를 가져와 Pre-Rendering을 하기 위해서는 getStaticProps
를 비동기 방식으로 export하여 사용한다. 이 함수는 build-time 시 호출되며 Pre-Rendering 시 가져온 데이터를 page의 contents로 전달한다.
getStaticPaths
이 경우는 페이지의 경로가 외부 데이터에 따라 다른 경우이다. 예를 들어 블로그에서 특정 post를 click하면 post/[id].js 파일을 로드하는 경우다.
function Post({ post }) {
// post 렌더링 하는 코드
}
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 }
}
export async function getStaticProps({ params }) {
const res = await fetch(`BASE_URL/post/${params.id}`);
const post = await res.json();
return { props: { post } }
}
export default Post
id 값은 Pre-Rendering 시 외부 데이터에 따라 달라질 수 있다. 이것을 다루기 위해서 getStaticPaths
를 비동기 방식으로 사용한다. 이 함수는 build-time에 실행되며 Pre-Rendering을 위해 원하는 path를 구체화한다.
Server-side Rendering은 요청마다 HTML 페이지를 생성한다. 하나의 페이지를 Server-side Rendering 하기 위해서는 getServerSideProps
함수를 비동기 처리로 사용한다.
예를 들어, 외부 API로 부터 자주 업데이트되는 데이터를 Pre-Rendering하기 위한 페이지가 필요하다면, getServerSideProps
함수를 작성하여 아래와 같이 페이지에 전달할 수 있다.
function Page({ data }) {
// data 렌더링 하는 코드
}
// 모든 요청마다 실행
export async function getServerSideProps() {
const res = await fetch(`https://.../data`);
const data = await res.json();
return { props: { data } };
}
export default Page;
우선, Next.js 공식 문서에서는 성능 상의 이유로 Static Generation을 권장한다. 하지만 Static Generation은 자주 update되는 데이터를 보여주기에는 적합하지 않다. 이러한 경우는 성능은 저하될지라도, contents를 최신으로 유지할 수 있는 Server-side Rendering이 적합하다.
따라서, 각각의 Pre-Rendering 방식이 어느 상황에 적절한지 알고 있는 것이 더더욱 중요하다. Next.js 공식문서에도 기재되어 있듯이, 페이지마다 원하는 Pre-Rendering 방식을 선택할 수 있기 때문에 요구사항에 맞게 구성하는 것이 중요할 것이다.