[꿀팁] ssr을 위한 react-dom/server 씹고 뜯고 맛보기 (1)

in-ch·2023년 12월 25일
1

꿀팁

목록 보기
1/14

서론


Next.js를 활용하면 서버 사이드 렌더링을 쉽게 알아서 해주니 정확히 어떤 동작을 수행하여 이를 할 수 있는 것인지 알 수가 없었다.

그냥 이해하기로는 서버에서 HTML를 그려주고 클라이언트로 내려주는 건데 과연 어떤 식으로 내려주는 걸까? 🤨🤨🤨

react-dom/server 패키지는 React 애플리케이션을 서버 측에서 렌더링하는 데 사용되며, 이를 통해 서버 측 렌더링(SSR)을 구현할 수 있다 !!

그렇다면 react-dom/server을 한 번 씹고 뜯고 맛 보도록 하자.

Server React DOM APIs


react-dom/server의 API을 사용하면 React Component를 HTML로 렌더링할 수 있다고 한다. 제공하는 API들을 한 번 살펴보자.

Server APIs for Node.js Streams

node.js 환경에서만 가능한 메소드들이다.

  • 지원하는 API: renderToPipeableStream, renderToStaticNodeStream

메소드들을 살펴보기 전에 먼저 Node.js의 Streams의 개념에 대해서 알아야 한다.

Stream은 순차적인 데이터라고 생각하면 편하다. 좀 더 덧붙이자면 전송하려는 데이터를 조각 조각 나누어서 처리하여 제공한다.

메모리를 아끼기 위해 사용되는 것인데, 만약 대용량의 데이터를 한꺼번에 전송한다고 하면 메모리를 많이 잡아 먹게되니

순차적으로 조각된 데이터를 전송하여 메모리를 효율적으로 쓰기 위한 일종의 인터페이스이다.

Stream의 종류

Node.js에서의 Stream은 크게 Readable, Writable, Duplex, Transform 네 가지 유형이 있다.

  1. Readable Streams: 데이터를 읽을 수 있는 스트림으로 예를 들어 파일에서 데이터를 읽거나, HTTP 요청에서 받은 데이터를 읽는 데 사용됨.

  2. Writable Streams: 데이터를 쓸 수 있는 스트림으로 예를 들어 파일에 데이터를 쓰거나, HTTP 응답으로 데이터를 보내는 데 사용됨.

  3. Duplex Streams: 읽기와 쓰기 모두 가능한 스트림으로, 예를 들어 TCP 소켓이나 웹소켓과 같은 양방향 통신에서 사용됨.

  4. Transform Streams: 데이터를 변환하면서 읽고 쓸 수 있는 스트림으로, 예를 들어 데이터를 압축하거나 암호화하는 데 사용됨.

왜 Stream을 사용할까 ??

생각해보면 간단한데, 대규모 어플리케이션의 경우 대량의 데이터를 처리해야 하기 때문에 메모리를 많이 잡아 먹게 된다. 또한 초기 렌더링 속도 향상에도 영향을 미칠 수 있는데, 서버에서 조각 조각으로 데이터(청크, chuck)를 보내기 때문에 클라이언트가 일부 데이터를 받아서 초기 렌더링을 빠르게 시작할 수도 있다.

renderToPipeableStream

renderToPipeableStream는 React 트리를 파이프 가능한 Node.js 스트림으로 렌더링한다.

const { pipe, abort } = renderToPipeableStream(reactNode, options?)
import { renderToPipeableStream } from 'react-dom/server';

const { pipe } = renderToPipeableStream(<App />, {
  bootstrapScripts: ['/main.js'],
  onShellReady() {
    response.setHeader('content-type', 'text/html');
    pipe(response);
  }
});

더 많은 옵션값들은 여기서 확인 가능합니다 !!

renderToStaticNodeStream

renderToStaticNodeStream는 정적인(hydrate되지 않는 완전 정적인 HTML ) React 트리를 Node.js 스트림으로 렌더링한다.

const stream = renderToStaticNodeStream(reactNode)
import { renderToStaticNodeStream } from 'react-dom/server';

const stream = renderToStaticNodeStream(<Page />);
stream.pipe(response);

reactNode를 제외하고 인자값으로 identifierPrefix를 줄 수 있는데 이는 문자열 접두사로 동일한 페이지에서 여러 루트를 사용할 때 충돌을 피하는 데 사용된다.

Server APIs for Web Streams

브라우저, Deno 등에서 사용할 수 있는 API이다.

  • 지원하는 API: renderToReadableStream

Server APIs for Node.js Streams과 다른 점은 브라우저에서 사용할 수 있다는 것이다.

renderToReadableStream

renderToReadableStream는 React tree를 Readable한 웹 Stream으로 렌더링한다.

const stream = await renderToReadableStream(reactNode, options?)
import { renderToReadableStream } from 'react-dom/server';

async function handler(request) {
  const stream = await renderToReadableStream(<App />, {
    bootstrapScripts: ['/main.js']
  });
  return new Response(stream, {
    headers: { 'content-type': 'text/html' },
  });
}

더 많은 option은 여기서 확인 가능하다.

Server APIs for non-streaming environments

Streaming을 하지 않고 한번에 React 트리를 문자열로 만드는 API들이다.
실제 Production에서는 쓰이지는 않겠지만 구현이 간단하다. 또한 일부 오래된 브라우저에서는 Stream API를 지원하지 않을 수 있는데 호환성이 중요한 경우 위의 API를 활용해 볼 수 있겠다.

renderToString

React 트리를 문자열로 렌더링한다.

const html = renderToString(reactNode)

여기서도 마찬가지로 reactNode 말고 identifierPrefix를 매개 변수로 전달 가능하다.

간단하게 CRA로 프로젝트를 생성해 보고 결과값을 출력해보자.

// App.jsx
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import './App.css';
import TestComponent from './TestComponent';

function App() {
  const {renderToString} = ReactDOMServer;

  console.log(renderToString(<TestComponent />));
                
  return (
    <div className="App">
      <header className="App-header">
      </header>
      <main></main>
    </div>
  );
}

export default App;
// TestComponent
const TestComponent = () => {
    return (<p>renderToString 테스트</p>);
};

export default TestComponent;
  • 결과값
<p>renderToString 테스트</p>

다음과 같이 그려야할 DOM을 string으로 출력해 주는 것을 확인해 볼 수 있다.

마무리

간단하게 react-dom/server에서 지원하는 API에 대해서만 알아봤다.
일단, renderToString만 콘솔에 결과값을 출력해 보았는데, 다른 API들은 node 환경에서 쓸 수 있으므로 서버를 만들어 봐야 한다.

다음 편에서 ssr을 직접 구현해보면서 결과값이 어떻게 출력되어 chunk로 나눠지는 지 확인해 보자 !!

끝 .. !!

profile
인치

0개의 댓글