일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 코테준비
- 자바
- stack
- js
- 일본어학습지
- 코딩테스트
- 마이라이트
- java
- 백준
- 가벼운학습지후기
- 프로그래머스
- SWEA
- array
- mybatis
- 카카오
- 가벼운학습지
- 성인학습지
- 자료구조
- 삼성
- 일본어독학
- 스텍
- 알고리즘
- 정렬
- 삼성소프트웨어아카데미
- 코딩
- 코테
- 인프런
- 자바스크립트
- 일본어공부
- javascript
- Today
- Total
개발에 AtoZ까지
Error:Text content does not match server-rendered HTML 본문
이슈
Error: Text content does not match server-rendered HTML
프로젝트 환경
- MonoRepo
- Next.js 12.3.1
- React 18.2.0
- M1 Macbook Pro
🔍원인
Next.js의 getServerSideProps를 활용하여 특정 페이지에서 SSR(server-side rendering) 사용했는데 React 트리 간에 차이가 존재하여, React 트리가 DOM과 동기화되지 않아 발생한 이슈였습니다.😭
조금 더 자세히 확인해보면 Next.js는 Pre-rendering
이 Default입니다.
Pre-rendering이란?
Pre-rendering의 HTML 파일을 생성하는 방법은 2가지입니다. 하나는 정적 생성
, 서버측 렌더링
입니다.
- 정적 생성: 빌드할때 생성됨, 각 요청할때 사용(캐시O)
- 서버측 렌더링: HTML을 요청할때 생성, 캐시X
이런 이유 때문에 SSR 사용 시 서버 측 렌더링 할 때 생성한 HTML로 1차 Paint가 진행됩니다.
그리고 난 후 서버로부터 받은 데이터를 가지고 HTML에 인터랙션 기능 및 이벤트 핸들러 등을 연결합니다.
이런 과정을 hydration
이라고 합니다.
즉 hydration
과정에서 React는 렌더링 된 컨텐츠가 서버와 클라이언트 간에 동일할 것으로 예상하는데 이 부분에서 차이가 있을 때 내용을 수정합니다.
그런데 개발 모드에서는 이런 불일치가 발생할 때 react는 불일치에 대한 알림을 보내주는데 그 알림을 제가 받았습니다ㅎㅎ
위의 사진을 보시면 초기에 서버 측 렌더링으로 생성한 HTML을 FCP(First Contentful Paint)하고 있습니다. 그 후 서버로부터 Response를 받으면 HTML에 인터랙션 기능 등을 연결해주는 작업이 진행됩니다. 이때 발생한 시간 차이로 데이터가 불일치하게 되고 이때 React Hydration Error
가 발생한 것입니다.
서버측 렌더링 된 HTML | HTML에 인터랙션 추가 |
---|---|
![]() |
![]() |
💊해결방법
상황에 따라 해결 방법은 여러 가지입니다. window 객체를 활용해서 window 객체가 준비됐다면(=HTML에 인터랙션 기능 등 연결이 완료됐다) 그 때 데이터를 표시하는 방법이 있는데 이 방법을 사용하려면 window 객체의 상태를 체크해주는 코드가 필요합니다. 상태를 체크해주는 코드보다 React의 suspense를 활용하여 해결하는것이 코드량이 적을것이라고 생각해서 Suspense를 사용해서 해결했습니다.
[수정전 코드]
interface IProps {
header?: ReactNode;
}
const UserList: React.FC<IProps> = ({ header }) => {
return (
<List
header={header}
footer={
<div>{new Date().toLocaleDateString()}</div>
}
bordered
dataSource={mockDataUsers}
renderItem={(item: IUser, index: number) => (
<List.Item>
<>
<Typography.Text mark>[{index + 1}]</Typography.Text>
{item.nickname}
</>
</List.Item>
)}
/>
);
};
export default UserList;
[수정 후 코드]
interface IProps {
header?: ReactNode;
}
const UserList: React.FC<IProps> = ({ header }) => {
return (
<List
header={header}
footer={
<Suspense fallback={null}>{new Date().toLocaleDateString()}</Suspense>
}
bordered
dataSource={mockDataUsers}
renderItem={(item: IUser, index: number) => (
<List.Item>
<>
<Typography.Text mark>[{index + 1}]</Typography.Text>
{item.nickname}
</>
</List.Item>
)}
/>
);
};
export default UserList;
마무리
사이드 프로젝트하면서 이슈들을 마주칠 때마다 성장할 수 있는 계기(?)인거 같아 기분 좋다. 사이드 프로젝트에서만큼은 이슈들이 많이 생겼으면 좋겠다 ㅎㅎㅎ
참고
https://nextjs.org/docs/messages/failed-loading-swc
https://nextjs.org/docs/basic-features/pages#pre-rendering
https://nextjs.org/docs/messages/react-hydration-error