본문 바로가기
💻 웹 개발/Frontend

Next.js 에서 알아보는 4가지의 렌더링 방식 (CSR, SSG, ISR, SSR)

by llddang 2025. 3. 7.

Next.js는 리액트 기반의 웹 프레임워크로, 다양한 렌더링 방식을 제공한다.

오늘은 Next.js 14 버전부터 등장한 App Router를 기반으로 CSR, SSG, ISR, SSR를 적용하는 방법에 대해 알아볼 것이다.

 

 

초기 설정

앞으로의 예시에서 실제 데이터환경을 구성하여 렌더링 방식이 어떻게 동작하는지 확인할 것 이다.

json-server를 사용하여 간단한 API 서버를 구축하고, 이를 통해 Next.js의 다양한 렌더링 방식에서 데이터를 가져오는 과정을 살펴볼 것이다.

 

1. Next.js 프로젝트 생성

$ npx create-next-app@15.2.1 [project_name]

설정 옵션으로는 app router를 사용하였고, 이 외에는 자유롭게 선택해도 된다.

 

2. json-server 설치 및 설정

$ npm install -D json-server

프로젝트에 json-server를 설치하고, 루트 경로에 db.json 파일을 생성하고 다음과 같이 샘플 데이터를 추가하였다.

{
  "posts": [{ "id": 1, "title": "json-server", "author": "llddang" }]
}

 

이후 손 쉬운 사용을 위해 package.json에 script를 추가하였다.

"scripts": {
  ...
  "server": "json-server --watch db.json --port 4000"
}

 

이제 렌더링 방식에 대해서 알아보자!

 

 

Client Side Rendering (CSR)

브라우저에서 JavaScript를 이용해 동적으로 페이지를 렌더링하는 방식
순수 React를 사용했을 때는 100% CSR로 렌더링된다.

Next.js에서 최상단에 "use client" 라고 선언해주면 해당 컴포넌트는 Client Component가 되고, CSR 방식으로 렌더링이 된다.

따라서 React에서 사용했던 방식대로 useState와 useEffect를 통해서 api를 호출하고 값을 관리하면 된다!

 

"use client";

import { useEffect, useState } from "react";

async function fetchPosts() {
  const res = await fetch("http://localhost:4000/posts");
  const data = await res.json();
  return data;
}

export default function CSRPage() {
  const [post, setPost] = useState([]);

  useEffect(() => {
    fetchPosts().then((data) => {
      setPost(data);
      console.log("csr - render");
    });
  }, []);

  return (
    <main>
      <h1>CSR PAGE</h1>
      {JSON.stringify(post)}
    </main>
  );
}

 

CSR 방식은 초기 HTML을 빈 상태로 전송된다. 이후, 브라우저에서 JavaScript가 실행된 후에 데이터를 가져와 렌더링한다.
따라서 페이지가 렌더링 되었을 당시 post의 값이 없고, JavaScript가 실행된 이후 post의 값이 생성됨으로 처음에는 빈 화면이었다가 이후에, post의 값이 나타나는 것을 볼 수 있다.

또한 개발자 도구의 콘솔에 "csr - render-"가 새로고침할 때 마다 표시되는 것을 볼 수 있다.

이는 페이지가 로드될때마다 클라이언트 측에서 데이터를 가져와 렌더링하고 있다는 것을 알 수 있다.

따라서 사용자가 페이지에 접속할 때마다 항상 최신 정보를 보여줄 수 있다.

 

  • 장점
    • 최초 한번 로드가 끝나면 사용자와의 상호작용이 빠르고 부드럽다.
    • 서버에게 추가적인 요청을 보낼 필요가 없기 때문에, 사용자 경험이 좋다.
    • 서버에서 렌더링이 이루어지지 않으므로 부하가 적다.
  • 단점
    • 첫 페이지 로딩 시간(TTV: Time-To-View)이 길 수 있다.
    • JavaScript가 로딩 되고 실행될 때까지 페이지가 비어있어 검색 엔진 최적화(SEO)에 불리하다.

 

 

Static Site Generation (SSG)

