리액트 서버 사이드 렌더링에 대한 분석

ReactDomServer

공식 사이트 : Server React DOM APIs – React

 

Server React DOM APIs – React

The library for web and native user interfaces

ko.react.dev

리액트에서는 서버 사이드 렌더링을 하기 위해서 react-dom/server.js를 활용할 수 있습니다.

react-dom/server.js 에서는 총 6가지 함수를 지원하는데 아래와 같습니다.

export{
    renderToString,
    renderToStaticMarkup,
    renderToNodeStream, -> 이제 사용하지 않고 renderToPiptableStream을 사용하는 것을 권장한다.
    renderToStaticNodeStream
    renderToPipeableStream
    rednerToReadableStream
} from "./src/server/ReactDOMServerNode"

 

 

renderToString

const html = renderToString(reactNode,options?)

매개변수

  • reactNode : HTML로 렌더링할 HTML 노드
  • optional options : 서버 렌더링을 위한 객체

반환

HTML 문자열

 

주의사항

  • renderToString 은 Suspense 지원에 한계가 있습니다.
    컴포넌트가 중단된다면 renderToString은 즉시 해당 폴백을 HTML로 보내게 됩니다.

renderToString 의 결과

  1. useEffect와 같은 hook이나 이벤트 핸들러는 결과물에 포함되지 않습니다.
    인수로 주어진 리액트 컴포넌트를 기준으로 빠르게 브라우저가 HTML을 제공하여 렌더링하는데 목적이 있는 함수이다. 즉, 클라이언트에서 실행되는 자바스크립트 코드를 포함시키거나 렌더링하는 역할을 하지 않습니다.
    클라이언트에서 실행되지 않고 먼저 완성된 HTML을 서버에서 제공해줄 수 있으므로 초기 렌더링에서 뛰어난 성능을 보여줍니다.
  2. 단순히 빠르게 그려주는 게 목적입니다.
    useEffect나 이벤트 핸들러가 포함되어 있지 않으므로 사용자와 인터렉션을 바로 할 수 없습니다.
  3. 리액트 컴포넌트를 식별합니다.
    data-reactroot 속성을 통해 리액트 컴포넌트의 루트 엘리먼트가 무엇인지 식별합니다.
  4. SEO에 유리합니다.

renderToStringMarkup

const html = renderToStaticMarkup(reactNode, options?)

매개변수, 반환, 주의사항

  • renderToString과 같습니다.

renderToStringMarkup의 결과

  • 함수를 사용하여 렌더링하게 될 경우, 브라우저 API를 실행할 수 없습니다.
    결과물은 hydrate를 수행하지 않는다는 가정하에 순수한 HTML만 변환하기 때문입니다.
  • 리액트의 이벤트 리스너가 필요없는 순수한 HTML을 만들때 사용합니다.
    리액트의 속성을 제거한 결과물을 만들기 때문에 속도나 용량 측면에서 유리합니다.

renderToNodeStream

-> 더 이상 사용되지 않습니다!

이 API는 향후 React의 주요 버전에서 제거될 예정입니다. 대신 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);
	}
});

 

매개 변수

반환값

renderToPipeableStream은 두 개의 메서드가 있는 객체를 반환합니다.

  • pipe는 HTML을 제공된 쓰기 가능한 Node.js 스트림으로 출력합니다. 스트리밍을 활성화하려면 onShellReady에서
    크롤러와 정적 생성을 사용하려면 onAllReady에서 pipe를 호출해야 합니다.
  • abort를 사용하면 서버 렌더링을 중단하고 나머지는 클아이언트에서 렌더링할 수 있습니다.

사용법

import { renderToPipeableStream } from 'react-dom/server';

  // 경로 핸들러 문법은 백엔드 프레임워크에 따라 다릅니다.
  app.use('/', (request, response) => {
    const { pipe } = renderToPipeableStream(<App />, {
      bootstrapScripts: ['/main.js'],
      onShellReady() {
        response.setHeader('content-type', 'text/html');
        pipe(response);
      }
    });
  });

루트 컴포넌트와 함께 부트스트랩<script> 경로 목록을 제공해야 합니다. 루트 컴포넌트는 루트<html>태그를 포함한 전체 문서를 반환해야합니다.

 

다음과 같이 표시될 수 있습니다.

