Hydration
main
date
Jul 19, 2024
slug
hydration-mismatch
author
status
Public
tags
Development
summary
type
Post
thumbnail
category
Development
updatedAt
Oct 19, 2025 02:11 PM
문제 상황.. 쿼리 조회 시 화면이 2번 그려진다.

interface ResultPageProps { SNUM: number; } const ResultPage = ({ SNUM }: ResultPageProps) => { // 설문 정보 불러오기 const resultProjectQuery = useResultProjectQuery(SNUM); // 설문 진행 현황 불러오기 const resultProgressQuery = useProgressStatusQuery(SNUM); const [chartInfo, setChartInfo] = useState<ResultChartInfo[]>([]); const [selectedQuestionId, setSelectedQuestionId] = useState(''); useEffect(() => { setChartInfo(chartData); }, []); if (resultProjectQuery.isLoading && resultProgressQuery.isLoading) return null; const handleClickSelectQuestion = (questionId: string) => { setSelectedQuestionId(questionId); }; return ( <ResultLayout> <TitleSection> <ResultTitle surveyTitle={resultProjectQuery.data?.surveyTitle || ''} /> </TitleSection> <ContentContainer> <ListSection> <ResultQuestionList questionList={resultProjectQuery.data?.questionList || []} selectedQuestionId={selectedQuestionId} handleClickSelectQuestion={handleClickSelectQuestion} /> </ListSection> <ContentSection> <ResultStatus status={resultProgressQuery.data} /> <Divider /> {chartInfo.map((chartInfo) => ( <ResultChart key={chartInfo.questionId} chartInfo={chartInfo} /> ))} </ContentSection> </ContentContainer> </ResultLayout> ); }; export const getServerSideProps: GetServerSideProps = async (context) => { try { const { params } = context; const snum = params?.snum as string; const snumParseNumber = +snum; // 설문 정보 프리 페칭 await queryClient.prefetchQuery({ queryKey: [queryKey.result.projectInfo, snumParseNumber], queryFn: () => getResultQuestionDataApi(snumParseNumber), }); // 설문 진행 현황 프리 페칭 await queryClient.prefetchQuery({ queryKey: [queryKey.result.statusInfo, snumParseNumber], queryFn: () => getProgressStatusApi(snumParseNumber), }); return { props: { SNUM: +snum, }, }; } catch (e) { return { props: {}, }; } }; export default ResultPage;
props로 queryclient hydartion을 하면..??
export const getServerSideProps: GetServerSideProps = async (context) => { try { const { params } = context; const snum = params?.snum as string; const snumParseNumber = +snum; // 설문 정보 프리 페칭 await queryClient.prefetchQuery({ queryKey: [queryKey.result.projectInfo, snumParseNumber], queryFn: () => getResultQuestionDataApi(snumParseNumber), }); // 설문 진행 현황 프리 페칭 await queryClient.prefetchQuery({ queryKey: [queryKey.result.statusInfo, snumParseNumber], queryFn: () => getProgressStatusApi(snumParseNumber), }); return { props: { SNUM: +snum, dehydratedState: dehydrate(queryClient), // <-- 추가하면 }, }; } catch (e) { return { props: {}, }; } };

바보였다.. 넣어 놓은 줄 알았던 hydratedState가 빠져있었다.
// _app.tsx export const getServerSideProps: GetServerSideProps = async (ctx) => { return { props: { dehydratedState: dehydrate(queryClient), // 쿼리 클라이언트 누락.. ...(await serverSideTranslations(ctx.locale ?? "ko", ["en"])), }, }; };
Nextjs 에서 Hydrate는 어떻게 하는지, tanstack-query를 사용했을 때에는 어떤 점을 주의 해야하는지 체크해볼 필요가 있을 것 같다.
Hydrate는 Server Side에서 렌더링 된 페이지와 번들링 된 JS 파일을 클라이언트에게 보낸 후 클라이언트에서 HTML 코드와 JS 코드를 서로 매칭 시키는 과정을 의미한다.
React에서의 페이지 구성
React의 경우 JS 파일만을 사용하여 화면을 구성한다. 그렇기 때문에 실제 HTML 을 봤을 때 내용이 없는 것을 볼 수 있다. (SEO에 적합하지 않은 가장 큰 이유이기도 하다.)
// index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>React App</title> </head> <body> <div id="root"></div> </body> </html>
화면을 구성하기 위해 필요한 JS 파일을 읽어와 화면을 렌더링하며 페이지를 구성하게 된다. 웹 페이지가 렌더링 된 후에도 페이지 내에서 동작하는 모든 이벤트 또한 JS에 의해 일어나게 된다.
import React from "react"; import ReactDOM from "react-dom"; import App from './src/App'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); ReactDOM.render(<App />);
JS 코드에서 보여지는 것과 같이 index.html의 id가 root인 엘리먼트를 찾아서 JS 파일을 주입하는 것을 알 수 있다.
Next.js 페이지 렌더링 원리
Next.js의 경우 클라이언트에게 웹 페이지를 보내기 전에 Server Side에서 미리 페이지를 Pre-Rendering 한다. 그렇게 Pre-Rendering 된 HTML을 클라이언트에게 전달한다.
이 때 클라이언트에게 전달한 HTML은 단순히 화면만 보여주는 HTML일 뿐 JS 요소들이 하나도 없는 상태이다. (JS가 없기 때문에 이벤트 리스터 또한 페이지 내에 없는 상태를 의미한다.)
Next.js Server에서 Pre-Rendering 된 웹 페이지를 클라이언트에게 보낸 후에 바로 번들링된 JS 코드를 클라이언트에게 전송한다. 그리고 나서 번들링된 JS 코드를 HTML 요소 위에서 한번 더 렌더링 하면서 필요한 곳에 매칭이 되는데 이 과정을 Hydrate라고 한다.
위 과정을 보면 렌더링을 여러 번 하기 때문에 비효율적으로 보일 수 있다.
하지만 서버 단에서 먼저 Pre-Rendering하고 사용자에게 웹 페이지를 좀 더 빠르게 보여줄 수 있다. (JS 코드도 없기 때문에 훨씬 가벼운 상태로 전달된다.)