본문 바로가기

오픈소스/노드

[Node] React 정리(6) - 서버사이드 렌더링

리액트로 프로젝트를 생성하여 개발하는 과정 중 리액트의 필수 문법 및 기초적인 부분, 심화 과정 등을 정리한 문서입니다. 정리한 부분에는 제가 이해하는 관점에서만 정리를 하였기 때문에 초점은 보여주는 형식이 아닌 제가 필요할 때 사용하려는 목적이 담겨져 있습니다. 이 문서는 어떠한 리액트의 이론을 다루는 것이 아닙니다. 또한 현재 정리하려는 것은 여러분이 필요한 JSX 부분이 아닐 수 있음을 알려드립니다.

 

 본 과정에서는 차례대로 난이도가 증가하는 정리를 하지 않았습니다. 필요한 부분만을 사용하거나 훑어보는 용도로 사용하시기 바랍니다.

 

 리액트로 만든 애플리케이션을 서버 사이드 랜더링하는 방법을 소개하고자 합니다.

 먼저 적용할 코드는 아래와 같습니다.

 

GitHub - PacktPublishing/Node.js-Design-Patterns-Third-Edition: Node.js Design Patterns Third Edition, published by Packt

Node.js Design Patterns Third Edition, published by Packt - GitHub - PacktPublishing/Node.js-Design-Patterns-Third-Edition: Node.js Design Patterns Third Edition, published by Packt

github.com

 

 그리고 서버 사이드 랜더링을 적용한 코드는 아래와 같습니다.

 

GitHub - PacktPublishing/Node.js-Design-Patterns-Third-Edition: Node.js Design Patterns Third Edition, published by Packt

Node.js Design Patterns Third Edition, published by Packt - GitHub - PacktPublishing/Node.js-Design-Patterns-Third-Edition: Node.js Design Patterns Third Edition, published by Packt

github.com

 

 한번 코드를 보고 파악을 해보세요. 그리고 제가 설명하는 것들을 읽었을 때 거기서 획득하는 지식은 자신의 것으로 남게 되어있을 것입니다. 그럼 이론적인 내용과 서버 사이드 랜더링을 사용하는 이유와 적용 방법에 대해 간략하게 설명하겠습니다.

 

 

# Contents


  • 서버 사이드 랜더링
  • 서버 사이드 랜더링 적용 방법

 

 

# 서버 사이드 랜더링


 범용 애플리케이션을 작성하기 위해서 필요한 개념일거라고 생각합니다. 리액트 네이티브의 예시로 설명해보도록 하겠습니다.  리액트 네이티브(React Native)는 페이스북이 개발한 오픈 소스 모바일 애플리케이션 프레임워크입니다. 안드로이드iOSUWP용 애플리케이션을 개발하기 위해 사용되며, 개발자들이 네이티브 플랫폼 기능과 더불어 리액트를 사용할 수 있게 해준 다고 나무위키에서 가져온 내용입니다. 리액트 네이티브를 사용하면 iOS와 안드로이드를 동시에 개발할 수 있으며 하나의 프로젝트를 통해 2개 이상의 개발환경에서 개발한 결과가 나오게 되므로 시간 효율성, 비용성에서도 엄청난 이익을 가져올 수 있습니다. .

 

 이처럼 서버 사이드 랜더링 을 이용하게 되면 클라이언트에서 만든 리액트 프로젝트를 이용하여 서버 쪽 에서도 리액트 프로젝트를 만들어 이용할 수 있게 된 것입니다. 하지만 서버 사이드 랜더링은 장점보다는 단점이 부각되는 편입니다. 리액트 네이티브와는 다르게 비용과 시간 효율성을 투자하여 서버 환경에서의 똑같은 프로젝트를 하나 더 만든다고 생각하시면 저로써도 사실 왜 존재하는 것일까 라는 의문을 많이 던져봅니다. 또한 프로젝트의 복잡도도 올라가게 되므로 나중에 유지보수 측면에서도 힘들 것이라 생각합니다.

 

 개발은 개발자가 하지만 유지보수는 개발자가 하지 않습니다. 그러면 운영자가 유지보수 할 수있는 지식을 가지고 있어야 하는데 두배로 힘든 것이죠...

 

 그럼에도 불구하고 일단 적어보겠습니다.

 서버 사이드 랜더링 적용방법에 대해..

 

 

# 서버 사이드 랜더링 적용 방법


 먼저 클라이언트에서 개발 된 소스코드를 가져와야 합니다. 코드는 아래와 같습니다.

 

GitHub - PacktPublishing/Node.js-Design-Patterns-Third-Edition: Node.js Design Patterns Third Edition, published by Packt

