iBetter Books
수정

Ch 04. 하이드레이션 이해와 mismatch 해결

서버에서 완성된 HTML이 브라우저에 도착하면 Vue는 그 HTML 위에 반응성을 "붙이는" 작업을 합니다. 이 과정을 하이드레이션(Hydration)이라고 합니다. DOM을 새로 만들지 않고 기존 HTML 구조를 재활용하기 때문에 빠르게 동작합니다.

문제는 서버가 만든 HTML과 클라이언트가 그려야 할 HTML이 다를 때 발생합니다. Vue는 이 불일치를 감지하면 브라우저 콘솔에 경고를 남기고, 경우에 따라 화면이 깜빡이거나 레이아웃이 틀어지기도 합니다.

mismatch가 발생하는 주요 원인

첫 번째, 비결정적 값을 렌더링에 사용할 때.

<!-- 잘못된 예 -->
<template>
  <p>방문 ID: {{ Math.random() }}</p>
  <p>접속 시각: {{ new Date().toLocaleTimeString() }}</p>
</template>

서버가 실행한 Math.random()과 클라이언트가 실행한 Math.random()은 값이 다릅니다. Vue가 두 값을 비교하면 mismatch가 납니다.

두 번째, 브라우저 전용 코드가 <script setup> 최상위에서 실행될 때.

windowlocalStorage에 접근하는 코드가 onMounted 밖에 있으면 서버에서도 실행을 시도합니다. 서버에는 해당 객체가 없으므로 오류가 발생하거나 undefined가 반환됩니다.

세 번째, 서버와 클라이언트의 데이터 불일치.

서버에서 가져온 데이터와 클라이언트 첫 렌더링에 사용된 데이터가 다른 경우입니다. useFetchuseAsyncData를 사용하면 서버 응답이 클라이언트로 전달되므로 이 문제를 예방할 수 있습니다.

해결 방법

ClientOnly로 문제 구간을 격리합니다.

비결정적 값이나 브라우저 API를 사용하는 부분을 <ClientOnly>로 감싸면 서버 렌더링 대상에서 제외됩니다.

<template>
  <ClientOnly>
    <p>현재 시각: {{ currentTime }}</p>
  </ClientOnly>
</template>

useId()로 고유 ID를 생성합니다.

폼 요소의 idaria- 속성에 임의 값을 쓰면 mismatch가 납니다. Nuxt 3.10 이상에서는 useId()를 사용하면 서버와 클라이언트가 동일한 ID를 생성합니다.

const inputId = useId()  // 서버와 클라이언트에서 동일한 값 반환

useCookie로 일관된 초기값을 사용합니다.

사용자 설정(예: 다크 모드, 언어)을 쿠키에 저장하고 useCookie로 읽으면 서버와 클라이언트 모두 같은 값으로 초기 렌더링을 합니다.

const theme = useCookie('theme', { default: () => 'light' })

콘솔 경고 해석하기

브라우저 개발자 도구 콘솔에서 다음과 같은 경고를 만나면 mismatch입니다.

[Vue warn]: Hydration text content mismatch on <p>
  - Server rendered: "0.73829..."
  - Client rendered: "0.41029..."

Server renderedClient rendered 값을 비교하면 어떤 데이터가 문제인지 바로 파악할 수 있습니다. 해당 컴포넌트에서 비결정적 값이나 브라우저 전용 코드를 찾아 수정하면 됩니다.