zerovoids.dev
react-domAPIs

prerenderToNodeStream

const { prelude, postponed } = await prerenderToNodeStream(reactNode, options?)

정적 생성을 위해 React 트리를 끝까지 다 그린 뒤 Node.js 스트림으로 HTML을 돌려주는 렌더링 API.

verified v19.2 · 2026-08-05
react-domapisssgstatic-generationnodejs

무엇

prerenderToNodeStream은 prerender의 Node.js 버전이다. React 트리를 정적 HTML로 미리 그려 Node.js Stream으로 돌려준다.

Deno나 엣지 런타임처럼 Web Streams를 쓰는 환경이면 이 API 대신 prerender를 쓴다.

import { prerenderToNodeStream } from 'react-dom/static'

app.use('/', async (request, response) => {
  const { prelude } = await prerenderToNodeStream(<App />, { bootstrapScripts: ['/main.js'] })
  response.setHeader('content-type', 'text/html')
  prelude.pipe(response)
})

무슨 일이 일어나나

renderToPipeableStream은 셸을 먼저 흘려보내는 스트리밍 SSR이지만, prerenderToNodeStream은 Suspense로 기다리는 데이터까지 전부 resolve된 뒤에 완성된 HTML을 돌려준다. 빌드 타임 정적 생성(SSG)에 맞는 API라 결과물에 로딩 fallback이 섞이지 않는다.

반환값은 prelude(HTML을 담은 Node.js Stream)와 postponed(다 못 끝났을 때 resumeToPipeableStream 또는 resumeAndPrerenderToNodeStream으로 이어 그릴 때 쓰는 값, 완전히 끝났으면 null) 두 필드다.

사용법

build.tsx
async function renderToString() {
  const { prelude } = await prerenderToNodeStream(<App />, { bootstrapScripts: ['/main.js'] })
  return new Promise<string>((resolve, reject) => {
    let data = ''
    prelude.on('data', (chunk) => { data += chunk })
    prelude.on('end', () => resolve(data))
    prelude.on('error', reject)
  })
}

signal로 중간에 끊을 수 있고, 이 경우 아직 안 끝난 Suspense 경계는 fallback 상태로 결과에 남는다. bootstrapScripts, bootstrapModules, bootstrapScriptContent, identifierPrefix, namespaceURI, onError, progressiveChunkSizerenderToPipeableStream과 같은 옵션이다. nonce는 지원하지 않는다 — nonce는 요청마다 달라야 하는데 프리렌더링 결과는 여러 요청에 재사용되기 때문이다.

실무 예시

Node.js 기반 빌드 파이프라인에서 페이지를 정적 HTML로 미리 뽑아 CDN에 올려 두는 SSG에서 쓴다. Deno나 엣지 런타임이 아니라 Node.js 환경이라는 점만 prerender와 다르다.

왜 중요한가

직접 호출할 일은 드물고 프레임워크의 빌드 파이프라인 안에서 쓰인다. "스트리밍(renderToPipeableStream)과 정적 생성(prerenderToNodeStream)은 다른 API"라는 구분과, 결과가 끝나지 않았을 때 남는 postponed가 이후 resume 계열 API로 이어진다는 것 정도는 기억해 둘 만하다.

Reference