Next.Js Konva Module not found: Can't resolve 'canvas' 에러 해결법
Module not found: Can't resolve 'canvas' 에러 해결
사용자가 직접 아이템을 배치하고 크기를 조절할 수 있는 동적인 캔버스 에디터 기능을 추가하고 싶었습니다.
Konva.js를 사용을 결정했고 인스톨 후에, yarn run dev를 실행하자 Module not found: Can't resolve 'canvas' 에러가 발생했습니다. 에러 해결 과정에서 배우게 된 Next.js의 핵심 렌더링 개념에 대해 이야기합니다.
"'canvas' 모듈을 찾을 수 없다."
Konva 라이브러리 내부 코드(konva/lib/index-node.js)가 require('canvas')를 호출하지만, 제 프로젝트는 해당 패키지가 존재하지 않았습니다.
Canvas 패키지 설치
// npm 사용자
npm install canvas
// yarn 사용자
yarn add canvas
위와같은 명령어를 터미널에 입력하면 패키지가 저장되어 konva의 사용이 가능해집니다.
SSR 렌더링 또는 CSR 렌더링
Next.js의 렌더링 환경 또한 영양을 미칠 수 있습니다.. Next.js 13 버전부터 도입된 App Router는 기본적으로 모든 컴포넌트를 서버 컴포넌트(Server Components)로 취급합니다. 즉, 서버에서 렌더링을 시도합니다.
하지만 Konva.js는 브라우저의 <canvas> 엘리먼트를 직접 조작하고, 사용자의 마우스 클릭, 드래그 같은 이벤트를 처리해야 합니다.
이는 window나 document 객체가 존재하는 클라이언트 환경에서만 정상적으로 동작함을 의미합니다.
서버에는 브라우저, 화면, document등이 없습니다. 그러므로 서버 환경에서 Konva.js를 실행한다면 에러가 발생합니다.
해결책: 'use client'
Next.js는 이런 상황을 위한 해결책이 존재하며, 'use client' 지시어로 해결 가능합니다.
파일 최상단에 'use client';를 추가하면, Next.js는 컴포넌트가 import하는 모든 자식들은 서버가 아니라 클라이언트에서 렌더링합니다.
// components/CanvasEditor.tsx
'use client'; // 이 컴포넌트는 클라이언트에서만 렌더링됩니다.
import React, { useEffect, useRef } from 'react';
import Konva from 'konva';
const CanvasEditor = () => {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (containerRef.current) {
const stage = new Konva.Stage({
container: containerRef.current,
width: 500,
height: 500,
});
const layer = new Konva.Layer();
stage.add(layer);
const rect = new Konva.Rect({
x: 50,
y: 50,
width: 100,
height: 100,
fill: 'skyblue',
draggable: true,
});
layer.add(rect);
}
}, []);
return <div ref={containerRef} />;
};
export default CanvasEditor;
동적 임포트(Dynamic Import)
한 걸음 더 나아가 최적화할 수 있는 방법도 있으며, next/dynamic의 사용입니다. Konva.js는 무거운 라이브러리 이므로 첫 페이지 로드에 포함시킬 필요가 없을 수 있습니다.
dynamic import를 사용하면 해당 컴포넌트를 서버 사이드 렌더링에서 완전히 제외하고, 필요할 때 클라이언트에서만 불러올 수 있습니다.
import dynamic from 'next/dynamic';
const CanvasEditor = dynamic(
() => import('@/components/CanvasEditor'),
{
ssr: false, // 서버 사이드 렌더링을 명시적으로 비활성화
loading: () => <p>Loading canvas...</p> // 로딩 중 보여줄 UI
}
);
// 페이지에서 이 CanvasEditor를 사용합니다
export default function MyPage() {
return (
<div>
<h1>My Awesome Editor</h1>
<CanvasEditor />
</div>
);
}
위의 방법 사용시 초기 로딩 성능을 개선하는 효과도 얻을 수 있습니다.
정리
버그 픽스 뿐만이 아닌 Next.js의 서버 컴포넌트와 클라이언트 컴포넌트의 차이를 알게 되었습니다.
- 문제: Konva.js 사용 시 Module not found: 'canvas' 에러 발생
- 원인: window, document 객체가 필요한 클라이언트 전용 라이브러리를 서버 환경(서버 컴포넌트)에서 렌더링하려고 시도함.
- 해결: 컴포넌트 파일 최상단에 'use client';를 추가하여 클라이언트 컴포넌트로 전환.
- 심화: next/dynamic과 ssr: false 옵션을 사용하여 코드 스플리팅 및 성능 최적화.