부분 사전 렌더링
부분 사전 렌더링(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
경로 구성 옵션을 내보내어 레이아웃과 페이지에서 부분 사전 렌더링을 점진적으로 도입할 수 있습니다:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
ppr: "incremental",
},
};
export default nextConfig;
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
ppr: "incremental",
},
};
module.exports = nextConfig;
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>
</>
};
}
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_ppr
을false
로 설정할 수 있습니다.
PPR 활성화하기 (버전 14)
버전 14의 경우 next.config.js
파일에 ppr
옵션을 추가하여 활성화할 수 있습니다. 이는 애플리케이션의 모든 경로에 적용됩니다:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
ppr: true,
},
};
export default nextConfig;
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
ppr: true,
},
};
module.exports = nextConfig;
동적 컴포넌트
next build
중에 경로에 대한 사전 렌더링을 생성할 때, Next.js는 동적 함수가 React Suspense로 감싸져 있어야 합니다. 그러면 fallback
이 사전 렌더링에 포함됩니다.
예를 들어, cookies()
또는 headers()
와 같은 함수를 사용할 때:
import { cookies } from "next/headers";
export function User() {
const session = cookies().get("session")?.value;
return "...";
}
import { cookies } from "next/headers";
export function User() {
const session = cookies().get("session")?.value;
return "...";
}
이 컴포넌트는 쿠키를 읽기 위해 들어오는 요청을 확인해야 합니다. PPR과 함께 사용하려면 컴포넌트를 Suspense로 감싸야 합니다:
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>
);
}
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으로 다른 컴포넌트에 전달할 수 있습니다:
import { Table } from "./table";
export default function Page({
searchParams,
}: {
searchParams: { sort: string };
}) {
return (
<section>
<h1>This will be prerendered</h1>
<Table searchParams={searchParams} />
</section>
);
}
import { Table } from "./table";
export default function Page({ searchParams }) {
return (
<section>
<h1>This will be prerendered</h1>
<Table searchParams={searchParams} />
</section>
);
}
테이블 컴포넌트 내부에서 searchParams
의 값에 접근하면 컴포넌트가 동적으로 실행됩니다:
export function Table({ searchParams }: { searchParams: { sort: string } }) {
const sort = searchParams.sort === "true";
return "...";
}
export function Table({ searchParams }: { searchParams: { sort: string } }) {
const sort = searchParams.sort === "true";
return "...";
}