export default function App() {
    return (
      <html>
        <head>
          <meta charSet="utf-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <link rel="stylesheet" href="/styles.css"></link>
          <title>My app</title>
        </head>
        <body>
          <Router />
        </body>
      </html>
    );
  }

React는 doctype과 부트스트랩 <script> 태그를 결과 HTML 스트립에 삽입합니다.

<!DOCTYPE html>
<html>
  <!-- ... 컴포넌트의 HTML ... -->
</html>
<script src="/main.js" async=""></script>

클라이언트에서 부트스트랩 스크립트는 hydrate를 호출하여 전체 document를 하이드레이트 해야합니다.

 import { hydrateRoot } from 'react-dom/client';
 import App from './App.js';
  
 hydrateRoot(document, <Ap

이렇게 하면 서버에서 생성된 HTML에 이벤트 리스너가 첨부되어 상호 작용이 가능해집니다.

 

더 많은 사용법

renderToPipeableStream – React 참조

 

renderToReadableStream

renderToReadableStream 은 Readable Web.Stream 을 이용해 React tree를 그립니다.

const stream = await renderToReadableStream(reactNode, options?)

 

❗ 이 API는 Web.stream에 의존합니다. Node.js의 경우 renderToPipeableStream을 대신 사용해야 합니다.

 

레퍼런스

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' },
  });
}

클라이언트에서, hydrateRoot를 호출해 서버에서 생성된 HTML을 상호작용 가능하도록 만듭니다.

 

매개변수

  • reactNode : 사용자가 HTML로 렌더링하고자 하는 React Node입니다.
  • optional options : 스트리밍 옵션을 지정할 수 있습니다.
    renderToReadableStream – React 참조

반환값

rednerToReadableStream은 Promise를 반환합니다.

  • shell렌더링에 성공했다면, 반환된 Promise는 Readable Web Stream으로 해결됩니다.
  • sheel렌더링에 실패하면, 반환된 Promise는 취소됩니다.

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

  • allReady : 모든 추가 컨텐츠와 shell의 렌더링을 포함한 모든 렌더링이 완료된 Promise의 추가 프로퍼티입니다.
    크툴러와 정적 생성을 위해 await stream.allReady를 응답 반환 전에 사용할 수 있습니다.
    설정 시에는 로딩 진행 상태를 받을 수 없습니다.
    스트림은 최종 HTML을 포함합니다.

사용법

Readable Web Stream을 이용해 React tree를 HTML 처럼 렌더링하기

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' },
    });
  }

root컴포넌트와 함께 bootstrap<script> 경로 리스트를 제공해야합니다. 제공된 root 컴포넌트는 최상위 <html>태그를 포함한 모든 문서를 포함해서 반환되어야 합니다.

 

다음과 같은 형태가 되어야 합니다.

export default function App() {
    return (
      <html>
        <head>
          <meta charSet="utf-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <link rel="stylesheet" href="/styles.css"></link>
          <title>My app</title>
        </head>
        <body>
          <Router />
        </body>
      </html>
    );
  }

React 는 doctype과 bootstrap <script> 태그들을 결과 HTML 스트림에 주입합니다.

<!DOCTYPE html>
 	<html>
   	 <!-- ... 사용자가 직접 작성한 컴포넌트의 HTML ... -->
  	</html>
 <script src="/main.js" async=""></script>

클라이언트에서 추가된 bootstrap 스크립트는 hydrateRoot를 호출해 document 전체를 hydrate 해야 합니다.

import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
  
hydrateRoot(document, <App />);

이 과정에서 서버에서 렌더링된 HTML에 이벤트 리스너를 붙이고 HTML을 상호작용 가능하게 만듭니다.

 

더 많은 사용법

renderToReadableStream – React 참조

 

hydreateRoot

hydrateRoot는 react-dom/server를 통해 사전에 만들어진 HTML으로 그려진 브라우저 DOM 노드 내부에 React 컴포넌트를 렌더링합니다.

 

레퍼런스

const root = hydrateRoot(domNode, reactNode, options?)

서버 환경에서 React로 앞서 만들어진 HTML에 후에 만들어진 React를 hydreateRoot를 호출해 붙입니다.

import { hydrateRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, reactNode);

 

React는 domNode 내부의 HTML에 붙어 내부 DOM을 직접 관리 할 것입니다.

App을 React로 전부 만들었다면 보통은 단 하나의 root component와 함께 hydrateRoot를 한번 호출할 것입니다.

 

