Next js hydration 오류

윤병현·2024년 6월 15일
1

FeedB

목록 보기
3/10
post-thumbnail

Hydration이란?

Hydration은 서버에서 렌더링된 HTML을 클라이언트 쪽에서 다시 React 컴포넌트로 변환하는 과정을 말합니다.
이는 서버에서 렌더링된 정적 HTML이 클라이언트 측에서 React의 동적인 기능과 합쳐져 완전히 인터랙티브한 애플리케이션이 되는 것을 의미합니다.


🚨 이슈 발생

헤더에 있는 로고를 누르게 되면 메인 페이지로 이동하게 되는데 어떤 페이지에서든 로고 버튼을 눌러 메인페이지로 이동을 하면 위와 같이 Hydration 오류가 나타납니다

만약 여기서 새로고침을 하게 되면 저 오류는 사라지게됩니다.

오류를 좀 더 확대해서 보겠습니다

Warning: In HTML, cannot be a child of . This will cause a hydration error.

위와 같은 오류가 뜹니다. 번역을 해보면

경고: HTML에서 <html>은 <body>의 자식이 될 수 없습니다. 이로 인해 수화 오류가 발생합니다.

이런 오류가 왜 일어나는지 정말 많이 고민을 해보았습니다. (사실 정답을 알려주고 있었음;;)


🤔 원인분석

이 문제를 해결하기 위해 여러가지 실험을 해보면서 알게된 사실이 하나가 있습니다. 이걸 설명하기 위해서는 일단 저희 프로젝트 폴더 구조를 먼저 아셔야합니다

폴더구조

📁 app
	└─ 📁 **main** `게시물 리스트 보여주는 페이지`
		 └─ 📘 layout.tsx		
		 └─ 📘 page.tsx	
	└─ 📘 layout.tsx		
	└─ 📘 page.tsx	

보시면 layout 파일이 두개가 있는데 하나는 저희 프로젝트에 전체에 적용되는 layout 파일이 있고 main 페이지에서만 적용되는 layout 파일 총 두개가 존재합니다.

Root layout 파일

import type { Metadata } from "next";
import "./_styles/globals.css";

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ko">
      <body>
        <div id="modal" />
        {children}
      </body>
    </html>
  );
}

프로젝트 Root에 만들어진 RootLayout 코드를 보시면 <div id="modal" /> 이 들어가 있습니다.

메인 페이지에서 새로고침후 HTML 구조를 개발자 도구를 사용해 확인해 보면 상단에 <div id="modal" /> 태그가 들어가 있는 걸 확인 할 수 있었습니다

저는 여태까지 layout 파일이 여러개가 존재를 하면 폴더안에 있는 layout 파일만 해당 페이지에 적용되는 줄 알았습니다.

그래서 공식문서를 찾아보니 폴더마다 만들어진 layout 파일만 해당 페이지에 적용이 되는 것이 아니고 중첩이 된다는 사실을 알게되었습니다.

이 사실을 알게되고 어떤게 문제인지 빠르게 파악했습니다.

원인

저희가 페이지마다 전부 layout 파일을 만들어서 사용하고 있는데 전부 Root layout 파일과 똑같이 <html> <body> 를 붙혀줬습니다.

import Header from "@/app/_components/Header/Header";
import type { Metadata } from "next";
import "@/app/_styles/globals.css";

export const metadata: Metadata = {
  title: "FeedB",
  description: "FeedBd에 오신 걸 환영합니다",
};

export default function MainPageLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ko">
      <body>
        <Header />
        {children}
      </body>
    </html>
  );
}
  

이렇게 되면 이미 루트에서 만들어진 <html> <body> 안에 다시 <html> <body> 만들어져 Hydration 오류가 나왔던 거였습니다.

새로고침했을 때 오류가 사라졌던 이유는 이때는 중첩된 레이아웃(root + main)로 재렌더링이 되는 게 아니라 main 페이지 레이아웃만 적용시켜 페이지를 새롭게 랜더링을 하게 되어서 오류가 안 나왔던 거 같습니다.

그 증거로 새로고침하면 <div id="modal" /> 는 렌더링이 되지가 않습니다.


🔆 해결방안

공식문서 설명 코드

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <section>{children}</section>
}

공식문서에도 중첩되는 Layout 코드에 <html> <body> 태그를 빼고 작성한 것을 확인하였습니다.

export const metadata: Metadata = {
  title: "FeedB",
  description: "FeedBd에 오신 걸 환영합니다",
};

export default function MainPageLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
		<>
     <Header />
     {children}
		</>
  );
}

그래서 저희도 기존에 있던 <html> <body> 를 지우고 React Fragment로 감싸주었습니다.

그랬더니 오류가 사라진 걸 확인할 수 있었습니다.

결론

  1. 페이지 이동시 상위 layout 파일 내용은 중첩이 된다.
  2. 페이지 새로고침시 Page에 적용된 layout 내용으로만 렌더링이 된다.(이땐 중첩 x)
profile
프론드엔드 개발자

0개의 댓글

관련 채용 정보