
렌더링 전략 완전 이해 - SSG, ISR, PPR, Cache Components
Next.js App Router에서 렌더링 전략은 단순한 기술 선택이 아니라,
서비스의 성능, 사용자 경험, 데이터 최신성을 결정하는 핵심 설계 요소입니다.
특히 최근에는 페이지 단위가 아니라
컴포넌트 단위로 렌더링 전략을 조합하는 방식이 중요해졌습니다.
이 글에서는 각 전략을 단순 개념이 아니라
실제 서비스에서 어떻게 활용되는지 중심으로 설명드리겠습니다.
SSG — 변하지 않는 영역을 담당하는 전략
SSG는 빌드 시점에 HTML을 미리 생성하여 사용자에게 제공합니다.
즉, 요청 시 서버 연산이 발생하지 않으며, 이미 만들어진 결과를 전달하는 방식입니다.
이 방식은 특히 변하지 않는 콘텐츠에서 강력한 성능을 제공합니다.
예를 들어 블로그 서비스에서는 다음과 같은 페이지가 있습니다.
- 블로그 글 목록
- 게시글 상세 페이지
- 회사 소개 페이지
이러한 데이터는 자주 변경되지 않기 때문에 매 요청마다 서버에서 렌더링할 필요가 없습니다.
// app/posts/page.tsx
async function getPosts() {
const res = await fetch("https://api.example.com/posts");
return res.json();
}
export default async function Page() {
const posts = await getPosts();
return (
<main>
<h1>블로그 목록</h1>
<ul>
{posts.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
);
}
이 경우 페이지는 빌드 타임에 생성되며, 사용자에게는 매우 빠르게 전달됩니다.
SSG의 가장 큰 장점은 빠른 응답 속도와 서버 부하 감소입니다.
반면 데이터가 변경되면 재배포가 필요하다는 한계가 있습니다.
데이터가 거의 변하지 않는 페이지라면 SSG가 가장 적합한 선택입니다.
ISR - 정적이지만 최신성을 유지하는 전략
ISR은 SSG의 한계를 보완하기 위해 등장한 전략입니다.
정적 페이지를 유지하면서도 일정 주기로 자동 갱신이 이루어집니다.
쇼핑몰 상품 목록을 예로 들어보겠습니다.
상품은 자주 변경되지만, 실시간으로 변경될 필요까지는 없습니다.
이 경우 ISR을 적용하면 성능과 최신성을 동시에 확보할 수 있습니다.
async function getProducts() {
const res = await fetch("<https://api.example.com/products>", {
next: { revalidate: 60 },
});
return res.json();
}
이 설정은 60초마다 페이지를 재생성하도록 합니다.
처음 요청에서는 정적 페이지가 제공되고, 재검증 시간이 지난 후 요청이 들어오면 기존 페이지를 그대로 응답하면서 백그라운드에서 새로운 페이지를 생성합니다.
이 방식의 핵심은 사용자 경험을 해치지 않으면서 데이터를 업데이트한다는 점입니다.
데이터가 주기적으로 변경되지만 완전한 실시간성이 필요하지 않은 경우 ISR이 적합합니다.
PPR - 하나의 페이지를 나누어 렌더링하는 전략
PPR은 Next.js에서 매우 중요한 개념으로,
하나의 페이지를 정적인 부분과 동적인 부분으로 나누어 렌더링하는 방식입니다.
기존에는 페이지 전체를 SSR 또는 SSG 중 하나로 처리해야 했지만,
이제는 한 페이지 안에서도 서로 다른 전략을 사용할 수 있습니다.
SNS 홈 화면을 예로 들어보겠습니다.
- 헤더와 레이아웃은 변하지 않는 정적 영역
- 사용자 피드는 실시간으로 변하는 동적 영역
이 두 영역을 동일한 방식으로 처리하는 것은 비효율적입니다.
import { Suspense } from "react";
function Header() {
return <header>고정 헤더</header>;
}
async function Feed() {
const res = await fetch("https://api.example.com/feed", {
cache: "no-store",
});
const data = await res.json();
return (
<ul>
{data.map((item: any) => (
<li key={item.id}>{item.content}</li>
))}
</ul>
);
}
export default function Page() {
return (
<div>
<Header />
<Suspense fallback={<div>피드 불러오는 중...</div>}>
<Feed />
</Suspense>
</div>
);
}
이 구조에서는 정적인 헤더는 즉시 렌더링되고,
피드 데이터는 이후에 스트리밍 방식으로 로드됩니다.
이로 인해 초기 화면 표시 속도가 빨라지고, 사용자 체감 성능이 크게 개선됩니다.
정적과 동적 데이터가 함께 존재하는 페이지에서는 PPR이 매우 효과적입니다.
Cache Components - 데이터 단위 캐싱 전략
기존의 ISR은 페이지 단위로 캐싱이 이루어졌습니다.
하지만 실제 서비스에서는 특정 데이터만 갱신하고 싶은 경우가 많습니다.
Cache Components는 이러한 요구를 해결하기 위한 전략입니다.
페이지가 아닌 함수 또는 데이터 단위로 캐싱을 제어할 수 있습니다.
대시보드를 예로 들어보겠습니다.
- 사용자 정보는 거의 변하지 않음
- 알림은 자주 변함
- 통계 데이터는 일정 주기로 변경됨
이 모든 데이터를 하나의 캐싱 전략으로 처리하는 것은 비효율적입니다.
"use cache";
export async function getUser() {
return fetch("<https://api.example.com/user>").then(res => res.json());
}
또는 tag 기반 캐싱도 가능합니다.
import { cacheTag } from "next/cache";
export async function getNotifications() {
cacheTag("notifications");
return fetch("<https://api.example.com/notifications>").then(res =>
res.json()
);
}
데이터가 변경되었을 때는 다음과 같이 특정 캐시만 무효화할 수 있습니다.
import { revalidateTag } from "next/cache";
export async function markAsRead() {
await fetch("...");
revalidateTag("notifications");
}
이 방식은 필요한 데이터만 선택적으로 갱신할 수 있기 때문에,
복잡한 서비스에서 매우 강력한 성능 최적화를 제공합니다.
실제 서비스에서는 어떻게 조합될까
실제 서비스에서는 하나의 전략만 사용하는 경우는 거의 없습니다.
여러 전략을 함께 사용하는 것이 일반적입니다.
SNS 서비스를 예로 들어보겠습니다.
- 헤더는 SSG로 처리
- 추천 친구 목록은 ISR로 처리
- 피드는 PPR로 처리
- 알림은 Cache Components로 관리
export default function Page() {
return (
<div>
<Header />
<Suspense fallback={<Skeleton />}>
<Feed />
</Suspense>
<Recommendations />
<Notifications />
</div>
);
}
이처럼 하나의 페이지 안에서도 각 영역의 특성에 따라
렌더링 전략을 다르게 적용할 수 있습니다.
어떤 전략을 선택해야 할까
렌더링 전략을 선택할 때 가장 중요한 기준은
“데이터의 특성”과 “사용자 경험”입니다.
단순히 기술 기준이 아니라,
이 데이터가 얼마나 자주 바뀌는지,
사용자가 얼마나 빠르게 화면을 봐야 하는지를 기준으로 판단해야 합니다.
먼저 데이터가 거의 변하지 않는 경우입니다.
예를 들어 회사 소개 페이지나 블로그 글처럼
콘텐츠가 정적으로 유지되는 경우라면 SSG가 가장 적합합니다.
빌드 시점에 미리 생성된 HTML을 제공하기 때문에
가장 빠르고 안정적인 응답을 보장할 수 있습니다.
데이터가 주기적으로 변경되는 경우라면 ISR을 고려할 수 있습니다.
상품 목록이나 랭킹 페이지처럼
“완전한 실시간성은 필요 없지만 최신 상태는 유지해야 하는 경우”에 적합합니다.
사용자는 항상 빠른 응답을 받으면서도, 백그라운드에서 데이터가 자연스럽게 갱신됩니다.
하나의 화면에 정적 요소와 동적 요소가 함께 존재한다면
PPR이 가장 효과적인 선택이 됩니다.
헤더, 레이아웃과 같이 변하지 않는 영역은 즉시 보여주고,
피드나 댓글처럼 실시간 데이터는 이후에 로드하는 방식입니다.
이 방식은 초기 렌더링 속도와 사용자 체감 성능을 동시에 개선합니다.
마지막으로 데이터마다 갱신 주기가 다르거나 특정 데이터만 선택적으로 업데이트해야 하는 경우라면
Cache Components를 사용하는 것이 좋습니다.
페이지 단위가 아니라 데이터 단위로 캐싱을 제어할 수 있기 때문에
복잡한 서비스에서 매우 유연한 구조를 만들 수 있습니다.
결국 중요한 것은 하나의 전략을 선택하는 것이 아니라,
각 데이터의 성격에 맞게 전략을 조합하는 것입니다.
마무리
Next.js의 렌더링 전략은 단순히 “빠르게 만드는 기술”이 아닙니다.
어떤 데이터를 언제, 어떤 방식으로 사용자에게 보여줄 것인지 결정하는 서비스 설계의 핵심 요소입니다.
과거에는 페이지 단위로 SSR 또는 CSR을 선택하는 것이 전부였다면,
지금은 컴포넌트 단위로 렌더링 전략을 조합하는 시대가 되었습니다.
하나의 페이지 안에서도 SSG, ISR, PPR, Cache 전략이 동시에 사용됩니다.
이 변화는 단순한 기술의 발전이 아니라 “사용자 경험을 더 정교하게 설계할 수 있는 도구가 생겼다”는 의미입니다.
따라서 렌더링 전략을 선택할 때는 기술 자체에 집중하기보다 다음 질문을 먼저 던져보는 것이 중요합니다.
- 이 데이터는 얼마나 자주 변하는가
- 사용자는 이 정보를 얼마나 빨리 봐야 하는가
- 이 영역은 초기 렌더링에 반드시 필요한가
이 질문에 대한 답이 곧 어떤 렌더링 전략을 선택해야 하는지에 대한 답이 됩니다.
이 관점을 기반으로 설계를 시작하신다면, 단순히 빠른 서비스가 아니라
“의도적으로 빠른 서비스”를 만들 수 있게 됩니다.
감사합니다!
'Next.js' 카테고리의 다른 글
| Next.js 스타일링 전략: Tailwind CSS, shadcn/ui, 그리고 CSS-in-JS (0) | 2026.05.17 |
|---|---|
| Next.js에서의 인증 전략과 쿠키 vs 토큰 (0) | 2026.05.10 |
| Next.js 데이터 페칭과 Server Actions (2) | 2026.04.09 |
| Next.js App Router 라우팅 & Proxy 완벽 이해하기 (0) | 2026.03.17 |
| Next.js에서 SSR vs CSR 제대로 이해하기 (0) | 2026.03.12 |