React에 SSR 적용하기
do4ng
2 years ago

보통 우리가 next와 같은 아무 프레임워크 없이 React로 앱을 만들때는 간단하게 reactreact-dom을 다운로드 받은다음에

React
import React from 'react';
import ReactDOM from 'react-dom';
 
ReactDOM.render(<>Hello World</>, document.getElementById('app'));

이런식으로 간단하게 만들면 된다.

근데 cra로 다운로드 받은 react 탬플릿에서 index.html 파일을 자세하게 보면 <noscript />태그가 있는데 이 <noscript />가 하는 역할이 브라우저가 javascript 실행을 막았을 때 띄우는 것이라고 한다.

근데 조금만 더 생각해보면 여기서 React는 자바스크립트가 활성화되야지만 페이지를 보여준다라는 것을 알 수 있다.

근데 이 자바스크립트 비활성화 문제는 SSR라는 것을 사용하면 어느정도 해결할 수 있다.
이번 포스트에서는 SSR을 구현해보도록 하겠다.

#구현 - 클라이언트

먼저 화면에 띄울 페이지를 만든다.

React
// src/app.tsx
import React from 'react';
 
export default function app() {
  return <>Hello World!</>;
}

그다음에 클라이언트에서 랜더링하라는 코드를 작성하면 된다.

React
// src/index.tsx
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import App from './app';
 
hydrateRoot(document.getElementById('app') as any, <App />);

공식문서에 따르면 hydrateRoot

hydrateRoot서버 환경에서 React에 의해 이미 렌더링된 기존 HTML에 React를 "첨부"하기 위한 호출 입니다. - 문서

라고한다. 뭐라하는지는 정확히 모르겠는데 HTML에 React를 적용한다는 것 같다.

이렇게 클라이언트 구현은 끝났다.

#구현 - 서버

React
// server/index.tsx
import path from 'path';
import { readFileSync } from 'fs';
import React from 'react';
import express from 'express';
import ReactDOMServer from 'react-dom/server';
 
import App from '../src/app';
 
const PORT = 3000;
const app = express();
 
app.get('/', (req, res) => {
  const app = ReactDOMServer.renderToString(<App />);
 
  const index = readFileSync(path.join(process.cwd(), 'build/index.html')).toString();
 
  return res.send(index.replace('<div id="app"></div>', `<div id="app">${app}</div>`));
});
 
app.use(express.static('./build'));
 
app.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
});

renderToString()은 리엑트 컴포넌트를 HTML으로 바꾸는 역할을 한다.

HTML
<!--build/index.html-->
<body>
  <div id="app"></div>
  <script src="/index.js"></script>
  <!-- /src/index.tsx => /build/index.js -->
</body>

모든 준비는 끝났다.

이제 .tsx 파일들을 .js 파일로 바꿔주기만 하면 된다.

빌드파일을 작성해준다.

Typescript
const build = require('esbuild');
 
build.build({
  entryPoints: ['./src/index.tsx'],
  outfile: './build/index.js',
  bundle: true,
  platform: 'browser',
});
 
build.build({
  entryPoints: ['./server/index.tsx'],
  outfile: './build/server.js',
  bundle: true,
  platform: 'node',
});

완성이다. 자바스크립트를 비활성화 시켜도 활성화한 것이랑 똑같은 결과가 나온다.

근데 참고로 자바스크립트를 비활성화시켜도 활성화시킨것처럼 모든 기능들이 작동하진 않는다. 그냥 눈에 보이는 것만 똑같은 것이다.