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> 엘리먼트를 직접 조작하고, 사용자의 마우스 클릭, 드래그 같은 이벤트를 처리해야 합니다.

이는 windowdocument 객체가 존재하는 클라이언트 환경에서만 정상적으로 동작함을 의미합니다.

서버에는 브라우저, 화면, 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/dynamicssr: false 옵션을 사용하여 코드 스플리팅 및 성능 최적화.
728x90

'Next.js' 카테고리의 다른 글

Next.js란?  (0) 2025.07.05

Next.js란?

Next.js는 React 라이브러리를 기반으로 구축된 웹 프레임워크입니다.

React가 사용자 인터페이스(UI)를 만드는 데 집중하는 라이브러리라면, Next.js는 서버사이드 렌더링(SSR), 정적 사이트 생성(SSG), 라우팅, 코드 분할 등 웹 애플리케이션 개발에 필요한 핵심 기능들을 통합하여 생산성과 성능을 향상한 도구이며, 과거 React만으로 웹을 개발할 때 복잡하게 설정해야 했던 부분들을 Next.js는 직관적이고 쉬운 방식으로 해결해줍니다.


다양한 렌더링 방식

  • 서버사이드 렌더링 (SSR - Server-Side Rendering): 사용자가 페이지를 요청할 때마다 서버에서 HTML을 미리 렌더링하여 클라이언트에 전송합니다. 이는 초기 로딩 속도를 크게 개선하고, 검색 엔진 최적화(SEO)에 매우 유리합니다.

  • 정적 사이트 생성 (SSG - Static Site Generation): 빌드 시점에 모든 페이지를 HTML 파일로 미리 생성합니다. 블로그, 마케팅 페이지, 문서 사이트와 같이 콘텐츠 변경이 적은 경우에 사용하면 CDN을 통해 매우 빠른 속도로 페이지를 제공할 수 있습니다.

  • 증분 정적 재생성 (ISR - Incremental Static Regeneration): SSG의 장점을 유지하면서 주기적으로 데이터를 업데이트하여 페이지를 다시 생성할 수 있는 기능입니다. 정적인 페이지의 최신성을 보장합니다.

  • 클라이언트 사이드 렌더링 (CSR - Client-Side Rendering): 기존 React 앱처럼 클라이언트(브라우저)에서 페이지를 렌더링하는 방식도 계속 지원하며, 페이지별로 렌더링을 유연하게 선택할 수 있습니다.

파일 시스템 기반 라우팅 (File-system based Routing)


app 또는 pages 디렉토리 내에 파일을 생성하면 해당 파일의 경로가 자동으로 URL 주소가 됩니다. 복잡한 라우팅 설정을 할 필요 없이 직관적으로 페이지를 추가하고 관리할 수 있습니다. 즉, 폴더명이 곧 URL 경로로 정해집니다. 예를 들어, app/about/page.js 파일을 만들면 /about 경로가 자동으로 생성됩니다.


API 라우트 (API Routes)


 Next.js 프로젝트 내에서 별도의 백엔드 서버 없이 간단한 API 엔드포인트를 쉽게 만들 수 있습니다. app/api 폴더 안에 파일을 생성하여 서버리스 함수처럼 활용할 수 있습니다.


ext.js 공식 웹사이트: https://nextjs.org/

Next.js 공식 문서 : https://nextjs.org/docs

728x90