Next.js 정적 사이트 생성(SSG) & 서버 사이드 렌더링(SSR)

지은·2023년 5월 8일
5

NEXT.js

목록 보기
3/6

사전 렌더링 (Pre-rendering)

Next.js는 서버 측 렌더링(SSR)을 지원하여 기본적으로 모든 페이지를 사전에 렌더링(pre-render)한다.
서버 사이드 렌더링은 페이지의 HTML 마크업을 미리 생성하여 클라이언트에 전달하는 방식이다.

이로 인해 Next.js 애플리케이션은 브라우저로 전송되는 시점에 사전에 렌더링된 HTML과 생성된 HTML에 필요한 최소한의 JavaScript 코드와 함께 을 로드한다. 이후 클라이언트 측에서 JavaScript 코드가 페이지를 완성하기 위해 실행되며, 이를 통해 상호작용 및 동적인 기능을 구현할 수 있다.

이러한 사전 렌더링 방식은 퍼포먼스 향상과 검색 엔진 최적화(SEO)에 도움을 준다.

  • 사전에 렌더링된 페이지는 초기 로딩 속도가 빠르며,
  • 검색 엔진은 HTML 콘텐츠를 쉽게 읽고 색인화(indexing)할 수 있다.
  • 또한, 사용자가 페이지를 더 빠르게 볼 수 있으므로 사용자 경험도 향상된다.

Next.js는 클라이언트 측 렌더링도 지원하며, 정적 사이트 생성(Static Site Generation) 기능도 제공한다. 이를 통해 동적인 콘텐츠와 정적인 콘텐츠를 효과적으로 조합하여 유연하고 성능이 우수한 웹 애플리케이션을 구축할 수 있다.

간단 정리

  • 클라이언트 측에서 JavaScript를 사용해 페이지를 동적으로 렌더링을 하는게 아니라, 서버에서 HTML 파일을 사전에 생성하여 클라이언트에게 전달한다.
  • 생성된 HTML에 필요한 최소한의 JavaScript 코드가 함께 전송되고, 클라이언트는 이를 로드하고 실행하여 완전한 페이지를 표현한다. (Hydration)
  • 이렇게 pre-render 된 페이지는 퍼포먼스 향상 & SEO에 더 좋다.

Next.js의 사전 렌더링(pre-rendering)은 두 가지 방식으로 이루어지며,
페이지별로 정적 생성을 할지 서버 사이드 렌더링을 할지 결정할 수 있다.

  1. 정적 생성 (Static Generation)
  2. 서버 사이드 렌더링 (Server-side Rendering)

두 방식의 차이점은 언제 HTML을 생성하느냐 이다.


1. 정적 사이트 생성(SSG, Static Site Generation)

: 빌드 시점에 페이지의 HTML을 생성하는 사전 렌더링 방식

  • 즉, next build 명령어를 실행할 때 페이지의 HTML이 생성된다.
  • 이렇게 생성된 HTML 파일은 유저들이 요청할 때마다 재사용된다.
  • 빌드 시점에 만들어둔 HTML 파일을 CDN(Content Delivery Network)을 통해 제공함으로써 성능을 향상시킨다.
  • 마케팅 페이지, 블로그 게시물, 포트폴리오, 제품 목록, 도움말/문서 등과 같이 유저가 요청을 보내기 전에 페이지를 미리 만들어두어도 되는 경우에 사용한다.

2. 서버 사이드 렌더링 (SSR, Server Side Rendering)

: 요청이 있을 때마다 서버에서 해당 페이지의 HTML을 동적으로 생성하여 보내주는 방식

  • 항상 최신 상태를 유지해야 하는 경우 적합하다.
  • 관리자 페이지, 분석 차트 등 동적인 콘텐츠가 필요한 경우 서버 사이드 렌더링을 선택할 수 있다.

1. 정적 생성 (SSG)

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

1) getStaticProps()

: 페이지의 콘텐츠가 외부 데이터에 의존하는 경우

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,
    },
  };
}

