신사업 플랫폼 개발을 하면서 이상하게 오랫동안 해결되지 않았던 에러가 있었다.
바로 NextJS의 hydration error 였다.
hydration error는 SSR(Server side rendering)을 통해 만들어진 html 파일에 javascript 이벤트를 등록하던 중 태그의 위치가 맞지 않거나 변경되어 제대로 이벤트를 등록하지 못하는 상황에서 발생하는 에러이다.
하지만 아무리 문서를 뒤져봐도 코드에는 문제가 없어보였다.
심지어 에러가 항상 발생하는게 아니고 10번 중에 한 번 간헐적으로 발생했기 때문에 에러를 찾기가 쉽지 않았다.
그래도 포기할 수는 없었다. 우리 서비스에서 가장 많이 발생하는 에러가 바로 hydration error였고, 그 에러를 계속 뒀다가는 점점 문제가 커질 것이라고 생각했기 때문이다.
우리는 여러 가지 방법을 시도해봤다.
hydration error가 발생했을 때의 SSR로 제공된 파일과 CSR을 하나하나 비교해보기도 하고, 네트워크 혹은 CPU에 과부화가 온 것은 아닌지 확인하기도 했다. 다른 여러 가지 방법들도 시도해봤지만 큰 문제가 없어보였다.
코드를 최소한으로 두고 발생하는 조건을 찾아보기로 했다. 그리고 가끔이지만 발생하는 조건은 바로 recoil에서 비동기로 api를 요청하는 부분이었다. 비동기로 감싸고 있는 컴포넌트는 NextJS의 dynamic import를 통해서 가져오고 있었다. 그리고 문서에 따라 dynamic의 옵션에 ssr: false 를 설정했다.
recoil 문서와 nextjs문서 상에서는 비동기로 가져오는 부분에서 문제가 없어보였다.
recoil 비동기 요청 방법: https://recoiljs.org/docs/guides/asynchronous-data-queries/
nextjs dynamic import: https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
그런데 한 가지 걸리는건 nextjs의 버전이었다. 우리는 nextjs의 12버전을 사용하고 있었다. 그리고 nextjs 12버전의 dynamic 코드 부분을 뜯어봤다.
그런데 확인해보니 이상한 점이 있었다. nextjs dynamic 부분에서 ssr을 false로 설정할 경우 react의 lazy를 이용하여 동적으로 가져오는 것으로 알고 있었는데, 그냥 null을 반환하는 것이 아닌가.
그리고 13버전의 코드를 확인해보니 같은 조건에서 Suspense와 React.lazy를 사용하고 있었다.
이 부분에서 nextjs 12 버전에서는 Suspense가 제대로 적용되지 않았을 가능성이 높다고 생각했고, 이번 기회에 13버전으로 업그레이드 후 배포해보았다. (13버전으로 업그레이드 하려면 꽤 많은 부분을 변경해야 해서 언제 해야하나 간을 보고 있었는데, 이번 기회에 업그레이드를 했다)
그런데,,,, 배포 후 더 이상 hydration error는 발생하지 않았다.
아무리 새로고침을 하고, 다시 들어가보아도 sentry에서는 어떤 메세지도 오지 않았다.
드디어 계속 해결하지 못했던 부분을 해결한 것이다.
CTO님의 조언과 팀원들의 도움이 정말 컸다.
드디어 끈질겼던 이 에러에서 해방이라는 생각에 매우 기쁜 하루였다.