🚨 에러 발생
Unhandled Runtime Error
Error: Text content does not match server-rendered HTML.
See more info here: <https://nextjs.org/docs/messages/react-hydration-error>
🧨 문제 상황
공식 문서: Text content does not match server-rendered HTML
더보기
- Incorrect nesting of HTML tags
- <p> nested in another <p> tag
- <div> nested in a <p> tag
- <ul> or <ol> nested in a <p> tag
- Interactive Content cannot be nested (<a> nested in a <a> tag, <button> nested in a <button> tag, etc.)
- Using checks like typeof window !== 'undefined' in your rendering logic
- Using browser-only APIs like window or localStorage in your rendering logic
- Using time-dependent APIs such as the Date() constructor in your rendering logic
- Browser extensions modifying the HTML
- Incorrectly configured CSS-in-JS libraries
- Ensure your code is following our official examples
- Incorrectly configured Edge/CDN that attempts to modify the html response, such as Cloudflare Auto Minify
이 문제는 공식 문서에도 나와있는 데 내 경우는 3번 문제에 속한다
- 서버에서 렌더링된 HTML과 클라이언트에서 렌더링된 HTML이 일치하지 않을 때 발생하는 에러이다
- 나 같은 경우는 Next.js의 서버 사이드 렌더링(SSR)과 클라이언트 측의 localStorage 사이에서 발생하는 차이 때문이었다
'use client';
import { useRouter } from 'next/navigation';
import useTicketStore from '@/store/ticketStore';
import { formatDateWithDayAndTime } from '@/utils/dayjsPlugin';
import { LogoImage, LogoText } from '@/public/icons';
import GenerateQR from '@/components/atoms/qrcode/GenerateQR';
import CloseButton from '@/components/atoms/button/CloseButton';
import { useEffect, useState } from 'react';
import LoadingSpinner from '@/components/atoms/feedback/LoadingSpinner';
const Page = () => {
const router = useRouter();
const { ticketDetail } = useTicketStore();
const [isHydrated, setIsHydrated] = useState(false);
useEffect(() => {
setIsHydrated(true);
}, []);
const handleClose = () => {
router.back();
};
return (
<div className="flex flex-col items-center justify-between h-screen py-[8vh]">
<div className="flex flex-col items-center justify-center flex-grow py-4 min-h-[60vh]">
{isHydrated ? (
<>
<div className="flex flex-col items-center mb-[8vh]">
<h1 className="text-xl font-medium">
{ticketDetail?.concertTitle}
</h1>
<p className="mt-2 text-lg text-gray-500">
{formatDateWithDayAndTime(ticketDetail?.startAt || '')}
</p>
</div>
<GenerateQR value={ticketDetail?.ticketUuid || 'no data'} />
</>
) : (
<LoadingSpinner />
)}
</div>
<div className="flex flex-col items-center justify-between flex-grow">
<div className="flex flex-col items-center">
<LogoImage className="mb-3 w-11 h-11" />
<LogoText />
</div>
<CloseButton onClick={handleClose} hasBorder />
</div>
</div>
);
};
export default Page;
- 위 코드에서 ticketDetail을 localStorage에서 가져오고 있는데, localStorage는 클라이언트에서만 사용할 수 있기 때문에 서버에서 렌더링될 때 localStorage를 참조할 수 없어서 Hydration Error가 발생한다
🚔 해결 방법
서버 렌더링과 클라이언트 렌더링을 일치시킨다
클라이언트에서 데이터가 로드되기 전까지 loading 상태를 표시하고 useEffect로 클라이언트에서만 ticketStore에 접근하도록 한다
Hydration 처리
- useEffect를 사용하여 클라이언트가 서버에서 Hydration된 후에만 ticektStore에서 데이터를 가져오도록 한다
- isHydrated라는 Zustand의 상태를 사용할 수 있는 여부를 저장하는 변수를 만든다
- true면 Hydration 되었으니 ticketStore의 데이터를 읽어서 화면에 보여준다
- false면 Hydration 되지 않았으니 로딩 스피너를 띄운다
반응형