Next.js는 서버 측 렌더링(SSR)을 지원하여 기본적으로 모든 페이지를 사전에 렌더링(pre-render)한다.
서버 사이드 렌더링은 페이지의 HTML 마크업을 미리 생성하여 클라이언트에 전달하는 방식이다.
이로 인해 Next.js 애플리케이션은 브라우저로 전송되는 시점에 사전에 렌더링된 HTML과 생성된 HTML에 필요한 최소한의 JavaScript 코드와 함께 을 로드한다. 이후 클라이언트 측에서 JavaScript 코드가 페이지를 완성하기 위해 실행되며, 이를 통해 상호작용 및 동적인 기능을 구현할 수 있다.
이러한 사전 렌더링 방식은 퍼포먼스 향상과 검색 엔진 최적화(SEO)에 도움을 준다.
Next.js는 클라이언트 측 렌더링도 지원하며, 정적 사이트 생성(Static Site Generation) 기능도 제공한다. 이를 통해 동적인 콘텐츠와 정적인 콘텐츠를 효과적으로 조합하여 유연하고 성능이 우수한 웹 애플리케이션을 구축할 수 있다.
간단 정리
- 클라이언트 측에서 JavaScript를 사용해 페이지를 동적으로 렌더링을 하는게 아니라, 서버에서 HTML 파일을 사전에 생성하여 클라이언트에게 전달한다.
- 생성된 HTML에 필요한 최소한의 JavaScript 코드가 함께 전송되고, 클라이언트는 이를 로드하고 실행하여 완전한 페이지를 표현한다. (Hydration)
- 이렇게 pre-render 된 페이지는 퍼포먼스 향상 & SEO에 더 좋다.
Next.js의 사전 렌더링(pre-rendering)은 두 가지 방식으로 이루어지며,
페이지별로 정적 생성을 할지 서버 사이드 렌더링을 할지 결정할 수 있다.
두 방식의 차이점은 언제 HTML을 생성하느냐 이다.
: 빌드 시점에 페이지의 HTML을 생성하는 사전 렌더링 방식
next build
명령어를 실행할 때 페이지의 HTML이 생성된다.: 요청이 있을 때마다 서버에서 해당 페이지의 HTML을 동적으로 생성하여 보내주는 방식
Next.js에서 페이지를 정적 생성할 때는 두 가지 경우가 있다.
데이터가 없는 정적 생성과 데이터가 있는 정적 생성이다.
Static Site Generation (SSG) - Next.js
기본적으로 Next.js는 데이터를 가져오지 않고 정적 생성을 사용해 페이지를 사전 렌더링(pre-render)한다.
아래 예시의 About 페이지는 사전 렌더링하기 위해 어떠한 외부 데이터로 필요로 하지 않는다. 이러한 경우 Next.js는 빌드시, 페이지당 하나의 HTML파일을 생성한다.
function About() {
return <div>About</div>;
}
export default About;
몇몇 페이지들은 사전 렌더링을 위해 외부 데이터를 필요로 한다. 두 가지의 경우가 있는데, 아래 중 하나를 사용하거나 두 개 모두 사용할 수 있다.
1. 페이지의 콘텐츠가 외부 데이터에 의존한다면, getStaticProps()
를 사용한다.
2. 페이지의 경로가 외부 데이터에 의존한다면, getStaticPaths()
를 사용한다. (주로 getStaticProps()
와 함께 사용한다.)
Static Generation with data - Next.js
: 페이지의 콘텐츠가 외부 데이터에 의존하는 경우
e.g. 블로그에서 블로그 포스트 목록을 불러와야하는 경우
// TODO: 페이지가 사전 렌더링 되려면 API 엔드포인트를 이용해 'posts'를 가져와야 함
export default function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
);
}
사전 렌더링시 데이터를 가져오려면, 같은 파일 내에서 getStaticProps
라는 비동기(async) 함수를 export해야 한다.
이 함수는 빌드시 호출되어, 페이지가 사전 렌더링될 때 데이터를 가져와 페이지의 props로 데이터를 전달할 수 있게 한다.
export default function Blog({ posts }) {
// ...
}
// 아래의 getStaticProps 함수는 빌드시 호출된다.
export async function getStaticProps() {
const res = await fetch('https://.../posts'); // API 엔드포인트를 이용해 posts를 가져온다.
const posts = await res.json();
// { props: { posts } }를 리턴함으로써 Blog 컴포넌트는 posts를 props로 받게 된다.
return {
props: {
posts,
},
};
}
getStaticProps()
는 매개변수로 context
를 가질 수도 있는데,
만약 페이지가 pages/posts/[postId]
로 동적 라우팅이 되어있는 경우 params
속성을 이용해 파라미터를 얻을 수 있다.
export async function getStaticProps(context) {
const meetupId = context.params.postId;
// localhost:3000/posts/1로 접근한 경우 meetupId는 1
return {
props: {...}
};
}
: 페이지의 경로(path)가 외부 데이터에 의존하는 경우
Next.js를 사용하면 동적 경로를 가진 페이지를 만들 수 있다.
예를 들어 pages/posts/[id].js
라는 파일을 만들면, 동적인 값인 id
에 근거하여 페이지를 보여줄 수 있다. ➡️ 경로 posts/1
로 접근하면 id
가 1인 블로그 포스트를 보여주기
하지만 이때, 빌드시 어떤 id
를 사전 렌더링해야 할지는 외부 데이터에 따라 달라질 수 있다.
예를 들어 데이터베이스에 블로그 포스트가 1개만 있다면 빌드시 posts/1
만 사전 렌더링하면 된다.
이후 id
가 2인 두번째 포스트를 추가했다면 posts/2
또한 사전 렌더링해야할 것이다.
이렇게 사전 렌더링되는 페이지의 경로가 외부 데이터에 의존한다면, 해당 동적인 페이지에서 getStaticPaths()
라는 비동기 함수를 export하면 된다.
이 함수는 빌드시 호출되어 사전 렌더링할 경로(path)를 지정할 수 있다.
// 아래의 getStaticPaths는 빌드시 호출된다.
export async function getStaticPaths() {
const res = await fetch('https://.../posts'); // API 엔드포인트를 이용해 posts를 가져온다.
const posts = await res.json();
// 데이터(posts)에서 우리가 사전 렌더링하고 싶은 path를 추출한다.
const paths = posts.map((post) => ({
params: { id: post.id },
}));
// 빌드시 여기서 전달한 경로들만 사전 렌더링된다.
// { fallback: false }은 다른 경로는 404에러로 처리된다는 뜻이다.
return { paths, fallback: false };
}
이제 여기서 포스트에 대한 데이터를 가져올 수 있도록 getStaticProps()
도 사용할 수 있다.
export default function Post({ post }) {
// ...
}
export async function getStaticPaths() {
// ...
}
// id를 이용해 해당 id를 가진 블로그 포스트의 데이터를 가져온다.
export async function getStaticProps({ params }) {
const res = await fetch(`https://.../posts/${params.id}`);
const post = await res.json();
return { props: { post } };
}
데이터를 항상 최신으로 유지해야 하고, 페이지에 동적인 데이터를 표시해야하는 경우에는 정적 사이트 생성(SSG)보다 서버 사이드 렌더링(SSR)을 사용하는 게 더 적합하다.
만약, 외부 API에서 가져오는 자주 업데이트되는 데이터를 가진 페이지를 사전 렌더링해야 한다면, getServerSideProps()
를 이용해 데이터를 가져와서 페이지에 다음과 같이 전달할 수 있다.
export default function Page({ data }) { // 데이터를 props로 받는다.
// ...
}
// 아래의 getServerSideProps 함수는 매 요청(request)마다 호출된다.
export async function getServerSideProps() {
const res = await fetch(`https://.../data`); // 외부 API로부터 데이터를 가져온다.
const data = await res.json();
return { props: { data } }; // 데이터를 props로 전달한다.
}
getServerSideProps()
는 getStaticProps()
와 유사하지만, 빌드 시간이 아니라 모든 요청에서 실행된다는 점에서 차이가 있다.
또한, getServerSideProps()
는 매개변수로 context
객체를 전달받아 요청 객체 / 응답 객체에 접근할 수도 있다.
export async function getServerSideProps(context) {
const req = context.req; // 요청 객체 - 인증(authentication)이 필요한 경우
const res = context.res; // 응답 객체
//...
return { props: { data } };
}
정리하자면, Next.js에서는 서버 사이드 렌더링(SSR)과 정적 생성(SSG)을 통해 페이지를 사전 렌더링하여 웹 애플리케이션을 최적화할 수 있는데...
Next.js에서는 이 두 가지 방식을 조합하여 사용할 수 있으므로, 애플리케이션의 요구사항과 데이터의 업데이트 빈도에 따라 선택하여 최적화하면 된다.
오 그 전까진 SSG, SSR 어렴풋이 알고 잇었는데 쓸 때 좋은 상황을 알려주니 되게 이해가 되네요 잘보고 갑니당