context

getStaticProps()는 매개변수로 context를 가질 수도 있는데,
만약 페이지가 pages/posts/[postId]로 동적 라우팅이 되어있는 경우 params 속성을 이용해 파라미터를 얻을 수 있다.

export async function getStaticProps(context) {
	const meetupId = context.params.postId; 
  // localhost:3000/posts/1로 접근한 경우 meetupId는 1
  
	return {
		props: {...}
	};
}

2) getStaticPaths()

: 페이지의 경로(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 } };
}

2. 서버 사이드 렌더링 (SSR)

데이터를 항상 최신으로 유지해야 하고, 페이지에 동적인 데이터를 표시해야하는 경우에는 정적 사이트 생성(SSG)보다 서버 사이드 렌더링(SSR)을 사용하는 게 더 적합하다.

Server-side Rendering (SSR)

getServerSideProps()

만약, 외부 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()와 유사하지만, 빌드 시간이 아니라 모든 요청에서 실행된다는 점에서 차이가 있다.

context

또한, getServerSideProps()는 매개변수로 context 객체를 전달받아 요청 객체 / 응답 객체에 접근할 수도 있다.

export async function getServerSideProps(context) {
  const req = context.req; // 요청 객체 - 인증(authentication)이 필요한 경우
  const res = context.res; // 응답 객체
  
  //...
  return { props: { data } };
}

정리

정리하자면, Next.js에서는 서버 사이드 렌더링(SSR)과 정적 생성(SSG)을 통해 페이지를 사전 렌더링하여 웹 애플리케이션을 최적화할 수 있는데...

서버 사이드 렌더링을 사용하면 좋은 경우

1. 요청 객체에 접근해야 하는 경우

  • 사용자의 인증 정보, 쿠키, 세션 등의 요청 관련 정보를 필요로 하는 페이지
  • 요청에 따라 콘텐츠가 변경되어야 하는 페이지

2. 데이터가 자주 업데이트되는 경우

  • 데이터가 빈번하게 변경되는 경우
    ➡️ 서버 사이드 렌더링을 통해 항상 최신 데이터를 적용할 수 있다.

정적 생성을 사용하면 좋은 경우

1. 데이터가 자주 업데이트되지 않은 경우

  • 데이터가 자주 변경되지 않는 경우
    ➡️ 초기에 한 번 렌더링된 정적 파일을 재사용하며 성능과 로딩 시간을 개선할 수 있다.

2. 요청 객체에 접근하지 않아도 되는 경우

  • 페이지의 내용이 요청과 무관하게 동일하게 유지되는 경우
    ➡️ 요청 객체에 접근할 필요가 없으므로 정적 생성을 사용할 수 있다.
  • e.g. 제품 카탈로그, 블로그 게시물 등은 데이터가 자주 변경되지 않고, 동일한 콘텐츠를 여러 사용자에게 제공할 수 있으므로 정적 생성을 사용하는 것이 적합하다.

Next.js에서는 이 두 가지 방식을 조합하여 사용할 수 있으므로, 애플리케이션의 요구사항과 데이터의 업데이트 빈도에 따라 선택하여 최적화하면 된다.

profile
블로그 이전 -> https://janechun.tistory.com

4개의 댓글

comment-user-thumbnail
2023년 5월 14일

오 그 전까진 SSG, SSR 어렴풋이 알고 잇었는데 쓸 때 좋은 상황을 알려주니 되게 이해가 되네요 잘보고 갑니당

답글 달기
comment-user-thumbnail
2023년 5월 14일

서버 사이드 렌더링만 어느 정도 알고 있었는데 이렇게 잘 정리해주시다니 !! 잘 보고 갑니다 !

답글 달기
comment-user-thumbnail
2023년 5월 14일

덕분에 복습이 되었습니다 화이팅하세요!

답글 달기
comment-user-thumbnail
2023년 5월 14일

오늘도 깔끔한 정리!‘

답글 달기