SSR인 Next.js를 공부하다보니 hydration이라는 개념을 공부할 필요성을 느꼈다. React만 할 때는 있는지도 몰랐지만 Next.js를 하다보니 hydration 에러를 만나게 되었다.
전지전능 Chat GPT 왈
React의 "hydration"은 서버에서 생성된 HTML과 클라이언트에서 생성된 HTML을 일치시키는 과정을 나타냅니다. 이는 서버 측 렌더링(SSR)이나 정적 사이트 생성(SSG)와 같은 방식으로 생성된 초기 HTML을 클라이언트에서 인식하고 관리하는 과정입니다.
React 애플리케이션은 기본적으로 클라이언트 측에서 JavaScript를 사용하여 동적으로 렌더링되지만, SSR을 사용하면 서버에서 초기 로딩 시점에 HTML을 생성하여 클라이언트로 전달할 수 있습니다. 클라이언트에서는 이 초기 HTML을 가져와서 자체적으로 관리하게 됩니다.
서버 측 렌더링이나 정적 사이트 생성을 통해 생성된 HTML은 초기 상태와 렌더링된 컴포넌트의 구조를 포함합니다. 이 HTML은 브라우저에서 JavaScript로 인터랙티브한 React 애플리케이션으로 변환됩니다. 이때, 기존에 서버에서 생성된 HTML과 클라이언트에서 생성된 HTML이 서로 일치해야 합니다.
"Hydration"은 이 두 HTML 버전을 일치시키는 프로세스를 나타냅니다. 서버에서 생성된 HTML에는 데이터와 초기 상태가 포함되어 있으며, 클라이언트에서는 JavaScript를 사용하여 동적으로 업데이트됩니다. 그리고 이때 두 HTML 버전의 차이가 발생하지 않도록 일치시키는 것이 중요합니다.
React에서는 ReactDOM.hydrate 함수를 사용하여 클라이언트 측에서 hydration을 수행합니다. 일반적으로 이러한 프로세스는 개발자에게 숨겨져 있으며, React 자체에서 자동으로 처리됩니다. 하지만 때로는 초기 상태, 데이터, 렌더링 로직에 차이가 있을 때 경고가 발생하며, 이를 해결하기 위해서는 서버와 클라이언트 간의 불일치를 해결해야 합니다.
일단 hydration이란, 서버에서 보내준 HTML과 클라이언트에서 생성된 HTML을 일치시키는 프로세스를 나타낸다.
Next.js 에서는 서버에서 이미 HTML 렌더링해서 클라이언트에게 보내고 클라이언트는 그 HTML 파일을 받은 다음에 CSR을 실행시킨다.
이 과정에서 이미 있던 SSR HTML과 CSR HTML이 연결 과정이 필요한데 이것을 hydration이 해주는 것이다. hydration을 Next.js하면서 알게되는 것은 당연한 것이었다. 왜냐하면 지금까지는 React가 알아서 처리해줬지만 SSR을 사용하는 순간 불일치를 해결해줘야하는 우리의 책임이 생긴 것이다.
내가 만나게 된 hydration 에러는
이다. 이 에러는 recoil-persist를 사용하면서 발생한 에러인데, recoil-persist는 새로고침을 해도 state를 브라우저 메모리에 저장을 해서 state를 유지하게 해준다. 여기서가 문제이다. localStorage에 데이터를 추가하고 새로고침을 하면 서버에서는 localStorage와는 다른 state의 초기값을 가진 상태가 되면서 hydration을 할 때 매칭이 안되기 때문에 에러가 발생하는 것이다.
따라서 이 에러를 해결하기 위해서는 초기 데이터를 똑같이 해야한다. 하지만 나는 이미 recoil-persist를 사용할 때 SSR이 끝난 후에야 localStorage에 접근하도록 해놨었다. 코드를 보면
const { persistAtom } = recoilPersist();
const defaultValue: string = "data";
export const datatAtom = atom<string>({
key: "recoilTest",
default: defaultValue,
effects_UNSTABLE: [persistAtom],
});
export function useDataAtom() {
const [ssrCompleteState, setSsrCompleteState] = useState(true);
const [value, setValue] = useRecoilState(datatAtom);
console.log("ssrCompleteState", ssrCompleteState);
useEffect(() => {
setSsrCompleteState(false);
}, []);
return [ssrCompleteState ? defaultValue : value, setValue] as const;
}
이미 이렇게 useState와 useEffect를 이용해서 SSR중에는 defalutValue를 반환해서 SSR와 CSR의 HTML 싱크를 맞추고 SSR이 끝난 후에야 recoil value에 접근할 수 있도록 해서 hydration 에러를 해결했는데 도대체 어디서 나는 에러인 걸까?
원인은 selector에 있었다.
export const dataSelector = selector<string>({
key: "dataSelector",
get: ({ get }) => {
const data = get(dataAtom);
... 데이터 조작 후 리턴 로직
},
});
selector 같은 경우에는 useEffect로 SSR이 끝날 때까지 기다리는 로직이 없기 때문에 hydration 에러가 발생했던 것 같다.
그래서 selector를 에러없이 사용하기 위해서 Suspense로 selector를 사용하는 컴포넌트를 감싼다거나 getter 안에서 useEffect를 사용해볼까도 생각했지만 그러면 아직 selector를 안사용하는 것보다 코드가 복잡해지는 것 같아서 일단은 selector를 안쓰는 쪽으로 정하였다... ㅜㅜ
혹시 해결법을 아는 사람이 있다면 댓글 달아주세요...😭
참고
'React.js' 카테고리의 다른 글
[Next.js] SSR에서 recoil-persist 사용하기 (1) | 2024.01.13 |
---|---|
[Next.js] Recoile 추가하기 + 'use client' error (1) | 2024.01.11 |
[TailWind] calc() 를 사용해서 height 크기를 정해보자. (0) | 2023.11.27 |
[React] useEffect 연속 2번 동작 원인 및 해결 (0) | 2023.11.13 |
[React] useEffect 처음 마운트 함수 실행 막기 (0) | 2023.10.14 |