Next.js로 서비스 운영하며 생긴 일 ep.1: Hydration 에러 해결하려다...
🚨 문제상황
- 100으로 올려두었던 Heath Score가 어느 새 34점으로 뚝 떨어져버렸다
-
설상가상으로 슬랙 등 기타 플랫폼에서 링크 공유 시 나오던 메타 정보도 언제부터인가 보이질 않고 있었다...
BEFORE
AFTER
🔍 배경: 어떻게 된 거냐면...
Heath Score 가 100점에서 34점으로 떨어졌던 그 사이 기간에 반영되었던 모든 변경사항을 추적해보니 원인이 아래와 같이 좁혀졌다:
- 페이지 퍼포먼스를 개선 / Lazy Loading을 적용하기 위해 Suspense를 Layout에 추가 (추후 이것 또한 좀 더 지역적으로 추가했어야 했음을 알게 되었다🥲)
- 개발환경에서 Next.js hydration 에러 대거 발생
- hydration 에러 픽스를 위해 mount 시점을 state로 반환해주는
useMounted()
라는 커스텀 훅을 만들어_app.tsx
에 반영해둠. 그리고_app.tsx
에는 헤더에 og meta 태그를 만들어주는 코드가 있었던 것!
// useMounted.ts
import { useEffect, useState } from "react";
export const useMounted = () => {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
return isMounted;
};
// _app.tsx
function App({ Component, pageProps }: AppProps) {
const isMounted = useMounted();
(... 생략)
// local storage로 인한 hydration error handling
if (!isMounted) return <></>;
return (
<Providers>
<Layout>
<OGMetas />
<Component {...pageProps} key={router.asPath} />
<CookieConsentBanner />
</Layout>
<OverlayLayout>{useVersionInfo && <VersionInfoOverlay />}</OverlayLayout>
<OverlayBanner />
</Providers>
);
}
🩺 해결방법: 고쳐보자!
-
원인이 좁혀졌기 때문에
_app.tsx
에 있던useMounted()
hook과isMounted
에 의존하던 부분을 제거했다. -
그리고 POSTMAN에서 localhost:3000으로 GET을 요청해서 리턴받는 HTML을 비교해보았다. 제거 전 / 후
<head>
의 og meta 태그에서 확연한 차이가 있었다:BEFORE
AFTER
- 이전에는 보이지 않던 메타태그들이
useMounted()
제거 후 확실하게 보여지기 시작했다!
- 이전에는 보이지 않던 메타태그들이
-
적용 후에 Health Score도 이전처럼 돌아온 걸 확인할 수 있었다:
BEFORE
AFTER
💡 오늘의 교훈: Next.js의 pre-rendering을 막지 말 것 (WIP)
- 부끄럽지만 이번 일은 Next.js의 동작방식을 제대로 이해하지 못하고 눈앞의 에러만 해결하려다 일어난 일이었다.
- "Next.js가 SEO 특화되어 있다"라는 건 공식처럼 외우고 있으면서도 pre-rendering으로 생성하고 있던 걸 마운트 이후 시점에 생성하도록 바꾸면 SEO에 영향을 줄 수밖에 없다는 사실을 간과했었다. 마치 SSR에게 CSR과 같이 동작하라고 주문한 후에 "아니, 왜 뼈대를 안만들었어?" 라고 물은 격.
- 즉, pre-rendering을 임의로 조정해서까지 렌더링 순서를 제어해야한다면, SSR 방식이 적절하지 않을 수 있다.
- 그래서 개발환경에서 나던 hydration 에러는 어떻게 픽스했는 지!? 는 to be continued...