IT#Next.js 16#PPR#Partial Prerendering#Server Actions#Suspense#웹성능#Frontend

Next.js 16 PPR(Partial Prerendering) 실무 적용기: SSR과 SSG의 장점만 취하는 법

Next.js 16의 핵심 기능인 PPR을 통해 웹 성능을 극대화하는 방법을 알아봅니다. Suspense 스트리밍 전략부터 서버 액션을 활용한 최신 개발 패턴까지 상세히 다룹니다.

Toolpack
2026-01-11
3 min read

이 게시물은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

Next.js 16 PPR(Partial Prerendering) 실무 적용기

Next.js 16의 정식 출시와 함께 가장 주목받는 기능은 단연 PPR(Partial Prerendering)입니다. 그동안 우리는 "정적인 페이지는 SSG, 동적인 데이터가 필요하면 SSR"이라는 이분법적 선택을 강요받아 왔습니다.

PPR은 이 경계를 허물고, 한 페이지 안에서 정적 요소와 동적 요소를 완벽하게 결합합니다. 실무에서 PPR을 어떻게 적용하고 성능을 개선할 수 있는지 깊이 있게 다뤄보겠습니다.


1. SSR과 SSG 사이의 정답, PPR이란 무엇인가? ⚖️

PPR은 페이지의 '정적 쉘(Static Shell)'은 즉시 서빙하고, 데이터가 필요한 '동적 구멍(Dynamic Holes)'은 백그라운드에서 스트리밍으로 채우는 방식입니다.

🧠 작동 원리

  1. 빌드 타임: 컴포넌트 트리에서 Suspense로 감싸진 부분을 찾아냅니다.
  2. 정적 생성: Suspense 바깥의 정적 요소들을 미리 렌더링하여 HTML 쉘을 만듭니다.
  3. 런타임: 사용자가 접속하면 즉시 정적 HTML을 보냅니다. 동시에 서버에서는 동적 데이터를 페칭하여 비어있는 Suspense 자리에 결과를 스트리밍합니다.

💡 왜 PPR인가?

  • TTFB(Time to First Byte) 최적화: 서버 사이드 렌더링을 기다릴 필요 없이 즉시 응답이 시작됩니다.
  • 컴퓨팅 비용 절감: 페이지 전체를 매번 생성하는 대신, 필요한 부분만 동적으로 처리하므로 서버 부하가 줄어듭니다.

2. loading.js에서 Suspense 기반 스트리밍으로의 전환 전략 🚀

과거 Next.js 13/14에서는 페이지 단위의 로딩 상태를 loading.js로 관리했습니다. 하지만 PPR 시대에는 더 세밀한 Component-level Suspense가 핵심입니다.

🛠️ 전환 체크리스트

  1. loading.js 제거: 페이지 전체를 가리는 로딩 스피너 대신, 실제 데이터가 들어갈 자리에 스켈레톤(Skeleton) UI를 배치하세요.
  2. 데이터 페칭 하위 이동: page.js에서 몰아서 await 하던 로직을 각 컴포넌트 내부로 옮기세요.
  3. Suspense 경계 설정: 독립적으로 로딩되어도 무방한 단위(예: 추천 상품 목록, 사용자 댓글)를 각각 Suspense로 감싸세요.
// page.js
export default function ProductPage() {
  return (
    <main>
      <StaticProductInfo />
      {/ 이 부분만 동적으로 스트리밍됩니다. /}
      <Suspense fallback={<ReviewsSkeleton />}>
        <DynamicReviews />
      </Suspense>
    </main>
  );
}
javascript

3. 서버 액션(Server Actions)과 낙관적 업데이트 패턴 🧠

PPR로 렌더링 성능을 잡았다면, 이제 사용자 인터랙션 성능을 잡을 차례입니다. Server ActionsuseOptimistic 훅의 조합은 현대적인 웹 앱의 필수 패턴입니다.

✅ 낙관적 업데이트(Optimistic UI) 구현

사용자가 '좋아요' 버튼을 눌렀을 때, 서버 응답을 기다리지 않고 즉시 UI를 변경하는 기법입니다.

'use client';
import { useOptimistic } from 'react';
function LikeButton({ initialLikes, productId }) {
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    initialLikes,
    (state, newLike) => state + 1
  );
  async function handleAction() {
    addOptimisticLike(1); // UI 즉시 반영
    await updateLikeCount(productId); // 실제 서버 액션 실행
  }
  return <button onClick={handleAction}>❤️ {optimisticLikes}</button>;
}
javascript

💡 실무 팁: 에러 핸들링

낙관적 업데이트는 실패했을 때의 복구 로직이 중요합니다. useOptimistic은 액션이 끝나면 자동으로 상태를 동기화해주므로, 실제 API 에러 발생 시 사용자에게 토스트 알림을 보여주는 정도로도 충분히 훌륭한 경험을 제공할 수 있습니다.


4. 마치며: Next.js 16이 그리는 웹의 미래 🏆

PPR은 단순히 속도가 빠른 것을 넘어, 개발자가 사용자 경험을 설계하는 방식 자체를 바꿉니다. 이제 우리는 "어떤 데이터를 먼저 보여줄 것인가?"에 대한 우선순위를 Suspense를 통해 선언적으로 결정할 수 있습니다.

  • 정적 쉘로 초기 체감 속도를 잡으세요.
  • 세밀한 Suspense로 데이터 스트리밍을 최적화하세요.
  • 서버 액션과 낙관적 UI로 인터랙션의 지연시간을 지우세요.

Next.js 16과 PPR을 통해 여러분의 서비스를 한 단계 더 높은 수준으로 끌어올려 보시기 바랍니다!

T

Toolpack

Author

2026-01-11

Published