개요
토스 테크 블로그의 '환경고민 없이 개발하기'라는 글을 읽고 요약, 내용을 추가하여 작성한 글이다.
서버 사이드 렌더링(SSR)
서버 사이드 렌더링은 클라이언트에서 제공한 컴포넌트 기반으로 렌더링 작업 일부를 서버에 위임하는 방식이라고 언급한다. 따라서 이 "서버"를 따로 관리할 필요가 있는데 NextJS 같은 프레임워크를 사용하게 되면 개발자가 따로 서버를 관리할 필요는 없어진다. 하지만 우리에게 직접적으로 보이지 않는 "서버"가 존재하기에 발생하는 문제가 있다고 언급한다.
SSR 도입 시 문제점 및 해결 방법
1. 브라우저 객체 접근 오류
문제 상황:
function App() {
// 쿼리 파라미터로 전달받은 유저의 이름을 얻어온다.
const name = new URL(location.href).searchParams.get('name');
// 유저의 이름을 화면에 출력한다.
return <div>{name}</div>;
}
이 코드는 서버 환경에서 에러가 발생한다. 서버에는 window 객체가 없기 때문이다. 없는 객체의 location에 접근하니 오류가 발생할 수밖에 없다. 그래서 서버 환경인 경우에는 해당 객체에 접근하지 않도록 해결해 보게 된다.
초기 해결 시도:
function App() {
const name = (() => {
/* 서버 환경인 경우, 객체에 접근하지 못하도록 수정 */
if (isServer()) {
return null;
}
return new URL(location.href).searchParams.get('name');
})();
return <div>{name}</div>;
}
해당 내용으로 해결하게 되면 Hydration Mismatch라는 오류가 발생한다.
2. Hydration Mismatch
SSR 이더라도 서버에서는 사용자 인터랙션이 불가능한 단순 markup html을 넘겨 주기 때문에 클라이언트 측의 이벤트 리스너나 상태관리 같은 로직을 결합하는 과정이 필요하다. 이것을 Hydration이라고 한다. 이 Hydration 과정에서 Mismatch가 발생했다는 오류는 클라이언트 측(react)에서 판단한 가상 DOM의 html과 서버 측에서 제공한 html이 다르다는 오류이다. 달라지는 이유는 다음과 같다.
// 서버
<div>{null}</div>
// 클라이언트
<div>{'김토스'}</div>
이로 인해 React가 hydration을 수행할 수 없게 되고 에러가 발생한다.
3. Isomorphic
서버와 클라이언트 양측에 동일한 렌더링 결과를 보장하는 코드 작성 해결법을 의미하는 것이 Isomorphic이다. 현재 예시에서는 useRouter가 Isomorphic의 한 예시이다. useRouter라는 훅을 다음과 같이 작성하였다. 서버측에서는 분명 window 객체가 없을테니, window 객체가 없는 상태에서 url을 가져오고 싶으면 NextJS 서버와의 통신 패킷 정보를 가져와서 url에 접근하면 된다.
function useRouter() {
// isServer 여부 확인
if (typeof window === 'undefined') {
// 서버 환경: 서버의 요청 객체(req)에서 쿼리 파라미터 추출
const { req } = useContext(ServerContext);
return {
query: parseQuery(req.url)
};
} else {
// 클라이언트 환경: window.location 사용
return {
query: parseQuery(window.location.href)
};
}
}
클라이언트 컴포넌트, 서버 컴포넌트 어디서든 같은 결과를 반환받을 수 있다.
import { useRouter } from 'test';
function App() {
const name = useRouter().query.name;
return <div>{name}</div>;
}
// 서버
<div>{'김토스'}</div>
// 클라이언트
<div>{'김토스'}</div>
마무리
Hydration Mismatch 문제는 NextJS를 처음 사용하면 가장 먼저, 자주 맞이할 문제이다. 현재 주어진 문제에서는 단순히 해당 컴포넌트를 클라이언트 컴포넌트로 작성하게 되면 해당 문제가 발생하지는 않겠지만, 서버 컴포넌트를 사용해야 한다면 조금 더 근본적으로 해결할 수 있는 Isomorphic 접근법을 찾는 것이 좋아 보인다. Isomorphic 접근법의 핵심은 '서버에서의 결과와 클라이언트 측에서의 결과를 동일하게 만드는 것'이다. 따라서 Hydration Mismatch가 발생하는 경우 Server 환경과의 분기 처리는 불가피할 것 같고, NextJS를 사용한다면 ServerContext 정보를 적극적으로 활용하여 Isomorphic 렌더링을 구현할 수 있을 것 같다."