김종이
Study

04. 서버 사이드 렌더링

SPA vs SSR

  • 싱글 페이지 애플리케이션

    • 최초의 첫 페이지에서 데이터를 모두 불러옴
    • 이후의 페이지 전환을 위한 모든 작업이 자바스크립트와 브라우저 history.pushState와 history.replaceState로 이루어짐
    • 사이트 렌더링에 필요한 body 내부의 내용을 모두 자바스크립트 코드로 삽입한 이후에 렌더링
    • 이러한 작동 방식은 최초에 로딩해야 할 js 리소스가 커지는 단점이 있지만 한번 로딩된 이후에는 서버를 거쳐 리소스를 받아올 일이 적어지기 때문에 사용자에게 훌륭한 UIUX를 제공
  • 서버 사이드 렌더링 방식

    • 렌더링에 필요한 작업을 서버에서 수행
    • 사용자 기기의 성능 영향을 받는 클라이언트 렌더링에 비해서, 렌더링을 서버에서 수행하기 때문에 비교적 안정적
    • FCP(First Contentful Paint)가 더 빨라지는 것이 장점
    • 검색 엔진과 SNS 공유 등 메타데이터 제공 용이
    • CLS 누적 레이아웃 이동을 줄일 수 있음
    • 사용자의 디바이스 성능에 비교적 자유로움
    • 보안에 좀 더 안전
    • 서버에서 렌더링 작업이 끝나기까지는 정보 제공이 불가능, 때문에 더 안 좋은 사용자 경험을 제공할 수 있다는 위험 존재

SSR을 위한 리액트 API

리액트는 리액트 애플리케이션을 서버에서 렌더링할 수 있는 API를 제공한다. 이 API는 Node.js와 같은 서버 환경에서만 실행할 수 있다.


renderToString
function App(){
	return (
      <>
      	<div>Hello World</div>
      </>
    )
}
 
const result = ReactDOMServer.renderToString(
	React.createElement('div', {id: 'root'}, <App />),
)
 
// result의 반환값
<div id="root" data-reactroot=""> // data-reactroot는 hydrate 함수에서 루트를 식별하는 기준
  <div>Hello World</div>
</div>
  • renderToString은 인수로 주어진 리액트 컴포넌트를 빠르게 브라우저가 렌더링할 수 있는 HTML을 제공하는 데 목적이 있다.
  • 완성된 HTML을 서버에서 서빙하기 때문에 초기 렌더링에서 뛰어난 성능을 보임
  • 검색 엔진이나 SNS 공유를 위한 메타 정보도 renderToString에서 미리 준비한 채로 제공
renderToStaticMarkup
  • renderToString과 유의미한 차이점은 루트 요소에 추가한 data-reactroot와 같은 추가적인 DOM 속성을 만들지 않는다는 점
  • 때문에 HTML 크기를 아주 조금이라도 줄일 수 있다.
renderToNodeStream
  • Node.js의 ReadableStream을 반환
    • ReadableStream은 utf-8로 인코딩된 바이트 스트림, Node.js 환경에서만 사용 가능
    • 스트림은 큰 데이터를 다룰 때 데이터를 청크로 분할해 조금씩 가져오는 방식을 의미
    • 대부분의 리액트 서버 사이드 렌더링 프레임워크는 renderToString이 아닌 renderToNodeStream를 채택
renderToStaticNodeStream
  • hydrate를 할 필요 없는 순수 HTML 결과물이 필요할 때 사용하는 메서드
hydrate
  • hydrate 함수는 renderToString이나 renderToNodeStream으로 생성된 HTML 콘텐츠에 자바스크립트 핸들러나 이벤트를 붙이는 역할
  • 정적으로 생성된 HTML에 이벤트와 핸들러를 붙여 결과물을 만듦
  • render 함수와의 차이점은 hydrate는 기본적으로 이미 렌더링 된 HTML이 있다는 가정하에 작업을 수행하고 이 HTML을 기준으로 이벤트를 붙이는 작업만 실행

Next.js

  • Next.js의 장점을 살리기 위해서 애플리케이션을 처음부터 서버에서 다시 불러와야 하는 드문 케이스 외에는 내부 페이지 이동시에
    • <a> 대신 <Link>를 사용한다.
    • window.location.push 대신 router.push를 사용한다.
  • next/link로 이동하는 경우는 SSR이 아닌 클라이언트 라우팅/렌더링 방식으로 작동하기 때문

상태 관리는 왜 필요한가?

React에서의 상태관리는 Context API를 떠올리기 쉬우나 Context API는 상태관리가 아니라 상태 주입을 도와주는 역할이다. 리액트의 상태 관리는 단방향으로 데이터 흐름을 변경하는 Flux 패턴의 리덕스를 시초로 발전되었다.


  • 웹에서 상태로 분류될 수 있는 것

    • UI : 다크/라이트 모드, 라디오 input, 알림창의 노출 여부
    • URL : 브라우저에서 관리되고 있는 상태값
    • 폼 : loading, submit, disabled, vaildation 등
    • 서버에서 가져온 값 : API 요청 등
  • 전역 상태 관리 라이브러리로 해결 된 것

    • prop 드릴링 문제를 해결
    • Context API와는 다르게 렌더링을 막아주는 기능 존재

05. 리액트와 상태 관리 라이브러리

상태 관리 라이브러리

  • Recoil

    • Recoil의 상태값은 RecoilRoot로 생성된 Context의 스토어에 저장
    • 상태값에 접근할 수 있는 함수로 상태값을 변경하고 접근할 수 있다
    • 값의 변경이 발생하면 하위 컴포넌트에 전파
  • Jotai

    • 작은 단위의 상태를 위로 전파할 수 있는 구조
    • 리액트 Context의 문제점인 불필요한 리렌더링을 해결하는 구조
    • 객체의 참조를 WeakMap에 보관해 해당 객체 자체가 변경되지 않는 한 별도의 키가 없이도 객체의 참조를 통해 값을 관리할 수 있다.
  • Zustand

    • 하나의 스토어를 중앙 집중형으로 활용해 상태를 관리
    • 보일러 플레이트 코드가 적다.