방구석에 놔둔 개발 노트

1년차 웹 / 앱 프론트엔드 엔지니어의 좌충우돌 얼렁뚱땅 앞뒤짱구 생존기

2024-07-01: hydration과 Waterfall

💭 서문

Next.js를 공부하면서 초반에 가장 많이 본 에러 중 하나가 바로 Hydration Error이다. 클라이언트 컴포넌트에 ‘use client’를 입력하지 않아서 종종 이런 에러들을 보곤 했는데, 돌이켜보니 이런 에러들을 해결하기에만 급급했지 대체 hydration이 뭐고, 또 이런 과정 속에서 종종 접했던 서버 컴포넌트와 연관된 waterfall 현상에 대해 무엇인지를 알아본 적은 없던 것 같다.

그래서 이 참에 이에 관련한 내용을 좀 정리하는 시간을 가져보고자 한다.

🚰 hydration(수분 공급)이란?

hydration이라는 단어에는 네이버 사전에 검색해보면 의학 용어로 수분 공급[보급]이라고 나타내고 있다.

수분공급[보급] 水分供給[補給]

  • 의학 hydration

[출저] 이우주 의학사전

그런데 이런 물과 전혀 관련 없는 코드에서 hydration이라니, 왜 이런 단어를 쓰는 걸까?

React의 아래 문서 내용을 참고해보자.

hydrateRoot – React

여기서 hydrateRoot API는 다음과 같이 설명되어 있다.

hydrateRootreact-dom/server를 통해 사전에 만들어진 HTML로 그려진 브라우저 DOM 노드 내부에 React 컴포넌트를 렌더링합니다.

사실 잘 와닿지 않는다. 아래를 좀 더 들여다보자.

서버 환경에서 React로 앞서 만들어진 HTML에 후에 만들어진 React를 hydrateRoot를 호출해 “붙입니다”.

import { hydrateRoot } from 'react-dom/client';

hydrateRoot(document.getElementById('root'), <App />);

즉, 정리하자면 이 hydration라는 친구는 서버 환경에서 만들어진 HTML, 즉 서버에서 렌더링된 HTML을 React, 더 나아가 JavaScript와 연결시켜주는 역할을 해준다.

근데 왜 이런 방식을 사용하게 된 걸까?

CSR을 사용하고 있다면 우리는 createRoot를 통해서 클라이언트에서 HTML과 JavaScript 모두를 받아오게 해준다.

그러나 SSR 방식으로 접근하는 사람들에게는 서버로부터 Pre-rendering 상태로서 HTML 문서를 먼저 그려낸 뒤, 아무런 동작하지 않는 상태에서 JavaScript를 빠르게 주유, 급유해 Rendering함으로서 HTML 파일에 JavaScript를 붙여주게 한다.

이렇게 hydration은 아무런 동작 없는 HTML 문서에 JavaScript의 이벤트나 동작 등을 보충한다는 의미로서 수분 공급이라고 불리는 듯 하다.

공부하기 이전에 내가 착각했던 부분이 하나 있었는데, 이 hydration은 Next.js가 아니라 본질적으로 React와 연관되어있다.

클라이언트 사이드에서 만들었을 땐 ReactDOM의 createRoot를 활용하지만, 서버 사이드에서 만들었을 땐 ReactDOMServer의 hydrateRoot를 활용하여 만들게 된다.

스스로에게 되뇌이는 거지만 Next.js에서 파생된 것이라고 생각하지 말자.

🌊 Waterfall 이란?

Waterfall, 우리말로 폭포라고 불리는 이 단어에 대해서 Next.js나 React로 프로젝트를 만든 사람이라면 한번씩은 들어본 적이 있을 것이다.

서버 컴포넌트가 등장하면서 더욱 더 대두되는 이 Waterfall이 무엇이고, 왜 생겨나게 됐는지 이번 글에서 좀 짚어보고자 한다.

기존 React에서는 클라이언트에서 렌더링이 일어난 이후에 useEffect 등을 통해서 외부 API와의 데이터 페칭 등을 한 후 이를 반영하는 식으로 형태가 자리잡히게 됐다.

즉, 렌더링과 데이터 로딩이 한 번에 진행되는 형태가 아니라 렌더링이 우선적으로 일어나고 비동기적으로 그 다음 데이터 로딩이 받아와지는 형태이다.

이러한 방식에서 부모와 자식 컴포넌트가 있고 각각 이 둘의 컴포넌트가 useEffect 등으로 외부 API와 데이터 페칭을 한다고 가정해보자.

function Course() {
  return (
    <CourseWrapper>
      <CourseList />
      <Testimonials />   
    </CourseWrapper>
  )
}

위의 내용에서 CourseWrapper 컴포넌트와 CourseList 컴포넌트가 둘 다 useEffect를 사용한다고 생각하자.

  1. Course 컴포넌트가 불러와지면, 최상위 컴포넌트인 CourseWrapper의 렌더링이 진행될 것이다.
  2. 컴포넌트 UI가 그려지면 useEffect를 통해 API 호출을 받아오고 렌더링을 완료할 것이다.
  3. CourseWrapper의 렌더링이 끝나면 이제 자식 컴포넌트인 CourseList와 Testimonials의 렌더링이 진행된다.
  4. 그럼 이제 CourseList의 컴포넌트 UI가 그려지고, useEffect통해 API 호출을 받아오고 렌더링을 완료한다.
  5. 이제 화면에 Course의 모든 컴포넌트가 보여진다.

이런 식으로 하나의 렌더링이 끝나고, 다음 렌더링이 일어나듯 순차적으로 데이터 요청을 보내게 되는 현상을 ‘Waterfall 현상’이라고 부른다.

React를 쓰는 사람들은 이 문제에 대한 고민을 안고 있는데, 이러한 Waterfall 현상으로 불필요한 대기 시간이 늘어가게 되어서였다.

이런 Waterfall 문제를 해결하기 위해 React에서는 두 가지 카드를 꺼내들었다.

하나는 비동기 데이터를 전부 받아올 때까지 fallback로 로딩 화면만 보여주도록 하는 Suspense이고, 다른 하나는 필요한 데이터를 서버에서 가져오고, 클라이언트에 바로 렌더링된 결과를 보여주도록 하는 서버 컴포넌트이다.

이 두 가지 방식을 이용해 기존의 Waterfall로 인해서 발생하는 문제들을 풀어낼 수 있다. (Suspense와 서버 컴포넌트에 관한 이야기는 별도의 블로깅에서 다뤄보도록 하겠다.)

📌 정리

긴 내용이긴 했지만 결론적으로, hydration과 waterfall에 대해서 요약하자면 다음과 같다.

Hydration은 서버에서 렌더링된 HTML에 JavaScript의 이벤트나 동작들을 연결해주는 것을 가리킨다.

Waterfall은 순차적으로 일어나는 데이터 요청 등으로 인해서 로딩이 지연되는 현상을 가리킨다.

앞으로 개발할 때 이 부분을 까먹지 말고, 비슷한 문제가 있을 시 어떤 식으로 문제 해결을 접근해야할 지 생각하면서 코드를 짜도록 하자.

🔖 참고 자료

웹에서 렌더링  |  Articles  |  web.dev

React의 Hydration에 대하여

Hydration (web development))

React 서버 컴포넌트를 사용해야 하는 이유와 방법

React Server Components(RSC) 차근차근 이해하기