[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.js
의 SSR
때문이었습니다. 서버 사이드에서 렌더링을 할 때에는 window
나 document
전역 객체가 존재하지 않습니다.
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는 아래와 같은 상황에 사용할 수 있습니다.
- 초기 로딩에 필요하지 않는 코드를 동적 로드(code splitting)함으로써 속도 개선
- 기능 별 코드 분할이 필요한 경우
단점
- dynamic import 사용 시 코드가 별도의 파일로 분리되어 추가 적인 HTTP 요청이 발생합니다.