트레이드오프가 사라진 것이 아니라, 이동했다
'정적이냐 동적이냐'는 프론트엔드 개발자가 오랫동안 페이지 단위로 내려야 했던 이진 결정이었다. ISR을 선택하면 CDN 속도를 얻지만 신선도를 잃고, 완전한 동적 렌더링을 선택하면 신선도를 얻지만 오리진 레이턴시를 감수해야 했다. Next.js의 Partial Prerendering(PPR)은 이 트레이드오프를 없애는 것이 아니라, 그 경계를 페이지 수준에서 컴포넌트 수준으로 끌어내린다.
PPR이 실제로 하는 일
dev.to에 공개된 실전 사례에 따르면, PPR은 하나의 HTTP 응답 안에서 두 가지 렌더링 단계를 병렬로 처리한다. 빌드 타임에 <Suspense> 바깥에 있는 모든 것—내비게이션, 제목, 설명, 레이아웃 구조—은 정적 HTML 셸로 CDN 엣지에 고정된다. 요청이 들어오면 CDN은 즉시 셸을 응답하고, 오리진은 동시에 <Suspense> 내부의 동적 섬들을 같은 응답 스트림으로 흘려보낸다.
결과적으로 사용자는 거의 완성된 페이지를 CDN 속도로 먼저 보고, 재고 배지나 개인화 추천처럼 요청 시점에만 알 수 있는 데이터는 그 직후 스트리밍된다. ISR의 스탈니스도, 전체 동적 렌더링의 TTFB 지연도 없다.
활성화는 놀랍도록 단순하다. next.config.ts에 experimental: { ppr: 'incremental' }을 추가하고, 각 라우트 파일에 export const experimental_ppr = true를 선언하면 된다. 기존 Server Components나 Suspense 패턴을 바꿀 필요가 없다. 단, <Suspense> 바깥에서 cookies()나 headers()를 읽으면 빌드가 명시적인 오류와 함께 실패한다—이는 버그가 아니라 설계 의도다.
설계 결정의 핵심: 무엇이 셸에 속하는가
PPR을 도입할 때 진짜 고민은 설정이 아니라 경계 설계다. 셸에는 세션과 무관한 콘텐츠—레이아웃, 정적 이미지, 공통 내비게이션—가 들어가야 한다. <Suspense> 안으로는 사용자 특정 데이터, 실시간 가격·재고, A/B 테스트 쿠키 읽기가 들어간다. SEO 관점에서도 제목과 주요 본문은 반드시 셸에 위치시켜야 크롤러가 정적 HTML을 완전히 파싱할 수 있다.
use cache와의 조합도 주목할 만하다. PPR이 CDN 경계를 제어한다면, use cache는 동적 섬 내부에서 오리진 렌더링을 메모이제이션한다. 두 레이어가 스택되면 CDN은 셸을, 컴포넌트 캐시는 동적 파트를 각각 담당하는 이중 최적화 구조가 완성된다.
TanStack Query는 왜 아직 살아있는가
RSC가 등장하면서 '클라이언트 데이터 페칭 라이브러리는 더 이상 필요 없다'는 주장이 돌았다. 같은 dev.to 시리즈에서 저자는 이를 '반만 맞다'고 정리한다. RSC와 Server Actions는 초기 페이지 데이터 페칭과 단순한 뮤테이션을 명확하게 대체한다. 서버 컴포넌트에서 DB를 직접 await하면 로딩 상태도, 클라이언트 워터폴도, 직렬화 보일러플레이트도 없다. 실제로 해당 패턴으로만 동작하는 두 프로젝트에서 TanStack Query를 완전히 제거했고 아무것도 그립지 않았다고 한다.
그러나 서버 컴포넌트는 요청당 한 번 렌더링된다. 데이터가 '요청의 타임라인'이 아니라 '클라이언트의 타임라인'에 따라 변해야 하는 순간부터 그림이 달라진다. 폴링, 무한 스크롤, 여러 라우트 간 캐시 공유, 다중 컴포넌트에 걸친 낙관적 업데이트—이 네 가지 시나리오에서 TanStack Query는 여전히 대체재가 없다.
Hydration 패턴: 둘 중 하나가 아니라 둘 다
가장 실용적인 통찰은 'either/or'가 아닌 병존 전략이다. 서버 컴포넌트에서 QueryClient로 데이터를 프리페치하고 HydrationBoundary로 감싸면, 클라이언트 캐시는 처음부터 워밍된 상태로 시작한다. 첫 렌더링에 로딩 상태가 없고, 이후 폴링·포커스 리페치·쿼리 무효화는 TanStack Query가 자연스럽게 이어받는다.
뮤테이션 쪽에서는 혼동이 자주 발생한다. revalidatePath와 invalidateQueries는 모두 캐시를 무효화하지만, 서로 다른 캐시를 건드린다. 전자는 서버 컴포넌트의 라우트 캐시를, 후자는 클라이언트 쿼리 캐시를 대상으로 한다. 클라이언트 캐시에 데이터가 살아있다면 Server Action에서 revalidatePath만 호출하는 것으로는 UI가 갱신되지 않는다. '데이터의 소유자를 하나로 고정하고, 그 소유자가 무효화를 책임진다'는 원칙이 핵심이다.
Claude Artifacts: 프로토타이핑 루프를 구조화하다
렌더링 전략을 설계하는 것과 그것을 빠르게 검증하는 것은 별개의 문제다. Claude의 Artifacts 기능은 이 간극을 좁히는 데 실질적인 도움을 준다. Artifacts는 대화 창 옆에 격리된 워크스페이스를 열어 코드, HTML, Mermaid 다이어그램, React 컴포넌트를 독립적으로 렌더링한다. 200줄짜리 코드가 채팅 흐름을 방해하지 않고, 변경이 있을 때마다 전체를 다시 출력하는 대신 해당 Artifact만 업데이트된다.
버전 선택기로 이전 상태로 돌아갈 수 있고, 대화의 특정 시점을 편집하면 브랜치가 생성되어 대안적인 설계 경로를 병렬로 탐색할 수 있다. Mermaid 지원은 아키텍처 다이어그램을 텍스트로 서술하면 Claude가 시각적으로 컴파일해주는 방식으로, 시퀀스 다이어그램이나 플로우차트를 별도 도구 없이 대화 중에 즉시 생성하고 반복할 수 있다.
이 흐름은 프로토타이핑 루프를 실질적으로 단축시킨다. PPR 경계를 어디에 그을지, TanStack Query를 도입할지 말지를 코드로 실험하기 전에 다이어그램과 컴포넌트 목업으로 먼저 검증할 수 있고, 그 결과물을 즉시 팀과 공유하거나 다음 대화 컨텍스트로 이어갈 수 있다.
시사점: 경계 설계가 곧 아키텍처다
세 가지 흐름이 가리키는 방향은 하나다. RSC 시대의 프론트엔드 아키텍처는 '어떤 도구를 쓰느냐'보다 '경계를 어디에 어떻게 긋느냐'의 문제로 이동하고 있다. PPR은 정적과 동적의 경계를 컴포넌트 트리 안으로 끌어들이고, TanStack Query는 서버 타임라인과 클라이언트 타임라인의 경계를 명확히 하며, Claude Artifacts는 그 경계 결정을 빠르게 시각화하고 검증하는 워크스페이스를 제공한다.
전망: 점진적 복잡도와 명시적 설계
PPR은 Next.js 16 기준으로 여전히 experimental이지만, Vercel이 내부적으로 프로덕션에서 사용하고 있다는 점에서 방향성은 분명하다. 앞으로의 과제는 도구 자체보다 팀이 이 경계 결정을 얼마나 명시적으로 문서화하고 공유하는가에 달려 있다. 어떤 컴포넌트가 셸에 속하는지, 어떤 데이터가 클라이언트 캐시를 소유하는지—이 결정들이 코드 리뷰와 설계 논의의 중심이 될 때, 렌더링 전략은 비로소 팀의 공유 자산이 된다.