4장 서버 사이드 렌더링 (1)

강석우's avatar
Mar 25, 2024
4장 서버 사이드 렌더링 (1)

4.1 서버 사이드 렌더링이란?

4.1.1 싱글 페이지 애플리케이션의 세상

싱글 페이지 애플리케이션

렌더링과 라우팅에 필요한 대부분의 기능을 서버가 아닌 브라우저의 자바스크립트에 의존하는 방식을 의미한다.

최초에 서버에서 최소한의 데이터를 불러온 이후부터는 이미 가지고 있는 자바스크립트 리소스와 브라우저 API를 기반으로 모든 작동이 이뤄진다.

최초에 로딩해야 할 자바스크립트 리소스가 커지는 단점이 있지만 한번 로딩된 이후에는 서버를 거쳐 필요한 리소스를 받아올 일이 적어지기 때문에 사용자에게 훌륭한 UI/UX를 제공한다.

전통적인 방식의 애플리케이션과 싱글 페이지 애플리케이션의 작동 비교

페이지 전환이 발생할 때마다 새롭게 페이지를 요청하고, HTML 페이지를 다운로드해 파싱하는 작업을 거친다.

spa

전통

페이지 전환

처음 요청 후 자바스크립트

전환 마다 새로운 리소스 다운

렌더링

페이지 전환에 필요한 일부 영역만 다시 렌더링

페이지가 전체 리렌더링

싱글 페이지 렌더링 방식의 유행과 JAM 스택의 등장

과거 : PHP 또는 JSP 기반의 웹 애플리케이션

자바스크립트의 모듈화 방안이 논의되며 CommonJS 와 AMD 등장.

AngularJS, Knockout.js 등장

자바스크립트의 역할이 더욱 중요해지며 React, Vue, Angular의 시대가 오게됨.

과거 LAMP ( Linux, Apache, MySQL, PHP ) 에서

현재 JAM ( Javascript, API, Markup ) 스택으로 웹개발이 변경되었다.

또한 JAM스택의 인기에 힘입어 MERN( MongoDB, Express.js, React, Node.js) 스택이 대세가 되어가고 있다.

4.1.2 서버 사이드 렌더링이란?

싱글 페이지 렌더링이란 자바스크립트를 활용해 하나의 페이지에서만 렌더링을 수행하는 것이다.
서버 사이드 렌더링은 최초에 사용자에게 보여줄 페이지를 서버에서 렌더링해 빠르게 사용자에게 화면을 제공하는 방식을 의미한다.

웹페이지에서 사용되는 종류와 범위가 점점 넓어지고 커짐에 따라 웹페이지의 성능에러를 해결하기 위해 서버에서 페이지를 렌더링해 제공하는 방식이 점점 떠오르고 있다.

서버 사이드 렌더링의 장점

  • 최조 페이지 진입이 비교적 빠르다.

    • 일반적으로 서버에서 HTTP 요청을 수행하는 것이 더 빠르고 HTML을 그리는 작업도 서버에서 해당 HTML을 문자열로 미리 그려서 내려주어 클라이언트에서 기존 HTML에 삽입하는 것보다 더 빠르다.

  • 검색 엔진과 sns공유 등 메타데이터 제공이 쉽다.

    • 검색 엔진 로봇이 페이지에 진입해 정적 데이터를 불러가기 때문에 싱글페이지 애플리케이션에서는 검색엔진 최적화를 해주기 어렵다. 반면 서버 사이드 렌더링은 최초의 렌더링 작업이 서버에서 일어난다. 따라서 검색 엔진에 제공할 정보를 서버에서 가공해서 HTML 응답으로 제공할 수 있기 때문에 검색 엔진 최적화에 대응하기가 매우 용이하다.

  • 누적 레이아웃 이동이 적다.

    • '누적 레이아웃 이동' 이란 사용자에게 페이지를 보여준 뒤에 뒤늦게 html이 추가되어 화면이 덜컥거리는 것과 같은 부정적인 사용자 경험을 말한다.
      서버사이드 렌더링에서는 모든 요청이 완료된 이후에 완성된 페이지를 제공하기 때문에 이런 문제에서 비교적 자유롭다.

  • 사용자의 디바이스 성능에 비교적 자유롭다.

    • SPA 의 경우 렌더링을 유저가 전적으로 부담해야하지만 SSR의 경우 서버에서 렌더링을 함께 부담하기 때문에 디바이스 성능으로 부터 조금 더 자유로워질 수 있다.

  • 보안에 좀 더 안전하다.

    • 서버 사이드 렌더링의 경우 인증 혹은 민감한 작업을 서버에서 수행하고 결과만 브라우저에 제공해 보안위협을 피할 수 있다.

서버 사이드 렌더링의 단점

  • 소스코드를 작성할 때 항상 서버를 고려해야 한다.

    • 브라우저 전역객체인 window 또는 sessionStorage를 사용한다면 'window is not defined' 라는 에러를 마주하게 된다.

  • 적절한 서버가 구축돼 있어야 한다.

    • 사용자의 요청을 받아 렌더링을 수행할 서버가 필요하다.

  • 서비스 지연에 따른 문제

    • SPA 에서 무언가 느린 작업이 있다고 할 때 최초에 '로딩 중' 과 같은 화면을 보여줄 수 있으나 SSR 에서 지연이 일어날 경우 사용자에게 어떠한 것도 렌더링 할 수 없기 때문에 더 안 좋은 사용자 경험을 제공할 수도 있다.

