nextjs 프레임워크를 이용해서 개발중인 프로젝트가 있다.
화면에 달력을 띄워야 했는데, 직접 만들기 너무 귀찮아서 react-calender 라이브러리를 import해서 개발하고 있었다.
그런데 이상하게도... 메인페이지에서 해당 페이지를 클릭해서 그 경로로 들어가면 에러가 안나는데, 이 경로에서 새로고침을 하면 에러가 났다.
에러가 발생한 코드는 아래와 같다.
<Calendar onChange={onChange} value={value} />
원인을 파악하기 위해서 찬찬히 에러메시지를 읽어보자.
이 에러는 Nextjs의 렌더링 기법에 의해 업데이트되었지만, React에서 페이지가 로드된 후 hydrate된 코드들은 업데이트되지 않았을 때 일어난다.
?? nextjs의 렌더링 기법이 뭔데? hydrate는 뭔데? 하시는 분들은 쭉 읽어주세요. 다 아는 놈들이군... 하시면 맨 하단에 해결방법만 제시하고 있습니다.
dom : 문서 객체 모델(DOM, Document Object Model). XML이나 HTML 문서에 접근하기 위한 일종의 인터페이스. 문서 내의 모든 요소를 정의하고, 각각의 요소에 접근하는 방법을 제공.
cssom : CSS 객체 모델(CSS Object Model). CSS 객체 모델로 자바스크립트가 CSS를 동적으로 조작할 수 있게 함.
⇒ dom은 html, cssom은 css를 위한 인터페이스.
SSR vs CSR
SSR
CSR
client side rendering. 클라이언트 측에서 렌더링을 담당하는 것.
컴포넌트를 렌더링하고 이벤트 핸들러를 연결하는 프로세스를 Hydration이라고 함.
인터랙션 기능 및 이벤트 핸들러를 이용해 '말라있는' HTML에 물을 주는 것.
Hydration 후 애플리케이션은 인터랙티브하며 클릭 등 사용자 입력에 응답함.
ttv → 🐳 hydrate 🐳 → tti
render()
vs hydrate()
render()
ReactDOM.render(element, container[, callback])
ReactDOM.render()
함수는 두번째 파라미터인 지정된 DOM 요소에 하위로 추가하여 렌더링. 렌더링이 완료되면 특정 이벤트를 처리할 콜백함수를 세번째 인자로 넣어줌.
hydrate()
ReactDOM.hydrate(element, container[, callback])
ReactDOM.hydrate()
함수는 특정 컴포넌트를 두번째 파라미터인 지정된 DOM 요소에 하위로 Hydrate . 렌더링을 통해 새로운 웹페이지를 구성하는 것이 아니라 기존 DOM Tree에서 해당되는 DOM 요소를 찾아 정해진 자바스크립트 속성들만 적용.
⇒ 새롭게 요소를 만드느냐 vs 원래 있던 요소를 찾아 js만 적용하느냐
일단 링크된 next 공식문서를 한번 들여다보자.
애플리케이션을 렌더링하는 동안, ssr / ssg 에 의해 pre-rendered된 react tree와 브라우저에 의해 처음으로 렌더링된 react tree에 차이가 존재했다. 이러한 첫 렌더링은 react의 특성인 hydration이다.
이 경우 react tree가 dom과의 동기화에 실패할 수 있으며 예기치 못한 콘텐츠가 화면에 나타날 수 있다.
⇒ 정리하자면 이 에러는 next의 특징인 ssr 방식으로 렌더링할 경우와, 브라우저에 의해 렌더링할 경우에 서로 다른 react tree가 만들어질 때 발생한다.
보통 이 문제는 특정 라이브러리나 코드 가운데 pre-rendering과 브라우저 간 차이가 있을 수 있는 부분에 의해 발생한다. 예를 들면 컴포넌트 렌더링에 window
를 사용한 경우가 있다.
function MyComponent() {
// This condition depends on `window`. During the first render of the browser the `color` variable will be different
const color = typeof window !== 'undefined' ? 'red' : 'blue'
// As color is passed as a prop there is a mismatch between what was rendered server-side vs what was rendered in the first render
return <h1 className={`title ${color}`}>Hello World!</h1>}
color
변수가 window 객체의 존재여부에 따라 달라지는데… 사실 이 코드는 이해가 안 되서 다른 분 블로그를 참고했다.
에러를 수정하려면, 위 코드가 window
객체에 의존하지 않도록 고쳐주면 된다.
// In order to prevent the first render from being different you can use `useEffect` which is only executed in the browser and is executed during hydration
import { useEffect, useState } from 'react'
function MyComponent() {
// The default value is 'blue', it will be used during pre-rendering and the first render in the browser (hydration)
const [color, setColor] = useState('blue')
// During hydration `useEffect` is called. `window` is available in `useEffect`. In this case because we know we're in the browser checking for window is not needed. If you need to read something from window that is fine.
// By calling `setColor` in `useEffect` a render is triggered after hydrating, this causes the "browser specific" value to be available. In this case 'red'.
useEffect(() => setColor('red'), [])
// As color is a state passed as a prop there is no mismatch between what was rendered server-side vs what was rendered in the first render. After useEffect runs the color is set to 'red'
return <h1 className={`title ${color}`}>Hello World!</h1>}
color의 기본값은 blue이다. 첫번째 렌더링이 일어나면 useEffect()가 실행된다. 따라서 첫번째 렌더링에 의해 hydration이 실행되면 setColor()가 실행되며 red가 된다.
⇒ 위에서 봤던 tti와 ttv 개념과도 이어지는 것 같다. ttv 일때는 blue, tti 일때는 red 인듯!
그 외에도 html 코드가 잘못 작성된 경우에도 발생할 수 있다고 한다. 아래 두 경우 외에 더 있을 수도 있다.
<p><div></div><p> → <div><div></div></div>
<div><li></li><div> → <div><ul><li></li></ul></div>
오류가 왜 일어나는지는 이해했다.
자 이제 중요한건… 이 라이브러리의 어느 부분에서 오류가 발생하는지 찾아내는 것.
일단 콘솔을 들여다보았다.
react-calendar에서 영문을 한글로 번역하는 과정에서 에러가 난 것 같다.
그럼 브라우저를 거치기 전에 미리 언어설정을 변경할 수 있나?
react-calendar 공식문서를 들여다보자.
다행히도 custom props 안에 있다!!!
IETF language tag에 의하면 한국어는 ko이다. 서버에서 렌더링할 때 미리 언어설정이 되도록 코드를 수정해주자.
<Calendar locale='ko' onChange={onChange} value={value} />
그럼 해결!!!
생각보다 ez한 해결법이라 어이없었지만, next의 렌더링 방식인 ssr에 대해서 제대로 알아볼 수 있었고 hydration에 대해서도 알 수 있어서 배움의 기회가 되었던 것 같다.
대단히 감사합니다. 헤매고 있었는데 큰 도움 됐어요!