현대 웹 개발에서 사용자는 즉각적인 반응과 매끄러운 경험을 기대합니다. 이 요구를 충족하기 위해 프런트엔드 프레임워크는 계속 진화했으며, 성능 최적화는 핵심 과제가 되었습니다. 선점적인 React 프레임워크인 Next.js는 강력한 스트리밍과 Suspense 기능을 제공합니다. 둘 다 고성능且 뛰어난 사용자 경험을 갖춘 애플리케이션을 구축하는 데 핵심적인 요소입니다. 이 튜토리얼에서는 이 기술들의 작동 원리와 발전 과정을 깊이 이해하고, 실전 코드를 분석하며 Next.js 애플리케이션에서 스트리밍 렌더링을 활용하는 요령을 익혀 보겠습니다.
1. 引言:为何需要流式传输与 Suspense?
전통적인 서버 사이드 렌더링(SSR) 모드는 보통 “전부 아니면 전무” 원칙을 따릅니다. 서버는 모든 데이터 조회와 페이지 렌더링을 마친 뒤에야 완전한 HTML 문서를 브라우저로 전송해야 합니다. 특정 데이터 조회가 오래 걸리면, 페이지 전체가 준비되기 전까지 사용자는 빈 화면이나 로딩 표시만 볼 수 있습니다. 이로 인해 첫 바이트 시간(TTFB)과 최초 콘텐츠 페인트 시간(FCP)이 길어지고 체감 성능이 저하됩니다.
스트리밍은 바로 이 문제를 해결하기 위해 등장했습니다.
1.1 流式传输简介:从“一次性倒满”到“边倒边喝”
컵에 물을 따는 비유를 다시 떠올리고 한 단계 더 확장해 보겠습니다.
- 전통 SSR(한 번에 채우기): 물을 틀어 컵이 가득 찰 때까지 기다린 다음(서버가 모든 데이터 조회와 렌더링을 완료함), 가득 찬 컵을 갈증한 사용자에게 건네야 합니다(완전한 HTML 전송). 따라서 사용자의 대기 시간이 깁니다.
- 스트리밍 SSR(따르면서 마시기): 먼저 컵을 사용자에게 건넨 다음 물을 틀어 따기 시작합니다(서버가 HTML 골격과 바로 사용할 수 있는 부분을 전송). 물이 흐르는 동안에도 사용자는 컵 바닥에 고인 물부터 마실 수 있습니다(브라우저가 페이지 레이아웃과 정적 텍스트 등 바로 사용할 수 있는 부분을 렌더링). 물이 계속 흐르는 동안에도(서버가 느린 데이터를 계속 조회 중) 사용자는 이미 갈증을 채우기 시작합니다. 결국 컵은 점차 가득 차고(서버가 남은 데이터와 해당 HTML 조각을 전송), 사용자는 긴 초기 대기를 겪지 않아도 됩니다.
웹 애플리케이션에서 “물”은 HTML, 인라인 JSON 데이터, 심지어 DOM 조작을 실행하는 JavaScript 코드 조각을 의미할 수 있습니다. 스트리밍을 사용하면 서버는 콘텐츠를 생성하는 동안 이를 브라우저로 조금씩 전송할 수 있습니다. 브라우저는 조각을 받자마자 파싱과 렌더링을 시작해 페이지 일부를 더 빨리 보여 줄 수 있으며, 인터랙티브성과 체감 성능이 크게 개선됩니다.
1.2 HTTP 分块传输编码 (Transfer-Encoding: chunked)
스트리밍의 기술적 기반 중 하나는 HTTP/1.1에서 도입된 청크 전송 인코딩입니다. 서버가 응답을 보낼 때 Transfer-Encoding: chunked 헤더를 설정하면, 전송 전에 응답 본문의 전체 크기를 알 필요가 없습니다. 서버는 응답 본문을 임의 개수의 ‘청크’로 나눌 수 있습니다. 각 청크에는 크기 정보와 데이터 자체가 포함되어 순서대로 전송됩니다. 마지막 청크의 크기는 0이며, 이는 응답 종료를 나타냅니다. 덕분에 서버는 전체 응답을 버퍼링하지 않고 콘텐츠를 동적으로 생성해 즉시 전송할 수 있습니다.
2. 核心概念:React Suspense —— 流式渲染的基石
React Suspense는 React 18에서 도입된 핵심 기능으로, 우아한 스트리밍 렌더링과 비동기 작업 처리를 가능하게 하는 열쇠입니다. Suspense를 사용하면 개발자는 컴포넌트와 그 데이터의 로딩 상태를 선언적으로 처리할 수 있습니다.
2.1 Suspense 的设计哲学
Suspense의 핵심 아이디어는 다음과 같습니다. 컴포넌트가 의존하는 데이터가 준비될 때까지 렌더링을 ‘일시 중지’하고, 그동안 지정된 fallback UI를 표시합니다. 이로써 데이터 조회를 기다리는 상태 관리가 컴포넌트 로직에서 분리되어 React 프레임워크가 처리하게 됩니다.
2.2 기본 문법과 fallback
이 예제에서는 다음과 같습니다.
<Suspense>컴포넌트가 비동기로 로드해야 할 수 있는 컴포넌트(<Comments />,<UserProfile />)를 감쌉니다.fallbackprop은 React 요소(로딩 표시기, 스켈레톤 화면 등)를 전달받으며, 감싸진 컴포넌트가 일시 중지되면 표시됩니다.
2.3 Suspense 如何“暂停”渲染
React가 <Suspense>로 감싸진 컴포넌트를 렌더링하는 중 그 컴포넌트(또는 자식 컴포넌트)가 비동기 작업을 트리거하면(예: React.lazy를 통한 컴포넌트 코드 로드 또는 향후 실험적인 use 훅을 통한 Promise 읽기), 해당 컴포넌트는 Promise를 “throw”합니다.
가장 가까운 상위 <Suspense> 컴포넌트가 이 Promise를 포착합니다. 이때:
- React는 해당 Suspense 경계 안의 모든 컴포넌트 렌더링을 일시 중지합니다.
- React는 해당 Suspense 컴포넌트의
fallbackUI를 표시합니다. - React는 이 Promise를监听합니다. Promise가 완료되면(resolved), React는 이전에 일시 중지된 컴포넌트 트리를 다시 렌더링을 시도합니다. 성공하면
fallbackUI를 실제 콘텐츠로 교체합니다.
2.4 HTML 中的表现形式详解
Next.js 또는 스트리밍 SSR을 지원하는 다른 프레임워크에서는 Suspense 경계가 초기 HTML에서 특별한 자리표시자 구조로 렌더링됩니다.
이 특별한 구조 <template id="boundary-id"></template>Fallback UI에는 다음이 포함됩니다.
- 시작 표시 ``: Suspense 경계의 시작을 표시합니다.
$$는 복구 가능한 경계일 수 있음을 나타낼 수 있습니다. - 경계 표시
<template id="boundary-id">:id가 이 Suspense 경계의 고유 식별자 역할을 하는<template>요소입니다.<template>태그의 내용은 브라우저에서 기본적으로 렌더링되지 않으므로 자리표시자로 적합합니다. - 로드 상태 UI(
Fallback UI): 처음 표시되는fallback콘텐츠입니다. - 종료 표시 ``: Suspense 경계의 끝을 표시합니다.
/$는 경계가 종료되었음을 나타낼 수 있습니다.
서버가 실제 콘텐츠를 준비하면, 실제 콘텐츠가 포함된 HTML 조각(대개 고유 ID(예: S:0)가 지정된 hidden <div>에 감싸여 있음)과 특정 JavaScript 함수(예: $RC)를 호출하는 <script> 태그를 스트리밍해 전송합니다. 이 함수가 fallback UI를 실제 콘텐츠로 교체합니다.
2.5 Suspense 边界与嵌套
Suspense 컴포넌트를 중첩해 더 세밀한 로딩 상태 제어를 할 수 있습니다. 내부 Suspense 경계가 자체 로딩 상태를 먼저 처리합니다.
NewsFeed와 ChatWidget가 모두 로드하는 데 시간이 걸린다면, 사용자는 먼저 PageSkeleton을 본 뒤 NavBar, Sidebar, Footer가 렌더링되면 이들을 보게 됩니다. 동시에 NewsFeed와 ChatWidget 위치에는 각각 FeedLoading과 ChatLoading이 표시됩니다.
3. Next.js 流式渲染机制演进
Next.js의 스트리밍 렌더링 구현은 단번에 완성된 것이 아니라 하나의演进 과정을 거쳤습니다. 초기 구현은 하위 React API에 더 의존할 수 있지만, 현대적인 구현은 통합과 자동화가 훨씬 잘 되어 있습니다.
3.1 从 $RC 到 __next_f 的抽象
튜토리얼에서 언급한 $RC 함수는 스트리밍 치환 로직의 기본적이고 단순화 된 표현으로 볼 수 있습니다. ID로 경계와 콘텐츠를 찾은 뒤 DOM을 직접 조작해 교체합니다. 이 방식은 단순하고 직관적이며, 스트리밍 교체 핵심 개념을 이해하기 쉽습니다.
그러나 현대 웹 애플리케이션의 요구는 단순 HTML 교체를 훨씬 넘어섭니다. 컴포넌트의 props, 상태, 이벤트 리스너 처리, 클라이언트 라우팅 내비게이션까지 전달해야 합니다. Next.js는 이러한 복잡한 시나리오를 지원하기 위해 더 고급 메커니즘을 도입했습니다. 이 튜토리얼에서 언급한 __next_f 큐도 그 메커니즘의 일부입니다(__next_f는 내부 구현 세부사항이며 버전에 따라 달라질 수 있으므로, 여기서는 현대적인 메커니즘의 대리어로 사용합니다).
__next_f 큐 패턴은 더 이상 미리 렌더링된 HTML과 단순한 치환 함수 호출만 전송하지 않습니다. 대신 직렬화된 렌더링 명령과 컴포넌트 데이터를 전송합니다. 클라이언트의 Next.js 런타임 코드가 이 명령을 처리하며, 다음과 같은 작업이 포함될 수 있습니다.
- 전달받은 HTML 조각을 DOM에 삽입합니다.
- 해당 Suspense 경계를 찾습니다.
- 하이드레이션을 기다리는 컴포넌트에 JSON 형식의 데이터(props, 상태)를 전달합니다.
- 하이드레이션 프로세스를 실행해 컴포넌트에 인터랙티브성을 부여합니다(이벤트 리스너 연결 등).
이 방식은 정적 콘텐츠 교체뿐 아니라 React 컴포넌트의 전체 수명 주기와 인터랙티브성을 지원하기에 더 유연하고 강력합니다.
4. 实战一:基础流式传输 ($RC 函数模拟)
이 예제는 가장 기본적인 스트리밍 HTML 교체 개념을 시뮬레이션해 핵심 흐름을 이해하는 데 목적이 있습니다.
4.1 场景设定
블로그 페이지에서 본문은 즉시 표시할 수 있지만 댓글 영역은 데이터베이스에서 로드하는 데 오래 걸린다고 가정합시다. 먼저 페이지 골격과 댓글 영역의 로딩 상태를 표시한 뒤, 댓글 데이터가 준비되면 스트리밍으로 전송해 로딩 상태를 교체하고 싶습니다.
4.2 服务器端实现 (Node.js + Express 示例)
4.3 客户端 $RC 函数详解
이 단순화된 $RC(boundaryId, contentId) 함수는 다음 작업을 수행합니다.
- 요소 찾기:
document.getElementById로 경계<template>(boundaryTemplate)과 실제 콘텐츠를 담은 숨겨진<div>(contentElement)를 찾습니다. - 컨테이너 찾기:
<template>의 부모 노드인 Suspense 경계가 위치한 컨테이너(boundaryContainer)를 찾습니다. - Fallback UI 제거:
<template>의 다음 형제 노드부터 순회하며 종료 주석 ``을 만날 때까지 모든 노드를 제거합니다. - 경계 표시 제거:
<template>요소 자체와 그 앞의 시작 주석 ``을 제거합니다. - 새 콘텐츠 삽입:
contentElement의 모든 자식 노드(실제 댓글 HTML)를boundaryContainer안으로 옮깁니다. - 정리: 비어 있는
contentElement컨테이너를 제거합니다.
4.4 局限性分析
이러한 기본 구현에는 다음과 같은 주요 한계가 있습니다.
- 정적 HTML에만 적용: HTML 콘텐츠만 교체하며 JavaScript 이벤트 리스너나 컴포넌트 상태 즉 하이드레이션은 처리하지 않습니다. 댓글 영역에 좋아요나 답글 같은 인터랙션이 필요하다면 이 방식만으로는 부족합니다.
- ID를 수동으로 관리: 개발자가 서버에서 생성한 경계 ID(
B:comments)와 콘텐츠 ID(S:comments), 그리고 클라이언트의$RC호출을 직접 맞춰야 합니다. - 취약한 DOM 조작: 예상과 다른 HTML 구조를 만나면 직접적인 DOM 조작이 실패할 수 있습니다.
- 오류 처리 부재: 데이터 조회 실패나 렌더링 오류를 처리하지 않습니다.
5. 实战二:现代流式传输 (__next_f 队列模拟)
이 예제는 서버가 구조화된 명령과 데이터를 전송하고, 클라이언트 런타임이 이를 해석해 렌더링과 하이드레이션을 담당하는 현대적인 방식을 시뮬레이션합니다.
5.1 场景设定
기본 정보는 빠르게 로드되지만 사용자 활동 피드는 동적으로 로드되는 데 시간이 오래 걸리는 사용자 프로필 페이지가 있다고 가정합시다. 피드 부분을 인터랙티브하게 만들고 싶습니다(예: ‘더 보기’ 클릭).
5.2 服务器端实现 (Node.js + Express 示例)
5.3 客户端处理逻辑 (modern-streaming-client.js)
__next_f큐 초기화: HTMLhead에서 가능한 한 빨리self.__next_f = []를 초기화해 이후push작업의 대상 배열을 보장합니다.- 컴포넌트 레지스트리(
ComponentRegistry): 컴포넌트 이름을 실제 렌더링 또는 하이드레이션 로직에 매핑하는 객체를 정의합니다. 여기서ActivityFeed.hydrate함수는 서버에서 렌더링된 HTML에 이벤트 리스너(예: ‘더 보기’ 버튼)를 연결합니다. $RC함수: HTML 교체를 수행하는 함수가 여전히 필요하지만, 이제는 클라이언트 런타임의 일부가 됩니다. 이 버전의$RC는contentId대신boundaryId와contentHtml을 받아 HTML 문자열을 직접 삽입합니다.processNextFQueue함수:self.__next_f큐의 명령을 반복해서 처리합니다.- 명령에 포함된 payload JSON을 파싱합니다.
$RC를 호출해payload.htmlContent를payload.boundaryId에 해당하는 위치에 삽입합니다.- 방금 삽입된 DOM 요소를 찾습니다.
payload.componentName이 존재하고ComponentRegistry에 정의되어 있으면 해당hydrate메서드를 DOM 요소,payload.props,payload.data와 함께 호출해 인터랙티브성을 추가합니다.
- 초기화와 리스닝(
initializeStreaming):DOMContentLoaded이후에 실행됩니다.- 먼저 큐에 이미 들어 있는 명령을 처리합니다(스크립트 로딩이 끝나기 전에 서버가 명령을 푸시했을 수 있음).
- 핵심:
self.__next_f.push메서드를 다시 정의합니다. 이후 서버가<script>를 통해self.__next_f.push([...])를 호출하면 새로 정의된 메서드가 먼저 원래push를 호출해 명령을 큐에 넣고, 비동기로(setTimeout(processNextFQueue, 0)) 큐 처리를 트리거합니다. 이렇게 하면 스크립트 실행 중 새 명령이 도착하더라도 처리되며, 메인 스레드를 막는 문제도 피할 수 있습니다.
5.4 优势分析
이 현대적인 방식의 장점은 분명합니다.
- 데이터와 표현의 분리: 명령에 구조화된 데이터(
props,data)가 포함되어 클라이언트가 이를 이용해 더 복잡한 렌더링과 상태 관리를 할 수 있습니다. - 완전한 인터랙티브성(하이드레이션):
hydrate함수를 통해 서버에서 렌더링된 정적 HTML을 ‘활성화’하면 완전한 React 컴포넌트 기능과 이벤트 처리를 갖게 됩니다. - 더 강한 추상화: 개발자는 하위 DOM ID와 교체 로직을 직접 다루기보다 컴포넌트와 데이터를 다루게 됩니다. 프레임워크가 대부분의 복잡성을 처리합니다.
- 오류 처리와 복구: 예제에서는 자세히 다루지 않았지만, 이러한 구조는 오류 경계(Error Boundaries)와 더 복잡한 복구 전략을 통합하기 쉽습니다.
6. $RC 与 __next_f 对比分析
| 특징 | 원래 $RC 구현(시뮬레이션) | 현대 __next_f 큐 구현(시뮬레이션) |
|---|---|---|
| 복잡도 | 단순하고 직관적이며 기본 개념을 쉽게 이해할 수 있음 | 직렬화, 클라이언트 런타임, 하이드레이션이 포함되어 더 복잡함 |
| 데이터 전달 | 어려움, 일반적으로 미리 렌더링된 HTML만 전달 | 강함, 구조화된 Props와 비동기 데이터 전달 가능 |
| 인터랙티브성 | 없음(정적 HTML 교체만 지원) | 완전 지원(하이드레이션을 통한 이벤트 리스너와 상태 연결) |
| 컴포넌트 모델 | React 컴포넌트 모델을 인식하지 못함 | 인식함, React 컴포넌트 수명 주기와 통합 가능 |
| 추상화 수준 | 낮음, DOM ID를 직접 조작 | 높음, 개발자가 컴포넌트와 명령을 다룸 |
| 견고성/오류 처리 | 기본적이며 오류가 발생하기 쉬움 | 더 견고하며 프레임워크가 오류 처리 메커니즘을 제공할 수 있음 |
| 적용 시나리오 | 개념을 이해하거나 아주 간단한 정적 콘텐츠를 삽입할 때 | 현대 웹 애플리케이션, 인터랙션과 복잡한 상태 관리가 필요한 경우 |
핵심 차이: $RC는 HTML 조각의 물리적 교체에 집중하는 반면, __next_f는 렌더링 명령과 데이터의 전달 및 실행에 집중하며, 최종적으로 하이드레이션을 완성해 스트리밍으로 로드된 콘텐츠를 기능이齐全한 인터랙티브 컴포넌트로 만듭니다.
7. 深入理解流式渲染的关键点
7.1 错误处理
스트리밍에서 오류를 처리할 때는 특별히 다음 사항을 고려해야 합니다.
- 서버 측 오류: 스트리밍 중 서버가 데이터를 조회하거나 특정 블록을 렌더링하는 중 오류가 발생하면 서버는 다음 중 하나를 선택할 수 있습니다.
- 스트림을 종료해 페이지 로딩이 불완전한 상태로 남게 합니다.
- 클라이언트에 특별한 오류 명령을 보내 클라이언트의 Suspense 경계가 오류 상태를 표시하게 합니다(클라이언트 런타임 지원 필요). React의
ErrorBoundary컴포넌트는 클라이언트에서 렌더링 오류를 포착할 수 있습니다. - 오류가 발생한 블록을 건너뛰고 다른 블록을 계속 전송합니다(콘텐츠가 누락될 수 있음).
- 클라이언트 오류: 하이드레이션 과정에서 오류가 발생하면 React의
ErrorBoundary가 이를 포착해 대체 UI를 표시하고 전체 애플리케이션의 충돌을 방지할 수 있습니다.
7.2 流式渲染与 SEO
검색 엔진 크롤러의 스트리밍 콘텐츠 색인 능력은 계속 향상되고 있지만, 여전히 다음 사항에 주의해야 합니다.
- 초기 HTML이 중요함: 처음 전송되는 HTML(비스트리밍 부분과
fallbackUI)에 핵심 SEO 정보(제목, 메타 설명, 주요 콘텐츠 골격)가 포함되도록 해야 합니다. - 크롤러 대기 시간: 크롤러는 실제 사용자처럼 모든 스트리밍 블록이 로드될 때까지 오래 기다리지 않을 수 있습니다. 중요한 콘텐츠는 스트림에 최대한 빨리 나타나야 하며, 초기 HTML에 포함되어야 합니다.
template태그: 크롤러는 일반적으로<template>태그의 내용을 색인하지 않습니다. 이는fallback뒤에 숨겨진 경계 ID를存放하는 데 적합합니다.- 서버 컴포넌트(RSC): RSC는 이전에는 비동기 로딩이 필요했던 부분까지 포함한 전체 HTML을 생성할 수 있습니다. 콘텐츠가 이미 서버에 완전히 준비되어 있어 SEO에 매우 친화적입니다.
7.3 缓存策略
스트리밍과 캐싱을 결합할 수는 있지만 주의가 필요합니다.
- 전체 페이지 캐싱: 전체 페이지의 스트리밍 응답을 캐싱할 수 있다면(예: 모든 사용자에게 동일한 콘텐츠), CDN이 전체 청크 응답을 캐싱할 수 있습니다.
- 조각 캐싱: 더 발전된 전략에서는 각 스트리밍 블록을 생성하는 데 필요한 데이터나 렌더링 결과를 캐싱한 뒤 요청 시점에 동적으로 스트리밍 응답을 조립할 수 있습니다. 일반적으로 더 복잡한 엣지 컴퓨팅이나 서버 측 로직이 필요합니다.
- 클라이언트 캐싱: 클라이언트가 가져온 데이터는 React Query, SWR 또는 Service Worker를 사용해 캐싱해 반복 로드를 피할 수 있습니다.
7.4 与 Next.js App Router 的集成 (React Server Components)
Next.js의 App Router(/app 디렉터리)는 스트리밍과 Suspense를 핵심 기능으로 제공합니다.
- 기본 스트리밍 렌더링: App Router로 만든 페이지는 기본적으로 스트리밍 렌더링됩니다.
loading.js파일: 라우트 세그먼트(폴더)에loading.js파일을 만들면 Suspense 경계가 자동으로 생성되어 해당 라우트 세그먼트의page.js또는layout.js를 감쌉니다. 해당 세그먼트의 컴포넌트(특히 Server Components)가 데이터를 조회하는 동안loading.js의 내용이fallback로 표시됩니다.jsx // app/dashboard/loading.js export default function Loading() { // 你可以添加任何 UI 组件,比如骨架屏 return <div>正在加载仪表盘数据...</div>; }- 서버 컴포넌트(RSC): RSC는 서버에서 실행되는 컴포넌트입니다. RSC에서
async/await로 데이터를 조회하면 React가 자동으로 이를 Suspense 경계로 감싸고(보통loading.js나 직접 추가한<Suspense>가 제공함), 결과를 클라이언트로 스트리밍합니다. 덕분에 데이터 조회와 렌더링이 매끄럽게 통합되고 로딩 상태를 직접 관리할 필요가 없습니다.
App Router와 Server Components는 스트리밍 렌더링 구현을 크게 단순화하고, __next_f 큐 관리 같은 하위 메커니즘을 추상화했습니다.
8. 性能优化策略
스트리밍을 사용하더라도 최적화할 여지는 남아 있습니다.
우선순위 정하기:
- 핵심 콘텐츠 우선: 첫 화면(Above-the-fold) 콘텐츠와 핵심 기능에 필요한 데이터 및 컴포넌트가 스트림에 최대한 빨리 나타나도록 합니다.
- 인터랙션 우선: 사용자 인터랙션이 필요한 요소(버튼, 폼)는 최대한 빨리 하이드레이션해야 합니다.
- Suspense 중첩 활용: 중요도가 다른 콘텐츠에 서로 다른 Suspense 경계를 만들어 핵심 부분이 먼저 로드되도록 합니다.
점진적 강화/하이드레이션:
- 핵심 컴포넌트 우선 하이드레이션: 복잡한 페이지라면 뷰포트 안에 있거나 사용자가 먼저 상호작용할 가능성이 높은 컴포넌트를 우선 하이드레이션할 수 있습니다.
- 비핵심 컴포넌트 하이드레이션 지연: 페이지 하단이나 부차 기능에 해당하는 컴포넌트는 뷰포트에 들어오거나 사용자가 상호작용을 시도할 때까지 하이드레이션을 미룰 수 있습니다.
코드 분할:
React.lazy()와 동적import()를 사용해 필요한 컴포넌트 코드만 필요할 때 불러오고 초기 JavaScript 부하를 줄입니다. 스트리밍은 먼저 HTML 골격을 전송한 뒤, 특정 지연 로드 컴포넌트가 필요할 때 코드 다운로드를 시작하고 렌더링하도록 할 수 있습니다.
미리 불러오기/미리 연결하기:
- 스트리밍 블록에서 나중에 사용될 핵심 리소스(JS, CSS, 폰트, API 엔드포인트)는
<link rel="preload">또는<link rel="preconnect">를 사용해 브라우저가 가능한 한 빨리 가져오도록 유도합니다.
CodeBlock Loading...- 스트리밍 블록에서 나중에 사용될 핵심 리소스(JS, CSS, 폰트, API 엔드포인트)는
요청 워터폴 방지:
- 가능한 한 데이터 조회를 병렬화하세요. 스트리밍 블록에 필요한 데이터가 여러 요청에 의존한다면 클라이언트에서 하이드레이션한 뒤 하나씩 요청하는 대신 서버에서 이 요청들을 병렬로 시작해 보세요. Server Components는在这方面 자연스러운 장점이 있습니다.
측정과 모니터링:
- 브라우저 개발자 도구(Network 패널로 스트리밍 응답을 확인하고 Performance 패널로 렌더링과 하이드레이션을 분석) 및 성능 모니터링 도구(Vercel Analytics, New Relic, Datadog)를 사용해 병목을 식별하고 FCP, LCP, TTI 등의 지표를 측정해 스트리밍 로딩 전략을 지속적으로 최적화하세요.
9. React Server Components (RSC) 与部分/选择性水合
스트리밍과 Suspense는 더 발전된 아키텍처로 나아가는 토대입니다.
9.1 React Server Components (RSC) 详解
- 정의: RSC는 새로운 유형의 React 컴포넌트이며, 서버에서만 실행되고 렌더링됩니다. 서버 측 리소스(데이터베이스, 파일 시스템, 내부 API)에 직접 접근할 수 있으며, JavaScript 코드는 절대로 클라이언트로 전송되지 않습니다.
- 장점:
- 번들 크기 제로: 순수하게 표시하거나 백엔드 리소스 접근이 필요한 컴포넌트의 경우, 대량의 JS를 클라이언트로 보내지 않아도 됩니다.
- 직접적인 데이터 접근: API 엔드포인트를 만들지 않아도 데이터 조회 로직이 단순해집니다.
- 자동 코드 분할: Client Components(
"use client")와 함께 사용할 때 RSC는 자연스럽게 코드 분할 지점이 됩니다. - 스트리밍과 완벽한 통합: RSC의
async/await데이터 조회는 Suspense 및 스트리밍과 자연스럽게 통합됩니다. 서버는 데이터가 준비되는 즉시 RSC 렌더링 결과(HTML 또는 특별한 명령)를 클라이언트로 스트리밍할 수 있습니다.
- 예시: 7.4절의
DashboardPage예제를 참고하세요.
9.2 部分水合 (Partial Hydration) / 选择性水合 (Selective Hydration)
- 목표: 전체 애플리케이션(또는 큰 영역)을 한 번에 하이드레이션해야 하며, 일부 컴포넌트가 비인터랙티브이거나 당장 상호작용이 필요하지 않아도 리소스를 소비하는 기존 하이드레이션의 문제를 해결합니다.
- 선택적 하이드레이션(React 18의 기능): React 18과 Suspense를 함께 사용하면 선택적 하이드레이션을 구현할 수 있습니다.
<Suspense>로 감싸진 콘텐츠의 하이드레이션 과정은 중단했다가 재개할 수 있습니다.- React는 사용자가 상호작용하는 영역을 우선 하이드레이션합니다. 예를 들어 페이지가 스트리밍 로드되는 중 사용자가 이미 로드되어 표시된
<Suspense>경계 안의 버튼을 클릭하면, 다른 경계가 아직 데이터를 로드 중이더라도 React는 해당 경계와 콘텐츠를 우선 하이드레이션합니다. - 하이드레이션은 비차단적이며, 하이드레이션 중에도 브라우저가 사용자 입력에 응답할 수 있습니다.
- 부분 하이드레이션(더 나아간 개념, 대개 Islands Architecture과 연관):
- 핵심은 페이지 대부분이 정적 HTML이고, 소수의 인터랙티브한 ‘아일랜드’(Islands)만 하이드레이션하는 것입니다.
- Astro 같은 프레임워크가 대표적입니다. Next.js App Router도 RSC(기본적으로 정적)와 Client Components(
"use client")의 조합을 통해 유사한 효과를 구현합니다."use client"로 표시된 컴포넌트와 그 자식 컴포넌트만 클라이언트로 전송되어 하이드레이션됩니다.
9.3 技术协同
RSC, 스트리밍, Suspense, 선택적 하이드레이션이 함께 작동해 뛰어난 성능과 개발 경험을 만들어 냅니다.
- RSC가 서버에서 데이터를 조회하고 렌더링해 HTML 또는 명령을 생성합니다.
- 스트리밍이 이 결과를 클라이언트로 점차 전송합니다.
- Suspense는 데이터가 준비되지 않은 동안
fallback을 표시하고, 데이터가 도착하면 콘텐츠 표시를 조율합니다. - 선택적 하이드레이션은 클라이언트 JS가 필요한 DOM 부분에만 효율적으로 연결되도록 해 사용자 인터랙션을 우선 처리하고 페이지의 반응성을 유지합니다.
10. 实战项目与学习资源
- 데모 프로젝트 사용하기
- 데모 프로젝트 링크
- 브라우저 개발자 도구를 엽니다(보통 F12).
- Network(네트워크) 패널로 전환하고
Doc또는HTML필터를 선택한 뒤 페이지를 새로 고칩니다./blog-basic-streaming또는/profile-modern-streaming요청을 관찰하세요. 요청의 응답 시간이 길고 “Response(응답)” 탭에서 콘텐츠가 청크 단위로 나타나는 것을 볼 수 있습니다.Transfer-Encoding: chunked헤더에 주목하세요. - Elements(요소) 패널로 전환해 스트리밍 블록을 받은 뒤 DOM이 어떻게 바뀌는지, 특히 Suspense 경계의
fallbackUI가 실제 콘텐츠로 교체되는 과정을 관찰하세요. - Console(콘솔) 패널에서 추가한 로그를 확인해
$RC또는processNextFQueue의 실행 단계를 이해하세요. - 현대 스트리밍 예제의 ‘더 보기’ 버튼과 상호작용해 클라이언트 하이드레이션 후의 이벤트 처리를 관찰하세요.
- 추천 학습 경로:
- HTTP
Transfer-Encoding: chunked를 이해합니다. - React
Suspense와React.lazy의 기본 사용법을 익힙니다. - Next.js Pages Router의
getServerSideProps(전통 SSR)와getStaticProps(SSG)를 학습합니다. - Next.js App Router를 중점적으로 학습합니다:
layout.js,page.js,loading.js,error.js의 역할을 이해합니다.- Server Components(RSC)와 Client Components(
"use client")의 차이와 사용법을 익힙니다. - RSC에서
async/await데이터 조회를 실습하고loading.js의 효과를 관찰합니다. - 직접
<Suspense>경계를 추가해 더 세밀하게 제어해 봅니다.
- HTTP
- 공식 문서 및 기타 리소스:
11. 总结
Next.js의 스트리밍과 Suspense 기술, 특히 App Router 및 React Server Components와 결합했을 때 현대 웹 애플리케이션 성능 최적화의 최전선을 대표합니다. 페이지를 독립적으로 로드할 수 있는 블록으로 나누고 Suspense를 활용해 로딩 상태를 우아하게 처리함으로써 다음과 같은 결과를 얻을 수 있습니다.
- TTFB와 FCP를 크게 단축: 사용자가 의미 있는 콘텐츠를 더 빨리 확인할 수 있습니다.
- 체감 성능 향상: 전체 로딩 시간이 같더라도 콘텐츠를 점차 보여 주는 방식은 오래 백색 화면을 보는 것보다 훨씬 빠르게 느껴집니다.
- 사용자 경험 개선: 스켈레톤 화면 같은 로딩 상태는 빈 화면이나 단순한 회전 아이콘보다 더 나은 맥락을 제공합니다.
- 비동기 처리 단순화: Suspense가 로딩 상태 관리를 컴포넌트 로직에서 분리합니다.
- 리소스 사용 최적화: RSC는 클라이언트로 전송되는 JS 양을 줄이고, 선택적 하이드레이션은 클라이언트 효율을 높입니다.
기본적인 $RC 방식의 HTML 교체부터 현대적인 __next_f 방식의 명령 큐, 그리고 Next.js App Router에서 이를 얼마나 높은 수준으로 추상화했는지에 이르는 발전 과정을 이해하면 프레임워크가 하위 수준에서 수행하는 복잡한 작업을认识할 수 있습니다. 이러한 기술들을 익혀두면 기능이强大할 뿐 아니라 속도가 빠르고 반응이 즉각적이며 사용자 경험이 더욱 원활한 Next.js 애플리케이션을 구축할 수 있습니다.