서버에서 페이지를 렌더링하여 클라이언트에게 HTML을 전달하는 방식
이름에서 처럼 사전에 미리 정적 페이지를 만들어놓는다. 이후 클라이언트에서 홈페이지를 요청하면 서버에서 만들어져있는 사이트를 바로 제공하여, 브라우저에서는 UI를 띄워주면 된다.

Next.js에서 컴포넌트에 async 키워드를 사용하여 컴포넌트 내부에서 API를 호출할 수 있다.
fetch를 통해 API를 호출할 때 아무 옵션을 지정하지 않거나 { cache: "force-cache" } 옵션을 사용하면 해당 페이지는 SSG(Static Site Generation) 방식으로 렌더링된다.

 

async function fetchPosts() {
  const res = await fetch("http://localhost:4000/posts", { cache: "force-cache" });
  const data = await res.json();
  return data;
}

export default async function SSGPage() {
  const posts = await fetchPosts();
  console.log("ssg - render");

  return (
    <main>
      <h1>SSG PAGE</h1>
      {JSON.stringify(posts)};
    </main>
  );
}

 

개발자모드로 실행하게 되면 페이지를 정적으로 미리 빌드하지 않고, 매 요청마다 서버에서 렌더링하게 된다.
따라서 해당 렌더링을 확인하기 위해서는 프로젝트를 빌드하여 실행해주어야한다.

 

npm run build && npm run start 를 통해서 프로젝트를 빌드 및 실행해주면,
페이지를 빌드 단계에서 실행하여 생성하기 때문에 터미널에 "ssg - render" 가 뜨는 것을 볼 수 있다.

이후 페이지에 들어가 아무리 렌더링하여도 이후에 "ssg - render"가 뜨는 것은 볼 수 없다.

 

  • 장점
    • 첫 페이지 로딩 시간(TTV)이 매우 짧아 사용자가 빠르게 페이지를 볼 수 있다. 또한, SEO에 유리하다.
    • CDN(Content Delivery Network) 캐싱 가능하다
  • 단점
    • 정적인 데이터에만 사용할 수 있다.
    • 사용자와의 상호작용이 서버와의 통신에 의존하므로, 클라이언트 사이드 렌더링보다 상호작용이 느릴 수 있다.
      또한, 서버 부하가 클 수 있다.
    • 마이페이지 처럼 데이터에 의존하여 화면을 그려주는 경우 사용이 어렵다.

 

 

 

Incremental Static Regeneration (ISR)

SSG 처럼 서버에서 페이지를 렌더링하여 클라이언트에게 HTML을 전달하는 방식
차이점은 설정한 주기마다 페이지를 재생성하여 새로운 페이지를 전달한다.
즉, SSG는 빌드 타임에 페이지가 생성된 후 더 이상 수정되지 않지만, ISR은 특정 주기마다 페이지를 다시 생성한다.

특정 주기마다 페이지를 다시 생성하기 위해서는 fetch를 통해 API를 호출할 때 옵션으로 { next: { revalidate: 2 } }를 넣어주면 된다. revalidate의 단위는 초이며, 이 예시에서 해당 API의 stale time(유효 시간)은 2초이다.
이 시간이 지나 데이터가 신선하지 않을 때 해당 페이지를 방문하면, 시스템은 새로운 값을 불러와 페이지를 다시 렌더링한다.

 

async function fetchPosts() {
  const res = await fetch("http://localhost:4000/posts", { next: { revalidate: 2 } });
  const data = await res.json();
  return data;
}

export default async function ISRPage() {
  const posts = await fetchPosts();
  console.log("isr - render");

  return (
    <main>
      <h1>ISR PAGE</h1>
      {JSON.stringify(posts)};
    </main>
  );
}

 

이 페이지도 빌드를 통해 실행하면 빌드 시점에 초기 정적 페이지를 생성하기 때문에, 빌드 단계에서 "isr - render"가 출력되는 것을 볼 수 있다. 
페이지를 아무리 자주 새로고침해도 터미널에서는 설정한 주기인 2초에 한 번씩 "isr - render" 이 출력되고 렌더링된다는 것을 확인할 수 있다.

 

  • 장점
    • 정적 페이지를 먼저 제공하므로 사용자 경험이 좋으며, 콘텐츠가 변경되었을 때 서버에서 페이지를 재생성하므로 그나마 최신 상태를 유지할 수 있다.
    • CDN(Content Delivery Network) 캐싱 가능하다
  • 단점
    • 실시간 페이지가 아니기 때문에, 동적인 콘텐츠를 다루기에 한계가 있다.
    • 마이페이지 처럼 데이터에 의존하여 화면을 그려주는 경우 사용이 어렵다.

 

 

 

