고도연
Study

정리하기 파일

04장. 서버 사이드 렌더링

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

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

싱글 페이지 애플리케이션(Single Page Application; SPA)이란?

SPA이란 렌더링과 라우팅에 필요한 대부분의 기능을 서버가 아닌 브라우저의 자바스크립트에 의존하는 방식을 의미한다. 페이지를 불러온 이후에는 서버에서 HTML을 내려받지 않고 하나의 페이지에서 모든 작업을 처리하므로 싱글 페이지 애플리케이션이라고 한다.

싱글 페이지 애플리케이션의 단점

최초에 로딩해야 할 자바스크립트 리소스가 커진다.

싱글 페이지 애플리케이션의 장점

한번 로딩된 이후에는 서버를 거쳐 필요한 리소스를 받아올 일이 적어지기 때문에 사용자에게 훌륭한 UI/UX를 제공한다.

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

과거 PHP나 JSP(JavaServer Pages)를 기반으로 대부분의 웹 애플리케이션이 만들어졌을 때는 거의 대부분의 렌더링이 서버 사이드에서 이뤄졌다. 프레임워크의 등장으로 등장한 것이 JAM(JavaScript, API, Markup)스택이다. 대부분의 작업을 자바스크립트에서 수행할 수 있었기 때문에 프런트엔드는 자바스크립트와 마크업(HTML, CSS)을 미리 빌드해 두고 정적으로 사용자에게 제공하면 이후 작동은 모두 사용자의 클라이언트에서 실행되므로 서버 확장성 문제에서 좀 더 자유로워질 수 있게 됐다.

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

서버 사이드 렌더링은 최초에 사용자에게 보여줄 페이지를 렌더링해 빠르게 사용자에게 화면을 제공하는 방식을 의미한다.

서버 사이드 렌더링의 장점

  1. 최초 페이지 진입이 비교적 빠르다.
  2. 검색 엔진과 SNS 공유 등 메타데이터 제공이 쉽다.
  3. 누적 레이아웃 이동이 적다.
  4. 사용자의 디바이스 성능에 비교적 자유롭다.
  5. 보안에 좀 더 안전하다.

서버 사이드 렌더링의 단점

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

    브라우저 전역 객체인 window 또는 sessionStorage와 같이 브라우저에만 있는 전역 객체의 접근을 최소화해야 하고, window 사용이 불가피하다면 해당 코드가 서버 사이드에서 실행되지 않도록 처리해야 한다. 외부에서 의존하고 있는 라이브러리도 마찬가지다.

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

  3. 서비스 지연에 따른 문제

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

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

웹페이지에서 사용자에게 제공하고 싶은 내용이 무엇인지, 어떤 우선순위에 따라 페이지의 내용을 보여줄지를 잘 설계하는 것이 중요하다.

현대의 서버 사이드 렌더링

요즘의 서버 사이드 렌더링은 최초 웹사이트 진입 시에는 서버 사이드 렌더링 방식으로 서버에서 완성된 HTML을 제공받고, 이후 라우팅에서는 서버에서 내려받은 자바스크립트를 바탕으로 마치 싱글 페이지 애플리케이션처럼 작동한다.

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

리액트는 브라우저 자바스크립트 환경에서 렌더링할 수 있는 방법을 제공하지만 동시에 리액트 애플리케이션을 서버에서 렌더링할 수 있는 API도 제공한다.

리액트레서 서버 사이드 렌더링을 실행할 때 사용되는 API를 확인해 보려면 리액트 저장소의 react-dom/server.js (opens in a new tab)를 확인하면 된다.

4.2.1 renderToString

const result = ReactDOMServer.renderToString(
	React.createElement('div', { id: 'root' }, <SampleComponent /> )
)
 
// result는 다음과 같은 문자열을 반환
<div id="root" data-reactroot="">
	<div>hello</div>
	<ul>
		<li>apple</li>
		<li>banana</li>
		<li>peach</li>
	</ul>
</div>
  • renderToString은 인수로 넘겨받은 리액트 컴포넌트를 렌더링해 HTML 문자열로 반환하는 함수다.
  • renderToString을 사용하면 클라이언트에서 실행되지 않고 먼저 완성된 HTML을 서버에서 제공할 수 있으므로 초기 렌더링에서 뛰어난 성능을 보일것이다.
  • 리액트의 루트 엘리먼트에 data-reactroot 속성이 있다.

4.2.2 renderToStaticMarkup

const result = ReactDOMServer.renderToStaticMarkup(
	React.createElement('div', { id: 'root' }, <SampleComponent /> )
)
 
// result는 다음과 같은 문자열을 반환
<div id="root">
	<div>hello</div>
	<ul>
		<li>apple</li>
		<li>banana</li>
		<li>peach</li>
	</ul>
</div>
  • 인수로 넘겨받은 리액트 컴포넌트를 렌더링해 HTML 문자열로 반환하는 함수다.
  • 루트 요소에 추가한 data-reactroot와 같은 리액트에서만 사용하는 추가적인 DOM 속성을 만들지 않는다.(HTML의 크기를 줄일 수 있음)
  • renderToStaticMarkup은 이벤트 리스너가 필요 없는 완전히 순수한 HTML을 만들 때만 사용된다.

