Error:Text content does not match server-rendered HTML

AtoZ·2023년 2월 12일
0

이슈 해결

목록 보기
2/6
post-thumbnail

이슈

Error: Text content does not match server-rendered HTML

프로젝트 환경

  1. MonoRepo
  2. Next.js 12.3.1
  3. React 18.2.0
  4. M1 Macbook Pro

🔍원인

Next.js의 getServerSideProps를 활용하여 특정 페이지에서 SSR(server-side rendering) 사용했는데 React 트리 간에 차이가 존재하여, React 트리가 DOM과 동기화되지 않아 발생한 이슈였습니다.😭

조금 더 자세히 확인해보면 Next.js는 Pre-rendering이 Default입니다.

Pre-rendering이란?
클라이언트측 JS에서 모든 작업을 수행하는 대신 미리 각 페이지에 대한 HTML 파일을 생성합니다. 이렇게 미리 HTML 파일을 생성하다보니 초기 로딩 속도도 감소하고 SEO도 얻을 수 장점이 있습니다.

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가 발생한 것입니다.

서버측 렌더링 된 HTMLHTML에 인터랙션 추가

💊해결방법

상황에 따라 해결 방법은 여러 가지입니다. 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

profile
코딩으로 글쓰는 작가

0개의 댓글