4.1.3 SPA 와 SSR을 모두 알아야 하는 이유

서버 사이드 렌더링 역시 만등이 아니다.

웹페이지에서 사용자에게 제공하고 싶은 내용이 무엇인지, 또 어떤 우선순위에 따라 페이지의 내용을 보여줄지를 잘 설계하는 것이 중요하다. 웹페이지의 설계와 목적, 그리고 우선순위에 따라 싱글 페이지 애플리케이션이 더 효율적일 수도 있다

싱글 페이지 애플리케이션과 서버 사이드 렌더링 애플리케이션

  • 가장 뛰어난 싱글 페이지 애플리케이션은 가장 뛰어난 멀티 페이지 애플리케이션보다 낫다.

  • 평균적인 싱글 페이지 애플리케이션은 평균적인 멀티 페이지 애플리케이션보다 느리다.

4.2 서버 사이드 렌더링을 위한 리액트 API 살펴보기

리액트는 프런트엔드 라이브러리로 브라우저 자바스크립트 환경에서 렌더링할 수 있는 방법을 제공하지만 서버에서 렌더링 하는 API 또한 제공한다. 해당 API는 당연히 Node.js와 같은 서버 환경에서만 실행할 수 있으며 window 환경에서 실행 시 에러가 발생할 수 있다.
API를 확인하기 위해서는 react-dom/server.js를 확인하면 된다.

4.2.1 renderToString

인수로 넘겨받은 리액트 컴포넌트를 렌더링해 HTML 문자열로 반환하는 함수다.
서버 사이드 렌더링을 구현하는데 가장 기초적인 API다.

const result = ReactDOMServer.renderToString(
 React.createElement('div', { id: 'root' }, <SampleComponent />),
)

위의 예시와 같이 코드를 작성하면 된다.

useEffect와 같은 훅과 handleClick과 같은 이벤트 핸들러는 결과물에 포함되지 않는데 이는 renderToString 이 인수로 주어진 리액트 컴포넌트를 기준으로 빠르게 브라우저가 렌더링할 수 있는 HTML을 제공하는 데 목적이 있는 함수이기 때문이다.

4.2.2 renderToStaticMarkup

renderToString과 매우 유사한 함수다.
차이점은 앞서 루트 요소에 추가한 data-reactroot와 같은 리액트에서만 사용하는 추가적인 DOM 속성을 만들지 않는다는 점이다.
해당 함수를 통해 렌더링을 수행하면 리액트에서 제공하는 useEffect와 같은 브라우저 API를 절대로 실행할 수 없다.

4.2.3 renderToNodeStream

renderToString과 매우 유사한 함수지만 두 가지 차이점이 있다.

  • renderToNodeStream은 브라우저에서 사용하는 것이 완전히 불가능하다.

    • renderToNodeStream이 완전히 Node.js 환경에 의존하고 있기 때문이다.

  • renderToString 은 결과물이 string인 문자열이지만, renderToNodeStream은 결과물이 Node.js의 ReadableStream이다.

    • ReadableStream은 utf-8 로 인코딩된 바이트 스트림으로 Node.js 나 Deno, Bun같은 서버환경에서만 사용할 수 있다.

renderToNodeStream은 왜 필요할까??
스트림은 큰 데이터를 다룰 때 데이터를 청크(chunk, 작은 단위)로 분할해 조금씩 가져오는 방식을 의미한다.
renderToString이 생성하는 HTML 결과물의 크기가 작다면 상관이 없지만 크기가 크다면 문자열을 한 번에 메모리에 올려두고 응답을 수행해야 해서 Node.js가 실행되는 서버에 큰 부담이 될 수 있기 때문에 renderToNodeStream으로 렌더링해서 분리해 렌더링 시킬 수 있다.

대부분의 널리 알려진 리액트 서버 사이드 렌더링 프레임워크는 모두 renderToString 대신 renderToNodeStream을 채택하고 있다

4.2.4 renderToStaticNodeStream

renderToNodeStream과 제공하는 결과물은 동일하나, renderToStaticMarkup과 마찬가지로 리액트 자바스크립트에 필요한 리액트 속성이 제공되지 않는다.
hydrate를 할 필요가 없는 순수 HTML 결과물이 필요할 때 사용하는 메서드다.

4.2.5 hydrate

hydrate 함수는 renderToString과 renderToNodeStream으로 생성된 HTML 콘텐츠에 자바스크립트 핸들러나 이벤트를 붙이는 역할을 한다.

render를 사용했을 때

import * as ReactDOM from 'react-dom'
import App from './App'

const rootElement = document.getElementById('root')

ReactDOM.render(<App />, rootElement)

hydrate를 사용했을 때

import * as ReactDOM from 'react-dom'
import App from './App'

// containerId를 가리키는 element는 서버에서 렌더링된 HTML의 특정 위치를 의미한다.
const element = document.getElementById(containerId)

// 해당 element를 기준으로 리액트 이벤트 핸들러를 붙인다.
ReactDOM.hydrate(<App />, element)

둘의 차이점은 hydrate는 기본적으로 이미 렌더링된 HTML이 있다는 가정하에 작업이 수행되고, 이 렌더링된 HTML을 기준으로 이벤트를 붙이는 작업만 실행한다는 것이다.

Share article

석우의 개발블로그