본문 바로가기

프론트엔드

Nextjs의 Hydration

클라이언트 컴포넌트는 CSR로 이루어지지 않는다.

next.js 에서 컴포넌트가 client component로 작동한다고 해도, 첫 렌더링은 서버에서 이루어진다. (SSR이 아닌 CSR로 작동하는 것이 아님)

컴포넌트 최상단에 'use client'를 작성하면 해당 컴포넌트에서는 hydration이 일어난다.

 

CSR방식은 리액트를 사용해봤다면 흔히 알 듯이, 초기 페이지를 로드할 때 하나의 빈 html파일과 js번들 파일을 서버로부터 전달받아

index.html파일의 body태그 내부에는 root 역할을 하는 div 태그만이 존재하며, 이후 페이지에 포함되어야 할 컴포넌트들은 Critical rendering path 과정에서 js파일의 해석과 함께 실제 렌더 트리에 추가된다.

 

React 프로젝트 내에 사용되는 모듈(의존성, 컴포넌트 등등)들은 빌드 과정에서 하나의 거대한 js파일로 번들링되기 때문에, 

큰 용량으로 인해 초기 페이지 로딩에 있어 불리하다.

 

반면, SSR 방식은 서버에서 렌더링을 마쳤으므로 DOM 조작을 위한 추가적인 js파일이 필요하지 않을 뿐더라 컴포넌트별로 완성된 html 파일로 제공되어 SEO 최적화에도 유리하다.

 

SSR방식이 완성된 정적 html만을 제공하면, 브라우저에서 발생하는 interactive한 event들을 어떻게 처리할 지에 대한 해결 방식이 바로 hydration이다.

 

Hydration은 SSR로 제공된 컴포넌트에 interactive한 동작을 추가하기 위한 이벤트 리스너를 추가하는 과정이며, 이 과정을 위해 또 한번의 렌더링이 이루어진다.

즉, Hydration은 서버렌더링된 정적 페이지와 js파일을 클라이언트로 보낸 뒤 클라이언트에서 html코드와 js코드를 서로 매칭시키는 과정을 말한다. (여기서 이루어지는 두번째 렌더링에서는 paint과정을 다시 거치지는 않는다.)

-> use client 선언이 있는 파일은 hydration을 적용한다는 의미가 된다.

 

즉, use client를 선언한 클라이언트 컴포넌트도 여전히 SSR로 동작하며, 서버에서 프리렌더링이 이루어진다. (클라이언트 컴포넌트라고 해서 CSR이 아님)

-> use client의 역할은 해당 컴포넌트에서 동적인 이벤트가 이루어질 수 있으며, 이벤트를 처리하기 위한 추가적인 js 파일과 hydration

이 필요하다고 명시해두는 지시어임

 

 

 

Hydration 에러의 원인

Hydration 에러는 리액트 컴포넌트가 서버에서 렌더링된 초기 html 구조와 클라이언트에서 렌더링된 (js가 실행된 후 생성된) DOM 구조가 일치하지 않을 때 발생

1. 서버와 클라이언트 간 렌더링 결과 불일치: SSR은 초기 로딩 속도를 개선하기 위해 서버에서 미리 HTML을 생성하여 클라이언트로 전송하고, 이후 React는 이 html을 기반으로 클라이언트에서 js를 실행하여 DOM을 생성하고 이벤트 리스너를 부착하는 Hydration과정을 거친다. 이 때, 서버와 클라이언트의 렌더링 결과가 다르면 Hydration 에러가 발생함

2. 동적인 데이터 처리 방식의 차이: 서버에서 알 수 없는 브라우저의 정보(쿠키, 로컬스토리지, window 객체 등)를 클라이언트에서는 접근 할 수 있고, 이로 인해 서버와 클라이언트가 동일한 데이터에 대해 다른 결과를 생성할 수 있음

3. 렌더링 시점의 차이: 서버에서는 컴포넌트가 처음 렌더링될 때의 상태를 기준으로 html을 생성하지만, 클라이언트에서는 hydration과정에서 상태가 변경될 수 있음. 

 

 

Hydration 에러 해결 방법

1. 데이터를 불러온 후 상태를 업데이트할 경우: useEffect를 활용한 클라이언트 전용 렌더링 (로딩 상태 표시)

- 서버에서 렌더링된 html과 클라이언트에서 렌더링된 html이 다를 경우, useEffect 를 사용해 클라이언트에서 동작하도록 설정할 수 있다. (서버에서는 빈 값을 렌더링하고, 클라이언트에서는 데이터를 업데이트함

import { useEffect, useState } from "react";

export default function Home() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch("/api/data")
      .then((res) => res.json())
      .then((data) => setData(data));
  }, []);

  return <div>{data ? data : "Loading..."}</div>;
}

-> 서버와 클라이언트 간의 html 불일치를 방지하며, 클라이언트에서만 데이터를 처리한다.

-> 로딩 상태를 표시하여 사용자에게 피드백을 제공할 수 있어서 사용자 경험을 개선할 수 있다.

2. Dynamic Import로 SSR 비활성화

Dynamic Import는 특정 컴포넌트나 모듈을 필요할 때 동적으로 불러오는 기능으로, Next.js에서 next/dynamic 을 사용하여 컴포넌트를 동적으로 로드하면, 해당 컴포넌트는 서버 사이드 렌더링 과정에서 제외되고, 클라이언트 측에서만 렌더링된다.

import dynamic from "next/dynamic";

const NoSSRComponent = dynamic(() => import("./NoSSRComponent"), { ssr: false });

export default function Page() {
  return <NoSSRComponent />;
}

-> 서버사이드 렌더링 우회: Dynamic Import로 불러온 컴포넌트는 서버에서 html에 포함되지 않으므로 서버와 클라이언트 간 초기 html 구조 불일치 문제가 발생하지 않음

-> 클라이언트 환경 처리: 클라이언트에서만 렌더링되므로, 브라우저 API나 클라이언트 전용 데이터에 안전하게 접근할 수 있으므로 서버 환경에 의존적인 코드를 분리할 수 있음

-> 점진적인 Hydration: 필요한 컴포넌트만 클라이언트에서 렌더링하므로 초기 로딩 속도를 유지하면서 Hydration 에러 발생 가능성을 줄일 수 있음

 

3. suppressHydarationWarning 사용

suppressHydrationWarning 속성을 사용해 서버와 클라이언트 간의 불일치를 무시할 수 있고, 특정 요소에만 적용할 수 있다.

단, 최후의 수단으로 사용해야함

<div suppressHydrationWarning>{dynamicContent}</div>

 

 

 

참고: https://velog.io/@yeshyungseok/hydration과-use-client