resumeReadable Web Stream을 이용해 사전 렌더링된 React 트리를 스트리밍합니다.

const stream = await resume(reactNode, postponedState, options?)

중요합니다!

이 API는 Web Streams에 의존합니다. Node.js에서는 resumeToPipeableStream을 대신 사용하세요.


레퍼런스

resume(node, postponedState, options?)

resume을 호출해 사전 렌더링된 React 트리의 렌더링을 재개하고, 이를 HTML로 Readable Web Stream에 렌더링합니다.

import { resume } from 'react-dom/server';
import {getPostponedState} from './storage';

async function handler(request, writable) {
const postponed = await getPostponedState(request);
const resumeStream = await resume(<App />, postponed);
return resumeStream.pipeTo(writable)
}

아래에서 더 많은 예시를 확인하세요.

매개변수

  • reactNode: prerender를 호출할 때 전달한 React 노드입니다. 예를 들어, <App />과 같은 JSX 엘리먼트입니다. 전체 문서를 나타낼 것으로 예상되므로 App 컴포넌트는 <html> 태그를 렌더링해야 합니다.
  • postponedState: prerender API에서 반환된 불분명한 postpone 객체로, 저장해 둔 위치(예: Redis, 파일, S3)에서 불러옵니다.
  • optional options: 스트리밍 옵션을 지정할 수 있는 객체입니다.

반환값

resume은 Promise를 반환합니다.

  • resumeshell을 성공적으로 생성하면, 해당 Promise는 Writable Web Stream으로 파이프할 수 있는 Readable Web Stream으로 이행됩니다.
  • shell에서 오류가 발생하면, 해당 Promise는 그 오류와 함께 거부됩니다.

반환된 스트림은 다음과 같은 추가적인 프로퍼티를 가지고 있습니다.

  • allReady: 모든 렌더링이 완료되면 이행되는 Promise입니다. 크롤러와 정적 생성을 위해 응답을 반환하기 전에 await stream.allReady를 사용할 수 있습니다. 이렇게 하면 점진적 로딩은 사용할 수 없습니다. 스트림에는 최종 HTML이 포함됩니다.

주의 사항

  • resumebootstrapScripts, bootstrapScriptContent, bootstrapModules 옵션을 받지 않습니다. 대신 postponedState를 생성하는 prerender 호출에 이 옵션들을 전달해야 합니다. 또한 쓰기 가능한 스트림에 부트스트랩 콘텐츠를 수동으로 주입할 수도 있습니다.
  • prerenderresume에서 접두사가 동일해야 하므로, resumeidentifierPrefix를 받지 않습니다.
  • nonce는 prerender에 전달할 수 없으므로, prerender에 스크립트를 제공하지 않는 경우에만 resumenonce를 전달해야 합니다.
  • resume은 사전 렌더링이 완전히 완료되지 않은 컴포넌트를 찾을 때까지 루트부터 다시 렌더링합니다. 사전 렌더링이 완전히 완료된 컴포넌트(해당 컴포넌트와 자식들의 사전 렌더링이 모두 완료된 경우)만 완전히 건너뜁니다.

사용법

사전 렌더링 재개하기

import {
  flushReadableStreamToFrame,
  getUser,
  Postponed,
  sleep,
} from "./demo-helpers";
import { StrictMode, Suspense, use, useEffect } from "react";
import { prerender } from "react-dom/static";
import { resume } from "react-dom/server";
import { hydrateRoot } from "react-dom/client";

function Header() {
  return <header>Me and my descendants can be prerendered</header>;
}

const { promise: cookies, resolve: resolveCookies } = Promise.withResolvers();

function Main() {
  const { sessionID } = use(cookies);
  const user = getUser(sessionID);

  useEffect(() => {
    console.log("reached interactivity!");
  }, []);

  return (
    <main>
      Hello, {user.name}!
      <button onClick={() => console.log("hydrated!")}>
        Clicking me requires hydration.
      </button>
    </main>
  );
}

function Shell({ children }) {
  // In a real app, this is where you would put your html and body.
  // We're just using tags here we can include in an existing body for demonstration purposes
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}

function App() {
  return (
    <Shell>
      <Suspense fallback="loading header">
        <Header />
      </Suspense>
      <Suspense fallback="loading main">
        <Main />
      </Suspense>
    </Shell>
  );
}

async function main(frame) {
  // Layer 1
  const controller = new AbortController();
  const prerenderedApp = prerender(<App />, {
    signal: controller.signal,
    onError(error) {
      if (error instanceof Postponed) {
      } else {
        console.error(error);
      }
    },
  });
  // We're immediately aborting in a macrotask.
  // Any data fetching that's not available synchronously, or in a microtask, will not have finished.
  setTimeout(() => {
    controller.abort(new Postponed());
  });

  const { prelude, postponed } = await prerenderedApp;
  await flushReadableStreamToFrame(prelude, frame);

  // Layer 2
  // Just waiting here for demonstration purposes.
  // In a real app, the prelude and postponed state would've been serialized in Layer 1 and Layer would deserialize them.
  // The prelude content could be flushed immediated as plain HTML while
  // React is continuing to render from where the prerender left off.
  await sleep(2000);

  // You would get the cookies from the incoming HTTP request
  resolveCookies({ sessionID: "abc" });

  const stream = await resume(<App />, postponed);

  await flushReadableStreamToFrame(stream, frame);

  // Layer 3
  // Just waiting here for demonstration purposes.
  await sleep(2000);

  hydrateRoot(frame.contentWindow.document, <App />);
}

main(document.getElementById("container"));

추가로 읽어보기

재개 동작은 renderToReadableStream과 유사합니다. 더 많은 예시는 renderToReadableStream의 사용법 섹션을 확인하세요. prerender의 사용법 섹션에는 prerender 사용 방법에 대한 예시가 포함되어 있습니다.