Error: document is not defined
NextJS 프로젝트를 실행하면 RCC에서 사용된 document 객체를 사용할 때, 서버는 브라우저 API를 모르는 상태이기 때문에 에러가 발생한다. 실제 앱을 구동시키는데는 문제가 없지만 에러를 제거해야 예기치 못한 오류에 대응할 수 있기 때문에 해당 문제를 해결해 보려고 한다.
해당 에러를 해결할 수 있는 방법은 2가지 존재한다.
dynamic api 사용useEffect를 통한 클라이언트 렌더링import dynamic from 'next/dynamic';
// 사용
const DynamicRecipeList = dynamic(
() =>
import("@/containers/recipe/recipeList/recipeList").then(
(m) => m.RecipeList
),
{
loading: () => <p>Loading...</p>,
ssr: false
}
);
// @/containers/recipe/recipeList/recipeList
export const RecipeList = () => { ... }
next/dynamic은 내부적으로 React의 lazy와 Suspense를 사용한다. ssr: false 옵션을 설정하면 해당 컴포넌트를 클라이언트 사이드에서만 렌더링하도록 할 수 있다. 또한, loading 속성을 통해 로딩 중 보여줄 컴포넌트를 지정할 수 있다.
이때 <Suspense>의 fallback 로딩 상태를 처리하려면 반드시 loading 속성을 사용해야 한다.
서버측에서 코드가 실행되지 않도록 컴포넌트 마운트 이후에 children이 렌더링 되는 재사용 컴포넌트를 작성한다.
"use client"
import { useEffect, useState } from "react";
export const ClientRender = ({ children }: { children: React.ReactNode }) => {
const [mount, setMount] = useState<boolean>(false);
useEffect(() => {
setMount(true);
}, [setMount]);
return mount && children;
};
<Dialog> 컴포넌트는 document객체를 사용하고 있기 때문에 <ClientRender> 컴포넌트로 감싸 클라이언트 측에서 렌더링 되도록 구현한다.
만약 Suspense의 사용이 필요하다면 Suspense로 감싸주면 된다.
export default function LoginModal() {
return (
<Suspense>
<ClientRender>
<Dialog>...</Dialog>
</ClientRender>
</Suspense>
);
}
에러를 해결하기 위한 2가지 방법을 알아봤다. 나는 기존의 1번 방법이 코드의 가독성을 너무 해치기 때문에 선언적으로 깔끔하게 사용할 수 있는 2번 방법을 사용했다. 가독성 좋은 코드를 위해 더 노력하자.