Next.js 15 스트리밍과 Suspense 완벽 가이드: 성능 최적화 전략
현대 웹에서 성능은 단순한 선택 사항이 아닙니다. 성능은 사용자 유지와 SEO 성공의 근간입니다. Next.js 15는 데이터 페칭과 UI 렌더링 방식을 개선하여 이 철학을 한 단계 더 발전시켰습니다. React 19의 기능을 깊이 있게 통합함으로써, Next.js 15는 **스트리밍(Streaming)**과 **서스펜스(Suspense)**를 위한 정교한 모델을 제공하며, 대량의 데이터를 처리할 때도 즉각적으로 반응하는 애플리케이션 구축을 가능하게 합니다.
이 가이드에서는 Next.js 15의 아키텍처 변화, 효과적인 스트리밍 구현 방법, 그리고 부분 사전 렌더링(Partial Prerendering, PPR)과 같은 기능이 왜 "빠름"의 경계를 재정의하고 있는지 살펴봅니다.
Next.js에서의 스트리밍(Streaming)이란?
전통적인 서버 사이드 렌더링(SSR)은 서버가 페이지의 모든 데이터를 가져오고, 전체 HTML을 렌더링한 다음 클라이언트로 전송해야 했습니다. 이러한 "전부 아니면 전무(all-or-nothing)" 방식은 병목 현상을 일으켰습니다. 사용자는 화면에 무언가 나타나기 전까지 가장 느린 데이터 페칭이 완료될 때까지 기다려야 했기 때문입니다.
스트리밍은 페이지를 더 작은 "청크(chunks)"로 나눕니다. 이를 통해 서버는 페이지의 정적 쉘(shell)을 즉시 전송하고, 데이터가 준비되는 대로 동적인 부분을 점진적으로 스트리밍할 수 있습니다.
스트리밍이 중요한 이유
- 최초 바이트 시간(TTFB) 단축: 정적인 부분이 준비되는 즉시 서버가 HTML 전송을 시작합니다.
- 최초 콘텐츠풀 페인트(FCP) 개선: 사용자는 레이아웃과 네비게이션을 거의 즉시 볼 수 있습니다.
- 체감 성능 향상: 빈 화면 대신 "로딩 스켈레톤(loading skeletons)"이 표시되어 애플리케이션이 더 반응성 있게 느껴집니다.
서스펜스 경계(Suspense Boundaries) 구현하기
React Suspense는 Next.js 스트리밍을 구동하는 핵심 메커니즘입니다. 데이터 페칭과 같은 비동기 작업을 수행하는 컴포넌트를 감싸고, 작업이 진행되는 동안 표시할 폴백(fallback) UI를 제공할 수 있게 해줍니다.
세밀한 접근 방식
Next.js 15에서 Suspense를 사용하는 가장 효과적인 방법은 데이터 페칭을 컴포넌트 수준으로 내리는 것입니다.
// page.tsx
import { Suspense } from 'react';
import { ProductGallery } from './components/ProductGallery';
import { GallerySkeleton } from './components/Skeletons';
export default function Page() {
return (
<main>
<h1>최신 트렌드 쇼핑하기</h1>
{/* 상단의 정적 콘텐츠는 즉시 렌더링됩니다 */}
<Suspense fallback={<GallerySkeleton />}>
<ProductGallery />
</Suspense>
</main>
);
}
이 예시에서 H1 제목과 네비게이션(레이아웃의 일부인 경우)은 사용자에게 즉시 전송됩니다. ProductGallery 컴포넌트는 서버에서 자체적으로 데이터를 가져오며, "서스펜딩(suspending)"되는 동안 GallerySkeleton이 표시됩니다.
주요 변경 사항: 비동기 Request API
Next.js 15에서 가장 중요한 업데이트 중 하나는 요청 관련 API가 비동기로 전환되었다는 점입니다. 이 변화는 부분 사전 렌더링(PPR)과 같은 공격적인 최적화를 지원하기 위해 필수적이었습니다.
이제 다음 API들은 Promise를 반환합니다:
cookies()headers()params(page/layout props 내)searchParams(page props 내)
대응 방법:
// Next.js 15 구현 방식
export default async function Page({
params,
searchParams,
}: {
params: Promise<{ slug: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const { slug } = await params;
const { query } = await searchParams;
return <div>포스트: {slug}</div>;
}
이러한 API를 비동기로 만듦으로써, Next.js는 렌더링 프로세스를 언제 "일시 중단(suspend)"할지 더 잘 조정할 수 있으며, 동적 데이터가 정적 세그먼트의 생성을 방해하지 않도록 보장합니다.
부분 사전 렌더링(Partial Prerendering, PPR): 궁극의 최적화
부분 사전 렌더링은 정적 렌더링과 동적 렌더링의 장점만을 결합한 Next.js 15의 실험적 기능입니다.
전형적인 App Router 설정에서 경로는 완전히 정적이거나 완전히 동적입니다. cookies()와 같은 동적 함수를 사용하면 경로 전체가 동적으로 변합니다. PPR을 사용하면 단일 경로 내에서 정적인 부분과 동적인 부분을 모두 가질 수 있습니다.
PPR이 Suspense와 작동하는 방식
- Next.js는 빌드 타임에 페이지의 정적 "쉘(shell)"을 생성합니다.
- 이 쉘에는 Suspense 경계로 감싸진 부분을 제외한 모든 것이 포함됩니다.
- 사용자가 페이지를 방문하면, 정적 쉘이 에지(edge)에서 즉시 제공됩니다.
- Suspense로 감싸진 동적 컴포넌트들은 "구멍(holes)"으로 남겨졌다가 서버가 처리하는 대로 채워집니다.
그 결과, 정적 사이트의 속도로 로드되면서도 완전한 동적 애플리케이션의 힘을 가진 페이지가 탄생합니다.
Next.js 15 성능을 위한 베스트 프랙티스
1. 스피너 대신 스켈레톤 사용
스피너(Spinners)는 때때로 흐름을 방해하는 느낌을 줄 수 있습니다. 들어올 콘텐츠의 레이아웃을 모방한 스켈레톤 화면(회색 박스 등)을 사용하면 더 부드러운 전환을 제공하고 레이아웃 이동(CLS)을 줄일 수 있습니다.
2. 전략적인 경계 배치
페이지 전체를 하나의 Suspense 경계로 감싸지 마십시오. 대신 독립적인 데이터 페칭 "단위"를 식별하십시오. 예를 들어 대시보드라면 "사용자 통계", "최근 활동", "알림" 섹션에 각각 별도의 경계를 둘 수 있습니다.
3. 데이터 페칭 계층 낮추기
최상위 layout.tsx나 page.tsx에서 모든 데이터를 가져오는 것을 피하십시오. 이는 경로 전체를 기다리게 만듭니다. 대신 컴포넌트가 직접 자신의 데이터를 가져오도록 하십시오.
4. 'use cache' 지시어 활용
Next.js 15는 비용이 많이 드는 계산이나 데이터 페칭 결과를 여러 요청에 걸쳐 캐싱할 수 있도록 돕는 use cache 지시어(실험적)를 도입했습니다. 이를 통해 서버 대기 시간을 더욱 줄일 수 있습니다.
FAQ: Next.js 15 스트리밍 및 서스펜스
Q1. 스트리밍이 Pages Router에서도 작동하나요?
아니요, 완전한 스트리밍과 세밀한 Suspense 지원은 App Router 전용입니다. Pages Router는 여전히 지원되지만, 이러한 고급 아키텍처 개선 사항은 적용되지 않습니다.
Q2. 스트리밍이 SEO에 악영향을 주나요?
오히려 도움이 됩니다. 구글과 같은 검색 엔진은 스트리밍된 콘텐츠를 크롤링할 수 있습니다. 스트리밍은 FCP 및 기타 코어 웹 바이탈(Core Web Vitals)을 개선하므로 검색 순위에 긍정적인 영향을 미칠 수 있습니다.
Q3. 스트리밍을 사용하지 말아야 할 때가 있나요?
콘텐츠가 매우 작고 페칭이 거의 즉각적인 경우(예: 로컬 데이터베이스의 간단한 텍스트), 스트리밍의 오버헤드가 이득보다 클 수 있습니다. 하지만 현대적인 웹 앱의 90% 이상에서는 스트리밍이 권장되는 선택입니다.
Q4. 클라이언트 컴포넌트에서 Suspense를 사용할 수 있나요?
네, 하지만 동작 방식이 다릅니다. 클라이언트 사이드 Suspense는 브라우저에서 비동기 컴포넌트를 처리하는 반면, 서버 사이드 Suspense(서버 컴포넌트 내)는 서버로부터 HTML의 점진적 전달을 처리합니다.
Q5. 부분 사전 렌더링(PPR)을 실무 환경에서 사용해도 안전한가요?
Next.js 15 기준으로 PPR은 여전히 실험적(experimental) 상태입니다. 많은 프로젝트에서 충분히 안정적이지만, 중요한 프로덕션 환경에 배포하기 전에는 철저한 테스트가 필요합니다.
Q6. PPR을 어떻게 활성화하나요?
next.config.js에서 활성화할 수 있습니다:
const nextConfig = {
experimental: {
ppr: 'incremental',
},
};
'incremental' 옵션을 사용하면 특정 레이아웃이나 페이지에 대해 개별적으로 적용할 수 있습니다.
결론
Next.js 15는 웹 성능의 전환점입니다. 스트리밍과 서스펜스를 마스터하고, 비동기 Request API와 부분 사전 렌더링으로의 변화를 수용함으로써 사용자의 기대를 뛰어넘는 속도의 애플리케이션을 구축할 수 있습니다.
웹은 이제 정적 vs 동적의 이분법에서 벗어나고 있습니다. Next.js 15와 함께라면 모든 바이트가 최적의 타이밍에 전달되는 진정한 하이브리드 렌더링의 시대로 진입할 수 있습니다. 지금 바로 데이터 집약적인 컴포넌트들을 리팩토링하고 애플리케이션의 반응성 차이를 직접 느껴보십시오.