4.2.3 renderToNodeStream

  • renderToSrgingrenderToStaticMarkup은 브라우저에서도 실행할 수는 있지만 renderToNodeStream은 브라우저에서 사용하는 것이 완전히 불가능하다.
  • renderToNodeStream은 완전히 Node.js 환경에 의존하고 있다.
  • renderToNodeStream의 결과물은 Node.js의 ReadaleStream이다. ReadaleStream은 utf-8로 인코딩된 바이트 스트림으로, Node.js 환경에서만 사용할 수 있다.
  • 스트리을 활용한다면 브라우저에 제공해야 할 큰 HTML을 작은 단위로 쪼개 연속적으로 작성함으로써 리액트 애플리케이션을 렌더링하는 Node.js 서버의 부담을 덜 수 있다.
  • 대부분의 리액트 서버 사이드 렌더링 프레임워크는 모두 renderToNodeStream 을 채택하고 있다.

4.2.4 renderToStaticNodeStream

  • renderToNodeStream의 결과물은 동일하나, renderToStaticMarkup 과 마찬가지로 리액트 자바스크립트에 필요한 리액트 속성이 제공되지 않는다.

4.2.5 hydrate

  • 생성한 HTML 콘텐츠에 자바스크립트 핸들러나 이벤트를 붙이는 역할

4.3 Next.js 톺아보기

a 태그로 이동

  • 네트워크에 hello라는 이름의 문서 요청
  • 이후에 webpack, framework, main, hello 등 페이지를 만드는 데 필요한 모든 리소스를 처음부터 다 가져옴
  • 서버에서 렌더링을 수행하고 클라이언트에서 hydrate하는 과정에서 한 번 더 실행

next/Link로 이동

  • hello.js만 존재, 나머지 요청은 없음
  • 서버 사이드 렌더링이 아닌, 클라이언트에서 필요한 JS만 불러온 뒤 라우팅하는 클라이언트 라우팅/렌더링 방식으로 작동

Next.js는 서버 사이드 렌더링의 장점과 싱글 페이지 애플리케이션의 장점을 모두 살리기 위해 이런 방식으로 작동

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

5.1 상태 관리는 왜 필요할까?

Flux pattern

웹앱이 복잡해지고 상태도 많아지며 어디서 어떤 일이 일어나서 상태가 변했는지 등을 추적하기 어려워졌다.

이는 Flux 패턴에서 단방향 데이터 흐름을 따르기 때문이다.

  • action: 상태에 변화가 필요할 때 발생하는 이벤트 혹은 객체
  • dispatcher: action을 발생시키는 함수
  • store: 애플리케이션의 상태를 저장하는 곳
  • view: 사용자에게 비춰지는 화면

Flux 패턴에서는 action이 발생하면 dispatcher가 action을 받아서 store에 전달한다. 그리고 store는 action에 따라 상태를 변경하고 변경된 상태를 view에 전달한다. view는 변경된 상태를 화면에 렌더링한다.

이렇듯 데이터나 상태, 액션 등을 단방향으로 전달하는 방식을 사용하여 Flux 패턴은 복잡한 데이터 흐름을 단순하게 만들어준다.

Redux

Redux는 Flux 패턴을 기반으로 만들어진 라이브러리이다.

장점

  • 상태를 더 직관적으로 관리할 수 있다.
  • 복잡한 상태 관리를 쉽게 할 수 있다.

단점

  • Redux를 사용하면서 불편한 점이 있다면, 액션 타입, 액션 생성 함수, 리듀서를 일일이 만들어야 한다는 점이다.

Context API

  • Context API는 리액트 v16.3에 새로 도입된 기능이다.
  • Context API를 사용하면 Context를 만들어서 Context 안에 Provider와 Consumer를 설정할 수 있다.

react-query, swr

  • react-query와 swr은 서버 상태를 관리하는 라이브러리이다.

5.2 리액트 훅으로 시작하는 상태관리

오랜 시간동안 리액트 애플리케이션의 상태 관리를 위해 리덕스에 의존했다. 그러나 현재는 새로운 Context API, useReducer, useState의 등장으로 컴포넌트에 결처셔 재사용하거나 컴포넌트 내부에 걸쳐서 상태를 관리할 수 있는 방법들이 점차 많이 등장하기 시작했고, 리덕스 외의 다른 라이브러를 선택하는 경우도 많아지고 있다.

Recoil

Recoil과 Jotai는 Context와 Provider, 훅을 기반으로 가능한 작은 상태를 효율적으로 관리하는 것에 초점을 맞추고 있다. 그리고 Zustand는 리덕스와 비슷하게 하나의 큰 스토어를 기반으로 상태를 관리하는 라이브러리이다. Recoil,Jotai와는 다르게 스토어의 상태가 변경되면 해당 상태를 구독하는 컴포넌트에 전파해 리렌더링을 알린다.

RecoilRoot는 크게 3가지의 단계로 나뉜다.

  • RecoilRoot의 AppContext에는 Recoil의 상태값들이 담긴다.
  • 스토어의 상태값에 접근 할 수 있는 함수들로 상태의 읽기나 쓰기를 할 수 있다.
  • 값의 변경이 있을 때 구독중인 모든 하위 컴포넌트에 값의 변화를 알린다.

atom은 상태를 나타내는 Recoil의 최소 단위이다.

Jotai

recoil과 비슷한 api 형태지만 recoil의 한계를 극복하기 위해 만들어졌다.

  • key를 사용하지 않는다.
  • atom 그 자체로 파생값을 만들 수 있다.

Zustand

Redux와 비슷한 api 형태를 가지고 있다.

  • 중앙 집중형 저장소 활용
  • 바로 custom hook 느낌으로 사용 가능
  • 보일러 플레이트 코드가 Flux, Redux 보다 적다.