Next.js는 총 4가지 렌더링 방식을 지원합니다. 각각의 특징과 사용법을 설명드리겠습니다.
SSG는 Static Site Generation으로, 빌드 타임에 미리 정적인 HTML 만들어 두는 방식입니다.
App Router에서는 fetch에서 기본 캐시(force-cache)를 사용하면, 그 라우트가 자동으로 정적 페이지로 빌드됩니다.
이런 방식은 블로그 글, 문서, 마케팅 페이지처럼 자주 바뀌지 않는 콘텐츠에 적합하고, 빠른 로딩 속도와 SEO에 강점이 있습니다.ISR은 Incremental Static Regeneration으로, SSG 페이지를 배포 후에도 일정 주기로 다시 생성할 수 있게 해주는 방식입니다.
라우트 파일 상단에revalidate를 설정하거나,fetch옵션으로revalidate주기를 설정해서 정적 페이지를 백그라운드에서 갱신합니다.
이렇게 하면 최초에는 SSG처럼 빠르게 응답하면서도, 몇 분에서 몇 시간 단위로 데이터가 변하는 리스트나 게시판에 최신 데이터를 반영할 수 있고, 매 요청마다 서버 렌더링을 하는 SSR보다 성능 면에서 이점이 있습니다.SSR은 Server-Side Rendering으로, 요청이 들어올 때마다 서버에서 데이터를 가져와 페이지를 렌더링하는 방식입니다.
라우트 파일 상단에dynamic을force-dynamic으로 설정하거나,fetch에서cache옵션을'no-store'로 주면 요청 시마다 데이터를 새로 가져오도록 만들 수 있습니다.
이런 방식은 항상 최신 데이터가 필요하거나, 로그인 사용자별 맞춤 정보처럼 요청마다 다른 데이터를 렌더링해야 하는 페이지에 적합합니다.
하지만 매 요청마다 서버에서 렌더링을 수행하므로 성능 부담이 있을 수 있습니다.CSR은 Client-Side Rendering으로, 페이지를 먼저 로드한 후 클라이언트에서 JavaScript를 이용해 데이터를 가져와 동적으로 렌더링하는 방식입니다.
"use client"가 선언된 클라이언트 컴포넌트 안에서useEffect, React Query, SWR 등을 사용해 API를 호출하면, 그 부분은 CSR로 동작합니다.
이 방식은 SEO보다는 사용자의 상호작용과 상태 관리가 중요한 대시보드나, 로그인 후 개인화된 정보가 필요한 페이지에 적합합니다.각 렌더링 방식은 프로젝트의 요구사항에 따라 선택적으로 적용할 수 있으며, Next.js는 이들을 조합하여 유연한 웹 애플리케이션을 개발할 수 있도록 지원합니다.
Next.js는 기본적으로 브라우저가 JS를 실행하기 전에 HTML을 먼저 만들어서 보내는 방식을 사용한다.
이 HTML을 미리 만드는 시점이 네 방식의 핵심 차이이다.
next build)SSG, ISR, SSR은 모두 서버에서 HTML + 브라우저에서 JS로 인터랙션 붙이는 2단계 구조를 쓴다.
이미 화면이 보이는 상태에서 React가 이벤트 핸들러와 상태를 입히는 과정을 hydration이라고 한다.
next build할 때별도 함수 없이도
fetch가 캐시 가능한 방식(기본값 또는 cache: 'force-cache')이면 해당 라우트가 정적 렌더링(SSG)으로 처리된다.
revalidate 시간이 지나면, 다음 요청이 들어오는 순간 백그라운드에서 새 HTML을 생성한다.export const revalidate = 60; // 페이지 단위await fetch('https://api.example.com/posts', {
next: { revalidate: 60 },
});revalidate 시간이 지나지 않으면 옛날 데이터(캐시)를 보여줄 수밖에 없다.revalidate 주기 설계 + 캐시/무효화 전략을 잘 잡아야 한다.export const dynamic = 'force-dynamic';await fetch('https://api.example.com/posts', {
cache: "no-store",
});app/ 또는 pages/ 안에서 "use client"가 선언된 컴포넌트 안에서useEffect, React Query, SWR 등을 통해 브라우저에서 fetch하면 사실상 CSR이다."use client";
import { useEffect, useState } from "react";
export default function ClientPage() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("/api/data")
.then(res => res.json())
.then(setData);
}, []);
if (!data) return <div>Loading...</div>;
return <div>{data.message}</div>;
}
이 경우 HTML은 거의 텅 빈 상태로 내려오고, 브라우저에서 JS 실행 후에야 실제 내용을 볼 수 있다.
| 방식 | HTML 생성 시점 | 데이터 신선도 | SEO | 서버 부하 | 대표 사용처 |
|---|---|---|---|---|---|
| SSG | 빌드 타임 | 빌드 시점 기준 (변경 시 재빌드 필요) | 매우 좋음 | 거의 없음(CDN) | 블로그, 문서, 랜딩 |
| ISR | 빌드 타임 + revalidate 주기마다 백그라운드에서 재생성 | 일정 주기마다 갱신 | 좋음 | 낮음 | 뉴스/리스트, 자주 갱신되는 컨텐츠 |
| SSR | 요청 시마다 서버 렌더링 | 항상 최신 | 좋음 | 높음 | 개인화 페이지, 실시간성 필요한 페이지 |
| CSR | 브라우저에서 JS로 렌더링 | 클라이언트 fetch 시점 기준 | 기본적으로 약함 | 서버 렌더링 부하는 없음 (API만) | 내부 대시보드, 앱형 UI |