Node.js Design Patterns Third Edition, published by Packt - GitHub - PacktPublishing/Node.js-Design-Patterns-Third-Edition: Node.js Design Patterns Third Edition, published by Packt

github.com

 

 위 코드를 이용하여 서버 애플리케이션을 만들어 보겠습니다.

 다음 명령으로 프로젝트에 fastify 및 esm을 추가하여 시작하겠습니다.

 

npm install --save fastify fastify-static esm

 

 이제 src/server.js 에서 서버 애플리케이션을 만들 수 있습니다.

 src/server.js 적용해야 하는 코드

 

import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
import react from 'react'
import reactServer from 'react-dom/server.js'
import htm from 'htm'
import fastify from 'fastify'
import fastifyStatic from 'fastify-static'
import { StaticRouter } from 'react-router-dom'
import { App } from './frontend/App.js'

const __dirname = dirname(fileURLToPath(import.meta.url))
const html = htm.bind(react.createElement)

// ①
const template = ({ content }) => `<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My library</title>
  </head>
  <body>
    <div id="root">${content}</div>
    <script type="text/javascript" src="/public/main.js"></script>
  </body>
</html>`

const server = fastify({ logger: true }) // ②

server.register(fastifyStatic, { // ③
  root: resolve(__dirname, '..', 'public'),
  prefix: '/public/'
})

server.get('*', async (req, reply) => { // ④
  const location = req.raw.originalUrl
  const staticContext = {}
  // ⑤
  const serverApp = html` 
    <${StaticRouter}
      location=${location}
      context=${staticContext}
    >
      <${App}/>
    </>
  `
  const content = reactServer.renderToString(serverApp) // ⑥
  const responseHtml = template({ content })

  let code = 200
  if (staticContext.statusCode) {
    code = staticContext.statusCode
  }

  reply.code(code).type('text/html').send(responseHtml)
})

const port = Number.parseInt(process.env.PORT) || 3000 // ⑦
const address = process.env.ADDRESS || '127.0.0.1'

server.listen(port, address, function (err) {
  if (err) {
    console.error(err)
    process.exit(1)
  }
})

 

 코드가 길기 때문에 여기서 소개해야 할 주요 개념을 단계별로 살펴보겠습니다.

 

  1. 웹팩 개발 서버를 사용하지 않을 것이므로 서버에서 페이지의 전체 HTML코드를 반환 해야 합니다. 여기서는 함수와 템플릿 리터럴을 사용하여 모든 페이지에 대한 HTML 템플릿을 정의합니다. 서버에서 렌더링한 React 애플리케이션의 결과 content를 이 템플릿에 전달하여 최종 HTML을 클라이언트 로 반환합니다.
  2. 여기서 Fastify 서버 인스턴스 를 만들고 로깅을 활성화합니다.
  3. 템플릿 코드에서 알수 있듯이 웹 애플리케이션은 /public/main.js 스크립트로 로드합니다. 이 파일은 웹팩에 의해 생성되는 프론트엔드 번들입니다. 여기서는 Fastify 서버 인스턴스가 fastify-static 플러그인을 사용하여 public 폴더의 모든 정적 파일을 서비스 하도록 합니다.
  4. 이 줄에서는 서버에 대한 모든 GET 요청을 가로채는 catch-all 라우트 를 정의합니다. 우리가 catch-all 라우트를 정의하는 이유는 실제 라우팅 로직이 이미 React 애플리케이션에 포함 되어 있기 때문입니다. React 애플리케이션이 렌더링할 떄 현재 URL을 기반으로 적절한 페이지 컴포넌트를 표시합니다.
  5. 서버 측에서 react-router-dom의 StaticRouter 인스턴스를 사용하여 애플리케이션 컴포넌트를 감싸야 합니다. StaticRouter는 서버 측 렌더링에 사용할 수 있는 React Router 버전입니다. 이 라우터는 브라우저 윈도우에서 현재의 URL을 획득하지 않고 location 속성을 통해 서버로부터 직접 현재 URL을 전달 할 수 있습니다.
  6. 끝으로 React의 renderToString() 함수를 이용하여 serverApp 컴포넌트에 대한 HTML 코드를 생성할 수 있습니다. 생성된 HTML은 주어진 URL에서 클라이언트 측 애플리케이션에 의해 생성한 HTML과 동일합니다. 다음 몇줄에서는 template() 함수를 사용하여 페이지 템플릿으로 생성된 HTML 코드인 content을 감싸고 그 결과를 클라이언트에 전송합니다.

 

이제 npm run build 를 실행하여 프론트엔드 번들을 만든 후, 마지막으로 다음과 같이 서버를 실행할 수 있습니다.

 

node -r esm src/server.js

 


결과는 아래와 같이 나오게 됩니다.

서버 랜더링 전

 

 

 

서버 랜더링 후