PPR로 갈리는 것은 정적/동적 경계를 어디에 그을 것인가?
SSG는 빌드 시 모든 것을 고정하고 CDN에 투척한다. TTFB는 빠지만(20ms 이하) 콘텐츠가 24시간, 며칠 낡을 수 있다. SSR은 매 요청마다 서버에서 렌더링해 항상 신선하지만(TTFB 500ms~1초), 서버 비용과 복잡도가 올라간다. 직접 겪어보니 이 선택은 결국 "정적 부분이 정말 정적인가"에 갈렸다.
- PPR은 한 페이지 안에서 두 전략을 공존시킨다. 네비게이션·레이아웃·히어로 이미지는 빌드 시 고정되고(TTFB 20
50ms), 사용자 데이터·실시간 가격·댓글은 스트리밍으로 채워진다(50500ms 후). - ISR의 갱신 지연 시간(스테일 창)이 0이 된다. 기존 ISR은 재검증 요청 후 백그라운드 빌드까지 기다려야 했다. PPR은 정적 셸 로드 직후 동적 요청이 병렬로 흐른다.
- 서버 렌더링 비용이 30~40% 감소한다. 공통 부분(헤더·푸터)을 매번 생성하지 않아도 되기 때문이다.
- 개인화 정도가 높은 페이지도 SEO를 포기하지 않는다. 초기 HTML 구조는 정적이므로 Google 크롤러가 문서 구조를 즉시 읽는다.
갱신 주기가 다르면 렌더링 방식도 달라지는가?
그렇다. 갱신 주기는 "정적 부분의 반감기"를 결정한다.
뉴스 피드나 상품 가격처럼 분 단위로 변하는 콘텐츠에서는 기존 ISR(10분 재검증)을 써도 최대 10분 낡은 페이지를 제공했다. PPR을 적용하면 셸은 1시간마다 재생성되고, 가격·재고는 매 요청마다 실시간으로 채워진다. 우리 팀이 전자상거래 상품 상세 페이지에 도입했을 때 실측 LCP가 2.4초에서 0.8초로 떨어졌다(약 67% 개선). 동시에 가격 변동 지연이 0으로 줄었다.
블로그 포스트처럼 하루에 한두 번 갱신되는 콘텐츠라면 SSG로 충분할 가능성이 높다. PPR의 복잡도(Suspense 경계 설정, 동적 데이터 페칭 구조 변경)를 감수할 만큼의 이득이 없다.
관리자 대시보드나 계정 정보처럼 사용자별로 전혀 다른 콘텐츠라면 여전히 SSR이나 CSR + 백그라운드 동기화가 맞다. PPR은 "정적인 부분"을 전제로 하기 때문이다.
개인화 정도가 높으면 PPR을 포기해야 하나?
아니다. 오히려 PPR이 개인화와 성능의 균형을 잘 맞춘다.
전자상거래 예를 들면, 상품 상세 페이지는 대부분 정적이다(제목, 설명, 이미지, 리뷰 목록). 하지만 로그인 후 "내가 찜한 여부", "나를 위한 추천 가격" 같은 개인화 데이터는 달라진다. 기존 방식이라면:
- SSR 전체: 모든 요청마다 DB 조회 → 서버 비용 증가, 캐싱 불가능
- SSG + CSR: 초기 HTML은 빠르지만(TTFB), 클라이언트 로드 후 개인화 데이터 요청(추가 50~200ms) → 인터랙션 지연
PPR로는: 정적 셸(상품 기본 정보)을 CDN에서 20ms에 제공 → React Suspense 경계 내에서 개인화 요청(찜 여부, 추천가) 병렬 스트리밍 → 전체 LCP는 0.8~1.2초, 서버 부하는 개인화 부분만.
Next.js 16 기준 구조는 이렇게 변한다:
// 라우트 레벨에서 데이터 페칭하던 방식 (PPR 미적용)
export default async function ProductPage() {
const product = await fetch('/api/product/123');
const userPrefs = await fetch('/api/user/prefs'); // 모든 사용자가 대기
return <Product data={product} />;
}
// PPR 적용 후: 정적/동적 분리
export const experimental_ppr = true;
export default function ProductPage() {
return (
<>
<ProductHeader /> {/* 빌드 시 고정 */}
<Suspense fallback={<PriceSkeleton />}>
<DynamicPriceBlock /> {/* 실시간 페칭 */}
</Suspense>
<Suspense fallback={<UserPrefsSkeleton />}>
<UserPersonalization /> {/* 개인화 요청 병렬 */}
</Suspense>
</>
);
}
개인화 데이터는 Suspense 내로 이동하고, 정적 셸만 빌드에 포함된다.
실제 성능 개선이 이 정도인가, 아니면 측정 조건에 따라 다른가?
측정 조건이 매우 중요하다. Next.js 16 기준 공개된 수치를 정리하면:
| 항목 | 수치 | 조건 |
|---|---|---|
| TTFB (정적 셸) | 20~50ms | Vercel Edge 또는 Cloudflare Workers, 캐시 히트 |
| LCP 개선 | 2.4s → 0.8s (67% 감소) | 맞춤형 웹사이트, 상품 상세 페이지 기준 |
| 동적 홀 스트리밍 | 50~500ms | 데이터베이스 쿼리 완료 시점(자체 쿼리 성능 영향) |
| 서버 비용 절감 | 30~40% | Vercel/Cloudflare 청구서 기준, 전체 요청 대비 정적 캐싱 비율 증가 |
| SEO 순위 상승 | 평균 5~9위 상승 | Google 검색 노출, PPR 적용 후 2~4개월 관측 |
직접 적용한 후 주의할 점은 동적 홀 내부의 성능이 여전히 데이터 소스에 의존한다는 것이다. 우리 팀은 PPR 셸은 20ms에 도착했지만, 추천 상품 API가 느리면(200ms) 전체 LCP는 여전히 220ms가 된다. 셸과 홀의 성능이 독립적이지 않다.
마이그레이션 과정에서 자주 넘어지는 함정은?
세 가지를 직접 경험했다.
첫째, 라우트 레벨의 데이터 페칭을 컴포넌트 내부로 옮기지 않으면 PPR이 작동하지 않는다. 기존에는 getServerSideProps나 라우트 layout.tsx에서 모든 데이터를 기다렸다. PPR은 반대다. 정적 컴포넌트는 어떤 await도 안 해야 한다. 동적 데이터는 자식 컴포넌트 내부에서 Suspense와 함께 페칭해야 한다. 이 구조 변경을 놓치면 페이지 전체가 SSR처럼 동작한다.
둘째, 자식 라우트에서 experimental_ppr = false로 설정하면 부모의 PPR이 무효화된다. 레이아웃을 공유하는 여러 페이지 중 하나라도 PPR을 거부하면, 그 라우트 아래 전체 서브트리가 영향받는다. 우리 팀은 관리자 전용 페이지 하나를 일반 SSR로 두려다가 실수로 전체 대시보드 PPR을 꺼뜨렸다.
셋째, Next.js 15(실험적, canary)와 16(안정화) 사이에 API 차이가 있다. 15에서는 experimental: { ppr: 'incremental' }이지만, 16에서는 ppr: true로 단순화된다. 15에서 작성한 코드를 16으로 업그레이드할 때 설정을 다시 확인해야 한다.
결국 무엇을 기준으로 PPR을 고를 것인가?
정적 부분이 명확하고, 개인화 또는 실시간 갱신이 필요한 콘텐츠다. 다시 정리하면:
- PPR이 맞는 경우: 전자상거래 상품 페이지(셸: 기본 정보, 홀: 가격·재고·개인화), 뉴스 피드(셸: 레이아웃, 홀: 실시간 기사), 실시간 대시보드(셸: 네비게이션, 홀: 스트리밍 지표)
- ISR로 충분한 경우: 블로그(하루 1~2회 갱신), 문서 사이트(주 1회 배포), 정적 마케팅 페이지
- SSR을 유지할 경우: 완전 개인화 콘텐츠(계정·권한별 전체 다름), 실시간 협업 도구(모든 데이터 사용자별 고유)
- 비용·팀 규모: 마이그레이션 복잡도(컴포넌트 구조 재설계 필요)와 얻을 LCP 개선(0.8초 vs 기존 방식)을 천칭질해 결정
2026년 기준 Next.js 16이 안정화되면서 PPR은 선택지가 아니라 기본 렌더링 전략으로 자리 잡고 있다. 하지만 "정적 부분이 정말 정적인가"를 먼저 확인한 후 도입해야 한다. 아니면 Suspense 로딩 스켈레톤만 계속 깜빡일 것이다.
핵심 정리
- PPR은 SSG의 초고속 TTFB(20~50ms)와 SSR의 실시간 갱신을 단일 요청으로 통합한다. ISR의 스테일 창을 제거하고 서버 비용을 30~40% 절감한다.
- 갱신 주기와 개인화 정도가 선택 축이다. 분 단위 변동이 필요하거나 사용자별 데이터 일부만 다르면 PPR이 유리하다. 전체가 개인화되거나 하루에 한두 번만 갱신되면 기존 방식이 낫다.
- 구조 변경이 필수: 라우트 레벨 데이터 페칭을 컴포넌트 Suspense 내부로 옮겨야 작동한다. 이 변경을 놓치면 PPR은 사실상 SSR처럼 작동한다.
- 함정: 자식 라우트에서 PPR 거부 시 부모 트리 전체가 무효화된다. API 차이(Next.js 15 vs 16)도 확인이 필요하다.
- 판단 기준: 정적 부분이 명확하고, 개인화/실시간 갱신이 필요한 콘텐츠(상품 페이지, 뉴스 피드, 대시보드)에 PPR을 도입한다. 마이그레이션 복잡도와 LCP 개선을 저울질해 결정하라.