문서
App Router 사용하기
부분 사전 렌더링

부분 사전 렌더링

부분 사전 렌더링(PPR)을 사용하면 동일한 경로에서 정적 및 동적 컴포넌트를 함께 결합할 수 있습니다.

빌드 중에 Next.js는 가능한 한 많은 경로를 사전 렌더링합니다. 들어오는 요청에서 읽기와 같은 동적 코드가 감지되면 관련 컴포넌트를 React Suspense 경계로 감쌀 수 있습니다. 그러면 Suspense 경계 폴백이 사전 렌더링된 HTML에 포함됩니다.

참고: 부분 사전 렌더링은 실험적인 기능이며 변경될 수 있습니다. 아직 운영 환경 사용에 적합하지 않습니다.

정적 내비게이션과 제품 정보, 동적 장바구니 및 추천 제품을 보여주는 부분 사전 렌더링된 제품 페이지

🎥 시청하기: PPR의 이유와 작동 방식 → YouTube (10분).

배경

PPR을 사용하면 Next.js 서버가 사전 렌더링된 콘텐츠를 즉시 전송할 수 있습니다.

클라이언트에서 서버로의 연속적인 요청 지연을 방지하기 위해, 동적 컴포넌트는 초기 사전 렌더링을 제공하는 동안 서버에서 병렬로 스트리밍을 시작합니다. 이를 통해 클라이언트 JavaScript가 브라우저에 로드되기 전에 동적 컴포넌트가 렌더링을 시작할 수 있습니다.

각 동적 컴포넌트에 대해 많은 HTTP 요청을 생성하는 것을 방지하기 위해, PPR은 정적 사전 렌더링과 동적 컴포넌트를 단일 HTTP 요청으로 결합할 수 있습니다. 이는 각 동적 컴포넌트에 대해 여러 네트워크 라운드트립이 필요하지 않도록 합니다.

부분 사전 렌더링 사용하기

점진적 도입 (버전 15)

Next.js 15에서는 next.config.js에서 ppr 옵션을 incremental로 설정하고, 파일 상단에 experimental_ppr 경로 구성 옵션을 내보내어 레이아웃페이지에서 부분 사전 렌더링을 점진적으로 도입할 수 있습니다:

next.config.ts
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  experimental: {
    ppr: "incremental",
  },
};
 
export default nextConfig;
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    ppr: "incremental",
  },
};
 
module.exports = nextConfig;
app/page.tsx
import { Suspense } from "react"
import { StaticComponent, DynamicComponent, Fallback } from "@/app/ui"
 
export const experimental_ppr = true
 
export default function Page() {
  return {
     <>
      <StaticComponent />
      <Suspense fallback={<Fallback />}>
        <DynamicComponent />
      </Suspense>
     </>
  };
}
app/page.js
import { Suspense } from "react"
import { StaticComponent, DynamicComponent, Fallback } from "@/app/ui"
 
export const experimental_ppr = true
 
export default function Page() {
  return {
     <>
      <StaticComponent />
      <Suspense fallback={<Fallback />}>
        <DynamicComponent />
      </Suspense>
     </>
  };
}

알아두면 좋은 점:

  • experimental_ppr이 없는 경로는 기본적으로 false로 설정되며 PPR을 사용하여 사전 렌더링되지 않습니다. 각 경로에 대해 PPR을 명시적으로 선택해야 합니다.
  • experimental_ppr은 중첩된 레이아웃과 페이지를 포함하여 경로 세그먼트의 모든 자식에 적용됩니다. 모든 파일에 추가할 필요는 없고, 경로의 최상위 세그먼트에만 추가하면 됩니다.
  • 자식 세그먼트에 대해 PPR을 비활성화하려면 자식 세그먼트에서 experimental_pprfalse로 설정할 수 있습니다.

PPR 활성화하기 (버전 14)

버전 14의 경우 next.config.js 파일에 ppr 옵션을 추가하여 활성화할 수 있습니다. 이는 애플리케이션의 모든 경로에 적용됩니다:

next.config.ts
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  experimental: {
    ppr: true,
  },
};
 
export default nextConfig;
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    ppr: true,
  },
};
 
module.exports = nextConfig;

동적 컴포넌트

next build 중에 경로에 대한 사전 렌더링을 생성할 때, Next.js는 동적 함수가 React Suspense로 감싸져 있어야 합니다. 그러면 fallback이 사전 렌더링에 포함됩니다.

예를 들어, cookies() 또는 headers()와 같은 함수를 사용할 때:

app/user.js
import { cookies } from "next/headers";
 
export function User() {
  const session = cookies().get("session")?.value;
  return "...";
}
app/user.tsx
import { cookies } from "next/headers";
 
export function User() {
  const session = cookies().get("session")?.value;
  return "...";
}

이 컴포넌트는 쿠키를 읽기 위해 들어오는 요청을 확인해야 합니다. PPR과 함께 사용하려면 컴포넌트를 Suspense로 감싸야 합니다:

app/page.tsx
import { Suspense } from "react";
import { User, AvatarSkeleton } from "./user";
 
export const experimental_ppr = true;
 
export default function Page() {
  return (
    <section>
      <h1>This will be prerendered</h1>
      <Suspense fallback={<AvatarSkeleton />}>
        <User />
      </Suspense>
    </section>
  );
}
app/page.js
import { Suspense } from "react";
import { User, AvatarSkeleton } from "./user";
 
export const experimental_ppr = true;
 
export default function Page() {
  return (
    <section>
      <h1>This will be prerendered</h1>
      <Suspense fallback={<AvatarSkeleton />}>
        <User />
      </Suspense>
    </section>
  );
}

컴포넌트는 값이 접근될 때만 동적 렌더링으로 전환됩니다.

예를 들어, page에서 searchParams를 읽고 있다면 이 값을 prop으로 다른 컴포넌트에 전달할 수 있습니다:

app/page.tsx
import { Table } from "./table";
 
export default function Page({
  searchParams,
}: {
  searchParams: { sort: string };
}) {
  return (
    <section>
      <h1>This will be prerendered</h1>
      <Table searchParams={searchParams} />
    </section>
  );
}
app/page.js
import { Table } from "./table";
 
export default function Page({ searchParams }) {
  return (
    <section>
      <h1>This will be prerendered</h1>
      <Table searchParams={searchParams} />
    </section>
  );
}

테이블 컴포넌트 내부에서 searchParams의 값에 접근하면 컴포넌트가 동적으로 실행됩니다:

app/table.tsx
export function Table({ searchParams }: { searchParams: { sort: string } }) {
  const sort = searchParams.sort === "true";
  return "...";
}
app/table.js
export function Table({ searchParams }: { searchParams: { sort: string } }) {
  const sort = searchParams.sort === "true";
  return "...";
}