[Next.js] Dynamic Import 적용

[Next.js] Dynamic Import 적용

NextJS환경에서 리포팅 툴을 테스트하면서 dynamic import 라는 흥미로운 기능이 있어 공유 해 봅니다.

리포팅 툴이란 시스템에서 추출한 결과 값을 보고서 양식으로 출력해주는 개발툴입니다.

기존 프로젝트에 다른 개발자가 개발한 서비스를 적용하는 것은 생각보다 쉽지 않은 일입니다. 저는 Next.js 로 구성된 환경에 active report에서 제공하는 Report Viewer를 적용하는 과정에서 몇 차례 시행착오를 겪었습니다.


위와 같은 Report Viewer 컴포넌트를 NextJS 환경에 적용하기 위해 먼저 npm에서 필요 패키지를 다운해 줍니다.

pnpm add @grapecity/activereports-react@latest
pnpm add @grapecity/activereports

App Router를 적용한 Next.js 프로젝트의 page.tsx에 위 뷰어 모듈을 적용합니다.

import { Viewer } from "@grapecity/activereports-react";

export default function Home() {
  return (
    <div>
      <Viewer />
    </div>
  );
}

코드 실행 후 접속 하면 아래와 같은 에러를 만나게 됩니다.

window is not defined 라는 에러메세지의 원인은 Next.jsSSR 때문이었습니다. 서버 사이드에서 렌더링을 할 때에는 windowdocument 전역 객체가 존재하지 않습니다.

window 객체는 웹 브라우저에서 제공하는 전역 객체로, 웹 페이지의 전역 속성과 브라우저 창에 대한 정보와 조작 방법을 제공합니다. HTML에 대한 루트로서의 역할을 하며, JavaScript를 통해 웹 페이지를 조작할 때 사용되는 함수 및 API의 진입점 입니다.

window 객체를 통한 브라우저 조작 예제 코드는 아래와 같습니다. 브라우저 콘솔 창에서 테스트 해볼 수 있습니다.

window.open("https://naver.com", "_blank");

해당 코드를 실행하면 새로운 창이 열리면서 네이버에 접속 하는 것을 확인 할 수 있습니다.


다시 window is not defined 에러 메세지로 돌아오면 서버 사이드 렌더링시 리포트 뷰어 모듈에서 사용하는 window 객체가 존재하지 않아 발생하는 에러 메시지였다는 것을 확인할 수 있습니다.

Active Report Viewer 같은 클라이언트 사이드 모듈을 Next.js 프로젝트에서 사용할 때는 이 모듈이 클라이언트 사이드에서만 실행되도록 해야 합니다. 이때 dynamic import 를 사용하여 문제를 해결할 수 있습니다.


먼저 components 폴더를 생성 후 npm package에서 import한 뷰어를 감싸는 Wrapper 컴포넌트를 생성해 줍니다.

// ViewerWrapper.tsx

"use client";

import { Viewer, Props } from "@grapecity/activereports-react";
import "@grapecity/activereports/styles/ar-js-ui.css";
import "@grapecity/activereports/styles/ar-js-viewer.css";

export type ViewerWrapperProps = Props;

const ViewerWrapper = (props: ViewerWrapperProps) => {
  return <Viewer {...props} />;
};

export default ViewerWrapper;

page.tsx에서는 위 Wrapper 컴포넌트를 dynamic import를 사용하여 불러옵니다.

import dynamic from "next/dynamic";
import { ViewerWrapperProps } from "../components/ViewerWrapper";

const ReportViewer = dynamic<ViewerWrapperProps>(
  () => {
    return import("@/components/ViewerWrapper");
  },
  { ssr: false }
);

export default function Home() {
  return (
    <div className="w-full h-screen">
      <ReportViewer />
    </div>
  );
}

이처럼 dynamic import를 사용하여 특정 컴포넌트의 서버 사이드 렌더링을 비활성화하고, 필요할 때만 클라이언트 사이드에서 로드 할 수 있습니다.


ViewerWrapper.tsx가 "use client" 지시어를 사용하므로 클라이언트 컴포넌트이기 때문에 dynamic import 없이 아래처럼 사용해도 되지 않을까 생각해봤습니다.

import dynamic from "next/dynamic";
import ViewerWrapper, { ViewerWrapperProps } from "../components/ViewerWrapper";

// const ReportViewer = dynamic<ViewerWrapperProps>(
//   () => {
//     return import("@/components/ViewerWrapper");
//   },
//   { ssr: false }
// );

export default function Home() {
  return (
    <div className="w-full h-screen">
      {/* <ReportViewer /> */}
      <ViewerWrapper />
    </div>
  );
}

하지만 위에서처럼 직접 ViewerWrapper 컴포넌트를 사용할 경우 화면은 렌더링 되지만 내부적으로는 ReferenceError: window is not defined 가 발생합니다. 그 이유는 "use client" 지시어를 사용하더라도 page.tsx의 import 구문은 서버 사이드에서도 평가되기 때문에 ViewWrapper 내부에서 window 객체를 접근하려 한다면 서버사이드 렌더링 시점에서 에러가 발생하게 됩니다.


위 경우 외에도 dynamic import는 아래와 같은 상황에 사용할 수 있습니다.

  1. 초기 로딩에 필요하지 않는 코드를 동적 로드(code splitting)함으로써 속도 개선
  2. 기능 별 코드 분할이 필요한 경우

단점

  1. dynamic import 사용 시 코드가 별도의 파일로 분리되어 추가 적인 HTTP 요청이 발생합니다.

Read more

Lumen - AI Agent를 위한 지속 가능한 두뇌

Lumen - AI Agent를 위한 지속 가능한 두뇌

왜 만들었는가 AI 에이전트는 모든 대화를 기억상실증 상태에서 시작합니다. Claude Code, Cursor, Codex, Mastra 하네스, LangChain 파이프라인 — 이 도구들은 세상을 알지만 당신의 세상은 전혀 모릅니다. 당신이 읽은 200편의 논문, 당신이 출시하는 코드베이스, 지난 분기에 내린 아키텍처 결정, 새벽 2시에 그 버그를 잡아냈을 때 마침내 통했던 트래젝토리. 모든 세션이 같은 컨텍스트를

By Sardor Madaminov

200 OK, 텅 빈 body — Starlette Race Condition 장애 분석기

발생일: 2026-04-23 / 해결일: 2026-04-27 영향 범위: report-dev.machine365.ai 전체 API 들어가며 API가 200 OK를 반환하는데 body가 비어있다. 프론트엔드에는 아무것도 안 뜨고, Swagger UI(/docs)도 빈 화면. 그런데 로컬에서 돌리면 멀쩡하다. 배경은 이랬다. 미터링(Metering) 기능을 만들면서 API 호출 로그를 수집할 미들웨어를 작성했다. Spring Boot 백엔드에 먼저 적용하고, Python

By Jeonggil