Next.js 14 버전의 패치노트를 보면 Partial Prerendeing(PPR)이라는 것을 Preivew 단계로 소개하고 있습니다.
간단한 설명으로는 정적 리소스의 빠른 응답과 동적인 컨텐츠의 스트리밍이라고 표현하고 있는데 오묘한 느낌이라 제대로 이해하기 위해 해당 방식을 찾아보았습니다. 앞으로는 Partial Prerendering은 PPR이라고 표현하겠습니다.
PPR이란?
PPR은 하나의 페이지에서 static한 부분은 사용자에게 바로 보여주고, dynamic한 부분은 fallback을 보여주다가 컴포넌트의 준비가 끝나면 해당 컴포넌트를 보여주게 됩니다. 하나의 페이지에서 static, dynamic한 렌더링을 새로운 API를 학습할 필요 없이 더욱 빠르게 제공할 수 있게 해줍니다. PPR은 리액트의 Concurrent API와 Suspense를 활용하여 dynamic 렌더링을 다루게 됩니다. 기존의 app router의 streaming 방식과 다른 점은 PPR은 빌드 시점에 페이지의 static, dynamic한 부분을 구분짓는다는 것입니다. 빌드 시점에 페이지의 static한 부분은 prerendered되고 나머지 부분은 유저의 요청이 들어올 때 렌더링이 진행됩니다. 따라서 페이지에서 SSR이 필요하더라도 static한 부분은 사용자에게 먼저 보여줄 수 있다는 점이 기존 렌더링 방식과 차이점이라고 볼 수 있습니다. 간단하게는 ISR + SSR 방식이라고 생각해볼 수 있습니다. Next.js 팀에서는 이러한 렌더링 방식이 웹 어플리케이션의 기본적인 렌더링 모델이 될 가능성이 있다고 생각하고 있습니다.
기술의 역사 또는 왜 필요한가/왜 생겨났는가
대부분의 웹 어플리케이션은 static rendering(SSG, ISR)과 dynamic rendering(SSR)과 같은 방식중에 무엇을 사용할 것인지 웹 어플리케이션 전체 혹은 특정 페이지에서 선택해야 합니다. 하지만 대부분의 페이지는 100% static하거나 100% dynamic 하지 않습니다. 그래서 dynamic rendering을 적용하는 페이지에서는 정적인 컨텐츠를 담고 있는 부분은 사용자에게 바로 보여줘도 괜찮음에도 불구하고 서버에서 html을 그리고 클라이언트로 전달했기 때문에 바로 보여줄 수가 없습니다. 대부분의 페이지는 static과 dynamic이 섞여 있는 경우가 많기 때문에 static한 부분은 먼저 사용자에게 보여줄 수 있다면 UX 관점에서 매우 효과적일 것 같습니다. 아마 이러한 바램으로 PPR 방식이 생겨났을 것이라고 생각합니다.
특징
Suspense로 페이지의 static과 dynamic한 부분을 구분지을 수 있습니다. PPR의 장점은 해당 렌더링 방식을 사용하기 위해 추가로 코드를 작성할 필요가 없다는 것입니다. 페이지의 dynamic한 부분을 Suspense로 감싸기만 하면 Next.js compilier는 static과 dynamic한 부분을 구분해주고 PPR을 적용해줍니다.
Partial Prerendering으로 이루어진 페이지의 .html파일을 보면 아래와 같은 코드를 확인할 수 있습니다.
dymamic한 렌더링이 필요한 부분의 경우에는 아래 코드와 같이 S:0, P:1과 같은 표시로 shell을 만듭니다. Next.js 코드에서는 이 부분은 Suspense 로 감싸져 있는 컴포넌트를 의미합니다.
<!-- index.html -->
<div hidden id="S:0">
<template id="P:1"></template>
</div>
<!-- app/page.tsx -->
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews />
</Suspense>
해당 컴포넌트에 필요한 데이터를 모두 불러오고 나면 아래에 있는 $RS, $RC와 같은 함수를 이용하여 shell의 위치에 실제 컨텐츠를 삽입해주게 됩니다. 아래 코드는 chatGPT를 이용하여 이해하는 데 도움을 받았습니다.
<script>
// 이 함수는 두 개의 인자(a와 b)를 받아서 특정 DOM 요소들을 조작하는 역할을 합니다.
$RS = function(a, b) {
a = document.getElementById(a);
b = document.getElementById(b);
for (a.parentNode.removeChild(a); a.firstChild; )
b.parentNode.insertBefore(a.firstChild, b);
b.parentNode.removeChild(b)
}
;
$RS("S:1", "P:1")
</script>
<script>
// 이 함수는 세 개의 인자(b, c, e)를 받아서 특정 DOM 요소들을 다루고 있습니다.
$RC = function(b, c, e) {
c = document.getElementById(c);
c.parentNode.removeChild(c);
var a = document.getElementById(b);
if (a) {
b = a.previousSibling;
if (e)
b.data = "$!",
a.setAttribute("data-dgst", e);
else {
e = b.parentNode;
a = b.nextSibling;
var f = 0;
do {
if (a && 8 === a.nodeType) {
var d = a.data;
if ("/$" === d)
if (0 === f)
break;
else
f--;
else
"$" !== d && "$?" !== d && "$!" !== d || f++
}
d = a.nextSibling;
e.removeChild(a);
a = d
} while (a);
for (; c.firstChild; )
e.insertBefore(c.firstChild, a);
b.data = "$"
}
b._reactRetry && b._reactRetry()
}
}
;
$RC("B:0", "S:0")
</script>
PPR은 현재 client-side navigations에 적용되지 않는데 이 부분은 vercel측에서 작업중에 있습니다.
PPR은 Node.js 런타임 전용으로 설계되었고 static shell을 즉시 제공할 수 있는 경우 Node.js 런타임의 subset을 사용할 필요가 없습니다.
사용방법
Next.js 14의 PPR은 아직 실험적인 기능이고 DX 이슈와 성능 이슈가 존재하기 때문에 프로덕션에서 사용하는 것은 추천하지 않고 있습니다. (Stable 단계가 되고 난 후에도 Production에는 천천히 도입하는게 좋을 것 같습니다)
Next.js 14에서 PPR은 실험적인(experimental) 기능이기 때문에 다음과 같이 사용할 수 있습니다.
(현재는 index route(/)에만 PPR이 적용됩니다)
1. next.config.js에 옵션을 추가해줌으로써 사용할 수 있습니다.
experimental: {
ppr: true,
},
2. React의 Suspense API로 dynamic content를 감싸줍니다.
아래 코드에서 RecommendedProducts 컴포넌트와 Reviews 컴포넌트는 dynamic content로 Next.js가 판단하게 됩니다.
static content인 SingleProduct, Ping 컴포넌트는 사용자에게 바로 보여지게 되고 RecommendedProducts와 Reviews 컴포넌트는 fallback을 먼저 보여주고 컴포넌트에서 데이터를 불러오고 나면 해당 컴포넌트를 렌더링합니다.
export default function Page() {
return (
<div className="space-y-8 lg:space-y-14">
<SingleProduct />
<Ping />
<Suspense fallback={<RecommendedProductsSkeleton />}>
<RecommendedProducts />
</Suspense>
<Ping />
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews />
</Suspense>
</div>
);
}
개인적인 생각
요즘 프론트엔드 생태계에서는 UX와 DX를 고도화 하는 작업이 많이 이루어지고 있는 것 같습니다.
PPR도 사용자에게 더 좋은 경험을 제공하기 위한 렌더링 방식으로 볼 수 있을 것 같습니다.
vercel 측에서도 기본적인 렌더링 방식이 될 수 있다고 이야기하는 만큼 관심을 가지고 지켜보면 좋을 것 같다는 생각이 들었습니다.
레퍼런스 모음
https://nextjs.org/docs/app/api-reference/next-config-js/partial-prerendering
https://github.com/vercel-labs/next-partial-prerendering
'TIL > 개발' 카테고리의 다른 글
그림으로 배우는 Http & Network Basic - 4장 (0) | 2023.12.15 |
---|---|
그림으로 배우는 Http & Network Basic - 3장 (0) | 2023.12.15 |
그림으로 배우는 Http & Network Basic - 2장 (0) | 2023.12.06 |
그림으로 배우는 Http & Network Basic - 1장 (0) | 2023.12.06 |
[리팩토링 2판 스터디] 2회차 정리 (0) | 2023.12.05 |