무엇
resume은 prerender가 미완성 상태로 남긴 React 트리를 이어받아, 나머지를 스트리밍 서버 렌더링으로 완성하는 API다. react-dom/server의 Web Streams 버전이다.
reactNode는 prerender를 호출했던 것과 같은 트리여야 하고, 문서 전체(<html> 포함)를 그린다. Node.js 환경이면 대신 resumeToPipeableStream을 쓴다.
import { resume } from 'react-dom/server'
import { getPostponedState } from './storage'
async function handler(request: Request) {
const postponed = await getPostponedState(request)
const stream = await resume(<App />, postponed)
return new Response(stream, { headers: { 'content-type': 'text/html' } })
}
무슨 일이 일어나나
prerender는 아직 준비 안 된 Suspense 경계를 만나면 거기서 멈추고, 완성된 부분(prelude)과 opaque한 postponed 객체를 돌려준다. 이 postponed를 저장소(redis, 파일, S3 등)에 저장해뒀다가, 실제 요청이 들어오면 resume에 넘겨 나머지를 이어 그린다.
resume은 루트부터 다시 렌더링을 훑지만, 이미 완전히 그려진 컴포넌트는 건너뛰고 못 끝낸 지점부터 스트리밍을 이어간다 — 처음부터 다시 렌더링하는 게 아니라 이어붙이기다.
bootstrapScripts, bootstrapModules, identifierPrefix 같은 옵션은 여기서 다시 주지 않는다. 이 값들은 최초 prerender 호출에서 이미 정해졌고, resume은 그 결과를 그대로 이어받기 때문이다.
사용법
반환된 스트림에는 allReady 프로미스가 붙어 있다. 렌더링이 전부 끝날 때까지 기다렸다가 응답해야 하는 크롤러나 정적 생성 상황에서 쓴다.
import { resume } from 'react-dom/server'
import { getPostponedState } from './storage'
async function handler(request: Request, writable: WritableStream) {
const postponed = await getPostponedState(request)
const stream = await resume(<App />, postponed, {
onError(error) {
console.error(error)
},
})
return stream.pipeTo(writable)
}
signal로 중간에 끊어 나머지를 클라이언트에 넘길 수 있다. nonce는 최초 prerender에 스크립트를 넘기지 않았을 때만 준다.
실무 예시
부분 프리렌더링(PPR) 파이프라인의 뒷부분이다. 정적 셸은 prerender로 미리 만들어 CDN에서 서빙하고, 동적 데이터가 필요한 요청이 들어오면 저장해둔 postponed를 resume에 넘겨 나머지를 스트리밍으로 완성한다. Next.js의 PPR이 개념적으로 이 짝을 쓴다.
왜 중요한가
직접 호출할 일은 거의 없다. 프레임워크가 PPR 구현 안에서 감싸서 쓰는 저수준 API다. 다만 "정적 셸 + 나머지를 잇는 스트리밍"이라는 모델 자체는 최신 React 렌더링 전략을 이해하는 데 필요하고, Web Streams 환경이 아니면 resumeToPipeableStream을 찾아야 한다는 것 정도는 기억해 둘 만하다.
Reference