매개변수

  • domNode : 서버에서 root element로 렌더링 된 Dom element
  • reactNode : 앞서 존재하는 HTML에 렌더링하기 위한 React 노드
  • optional options : React root에 옵션을 주기 위한 객체
    hydrateRoot – React 참조

반환

hydrateRoot는 2가지 메소드가 포함된 객체를 반환합니다.

render , unmount

 

주의사항

  • hydrateRoot()는 서버에서 렌더링된 내용과 후에 렌더링 된 내용이 동일해야합니다. 동일하지 않은 부분들은 직접 ㅓ버그로 취급하거나 수정해야합니다.
  • App에서 hydrateRoot를 한번만 호출해야 합니다. 프레임워크를 사용한다면 프레임워크에서 대신 호출해줍니다.
  • App을 사전에 렌더링된 HTML 없이 클라이언트에서 직접 렌더링 한다면 hydrateRoot()는 지원되지 않습니다.
    createRoot()를 대신 사용합니다.

root.render(reactNode)

hydrate된 React root 부터 내부 컴포넌트를 새로운 React 컴포넌트로 갱신하기 위해 root.render를 호출합니다.

브라우저 DOM 요쇼들도 함께 갱신됩니다.

root.render(<App />);

 

React는 hydrate된 root부터 내부를 <App /> 으로 갱신합니다.

 

매개변수

  • reactNode : 갱신하고 싶은 React 노드 입니다. 주로 <App /> 같은 JSX를 넘기지만
    createElement()로 만든 React 엘리먼트를 넘겨도 되고 문자열이나 숫자, null, undefined를 넘겨도 됩니다.

반환값

root.render는 undefined를 반환합니다.

 

주의사항

hydrate가 끝나기 전에 root.render를 호출하면 React는 서버에서 렌더링된 HTML을 모두 없애고 클라이언트에서 렌더링된 컴포넌트들로 완전히 교체합니다.

 

root.unmount()

root.unmount 를 호출해 React.root부터 그 하위에 렌더링 된 트리를 삭제합니다.

root.unmount()

 

root.unmount를 호출하는 경우가 거의 없습니다.

주로 React root부터 혹은 그 상위에서부터 시작된 Dom node 들을 다른 코드에 의해 DOM에서 삭제되어야 하는 경우 유용합니다. root.unmount()를 호출해 React에게 삭제된 컨텐츠들을 그만 다루라고 알려줘야 합니다. 그렇지 않으면 삭제되어 버린 React root 내부의 컴포넌트들은 삭제되지 않을 것이며 컴퓨팅 자원에 손해가 있을 수 있습니다.

 

root.unmount를 호출하면 root 내부의 모든 컴포넌트를 umount 하고 root Dom node 에서 React를 분리합니다.

root 내부의 event handler와 state 까지 모두 포함해 unmount 및 삭제 됩니다.

 

주의사항

  • root.unmount를 호출하면 root부터 그 안의 모든 컴포넌트가 unmount되고 root Dom node 에서 React를 떼어냅니다.
  • root.unmount를 한번 호출한 이후엔 root.render를 root에 다시 사용할 수 없습니다. umount된 root에 다시 root.render를 호출하려고 한다면 에러를 발생시킵니다.

사용법

 

- 서버에서 렌더링된 html을 hydrate하기

react-dom/server로 앱의 HTML을 생성했다면 클라이언트에서 hydrate 해주어야 합니다.

import { hydrateRoot } from 'react-dom/client';

hydrateRoot(document.getElementById('root'), <App />);

 

위 코드를 통해 서버 HTML을 브라우저 DOM node 에서 React 컴포넌트를 이용해 hydrate 해줄 것 입니다.

주로 앱을 시작할 때, 한번 실행됩니다. 프레임워크를 사용중이라면 프레임워크가 알아서 사용해줍니다.

 

앱을 hydrate하기 위해서 React는 컴포넌트의 로직을 사전에 서버에서 만들어진 HTML에 붙입니다.

Hydration을 통해 서버에서 만들어진 최초의 HTML를 브라우저에 완전히 인터랙티브한 앱으로 바꿔주게 됩니다.

 

더 많은 사용법

hydrateRoot – React  참조

 

레퍼런스 참조

Server React DOM APIs – React

 

Server React DOM APIs – React

The library for web and native user interfaces

ko.react.dev

 

 

<