Server Side Rendering (SSR)

SSG 처럼 서버에서 페이지를 렌더링하여 클라이언트에게 HTML을 전달하는 방식이지만, SSR은 매 요청마다 서버에서 페이지를 새롭게 렌더링하여 전달한다.
즉, SSG나 ISR은 빌드 타임이나 특정 주기에 페이지를 생성하지만, SSR은 사용자가 페이지를 요청할 때마다 서버에서 새롭게 페이지를 생성한다.

매 요청마다 페이지를 새롭게 생성하기 위해서는 fetch를 통해 API를 호출할 때 옵션으로 { cache: 'no-store' }를 넣어주면 된다.
이 옵션을 통해 데이터를 캐싱하지 않고 매 요청마다 새로운 데이터를 가져오도록 한다.

 

async function fetchPosts() {
  const res = await fetch("http://localhost:4000/posts", { cache: "no-store" });
  const data = await res.json();
  return data;
}

export default async function SSRPage() {
  const posts = await fetchPosts();
  console.log("ssr - render");

  return (
    <main>
      <h1>SSR PAGE</h1>
      {JSON.stringify(posts)};
    </main>
  );
}

 

이 페이지는 Dynamic 페이지로 생성되어, 사용자가 페이지에 접근할 때마다 서버에서 "ssr - render"가 출력되며 새롭게 렌더링된다.
페이지를 새로고침할 때마다 터미널에서 "ssr - render"가 출력되는 것을 확인할 수 있다.

 

  • 장점
    • 항상 최신 데이터로 페이지가 렌더링되므로 실시간성이 중요한 페이지에 적합하다.
    • 사용자별 맞춤 콘텐츠(로그인된 사용자의 정보 등)를 제공할 수 있다.
    • 마이페이지처럼 사용자 데이터에 의존하는 페이지에 적합하다.
  • 단점
    • 매 요청마다 서버에서 렌더링하므로 서버 부하가 증가한다.
    • 페이지 로드 시간이 데이터 페칭 시간에 의존하므로 느릴 수 있다.
    • CDN(Content Delivery Network) 캐싱이 불가능하다.

 

 

렌더링 방식 비교 및 선택 가이드

렌더링 방식 데이터 가져오기 렌더링 시점 SEO 성능 최신 데이터 적합한 사용 사례

렌더링 방식 데이터 가져오기 렌더링 시점 SEO 성능 최신상태
SSR cache: "no-store" 요청 시 서버에서 우수 중간 항상 최신
SSG cache: "force-cache" 또는 기본값 빌드 시 우수 매우 좋음 빌드 시점
CSR 클라이언트에서 fetch 클라이언트에서 불리 초기 로딩 느림 항상 최신
ISR next: { revalidate: 시간(초) } 빌드 시 + 주기적 갱신 우수 좋음 주기적 갱신

 

 

결론

Next.js 14 이후 버전에서는 데이터 페칭 옵션을 통해 다양한 렌더링 방식을 쉽게 구현할 수 있게 되었다.
각 렌더링 방식은 고유한 장단점을 가지고 있으며, 애플리케이션의 요구사항에 맞게 적절한 방식을 선택하거나 혼합하여 사용하는 것이 중요할 것이다.

  • SSR은 항상 최신 데이터가 필요한 동적 페이지에 적합하다.
  • SSG는 변경이 적은 정적 콘텐츠에 최적화되어 있다.
  • CSR은 풍부한 사용자 인터랙션이 필요한 애플리케이션에 유용하다.
  • ISR은 정적 생성의 성능 이점과 최신 데이터 제공의 균형을 유지해야 할 때 사용한다.

Next.js의 다양한 렌더링 방식을 적절히 활용하면 성능, SEO, 사용자 경험을 모두 최적화하는 웹 애플리케이션을 구축할 수 있다!

 

 

 

참고 자료