Next.js에서 MSW 도입하기... 그런데 dynamic import를 곁들인

트러블슈팅 · 2025. 3. 10.

Next.js의 이중환경

Next.js에서 작성된 코드는 서버(Node.js)와 브라우저라는 두 가지 다른 환경에서 실행됩니다. SSR(또는 SSG)단계에서는 Node.js 환경에서 코드가 실행되고, CSR단계에서는 브라우저 환경에서 코드가 실행됩니다. 따라서, alert()나 localStorage같은 브라우저 전용 API는 브라우저 환경에서만, fs같은 Node.js 전용 API는 서버환경에서만 호출 가능합니다.

MSW

Mock Service Worker는 자바스크립트 API 모킹 라이브러리입니다. 모킹 API를 이용하면 API가 준비되지 않은 상태에서도 프론트 엔드 개발이 가능하기 때문에 실제 개발에서 유용합니다. 다른 모킹 라이브러리리들은 주로 코드 레벨에서 요청을 가로채는 반면 MSW는 브라우저 전용 API인 Service Woker API를 사용하여 네트워크 레벨에서 작동을 지원하므로, 더 현실적인 테스트 환경을 제공하는 강점이 있습니다.

추천 아티클 1 Next.js에서 MSW(Mock Service Worker)로 네트워크 Mocking하기

추천 아티클 2 Mocking으로 프론트엔드 DX를 높여보자

문제 상황

수영장 정보사이트 어푸는 SSR과 CSR을 동시에 사용하는 하이브리드 렌더링방식으로 개발할 예정이었기 때문에, 모킹을 Node.js 환경과 브라우저 환경 모두에서 설정할 필요가 있었습니다.

따라서 다음과 같이 설정해주었습니다.

// 주의 잘못된 코드입니다. 절대 따라하지마세요.
// node환경에서 모 킹설정 : rsc 최상위 layout에 server.listen()함수 실행
//mocks/node.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'

export const server = setupServer(...handlers)

//layout.tsx
import { server } from '@/mocks/node'
...
if (process.env.NODE_ENV === 'development') {
  server.listen()
}
...

// 브라우저 환경에서 모킹 설정
//mocks/browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'

export const worker = setupWorker(...handlers)

//providers/msw-provider.tsx
// latout 컴포넌트에 프로바이더를 추가해줍니다. msw시작에 시간시 소요되므로
// 시작이 완료된 뒤 페이지가 렌더링 되도록 합니다.
'use client'

import { ReactNode, useEffect, useState } from 'react'
import { worker } from "@/mocks/browser"

export function MSWProvider({ children }: { children: ReactNode }) {


  useEffect(() => {
    async function enableMocking() {
      if (
        typeof window !== 'undefined' &&
        process.env.NODE_ENV === 'development'
      ) {
        await worker.start()
      }
    }

    enableMocking()
  }, [])

  return <>{children}</>
}

그런데, 빌드에러가 발생하였습니다.

Build Error
Module not found: Package path ./browser is not exported from package /.../frontend/node_modules/msw ...

해결방법

에러 메세지 Module not found: Package path ./browser is not exported from package 와 키워드 msw Next.js 구글링해서 해결방법을 찾을 수 있었습니다. dynamic import을 통해msw/browser모듈을 브라우저 환경인 경우에서만 불러와 해결했습니다.

'use client'

import { ReactNode, useEffect, useState } from 'react'

export function MSWProvider({ children }: { children: ReactNode }) {

  useEffect(() => {
    async function enableMocking() {
      if (
        typeof window !== 'undefined' &&
        process.env.NODE_ENV === 'development'
      ) {
        const { worker } = await import('@/mocks/browser') // 수정된 부분
        // useEffect훅안에서 클라이언트에서 동적으로 임포트 합니다.
        await worker.start()
      }
    }

    enableMocking()
  }, [])

  return <>{children}</>
}

문제원인

이는 msw/browser에서 setupWorkerimport하는 과정에 있었었습니다. 빌드에러시 발생한 안내 메세지대로msw/package.json을 살펴보면 msw/browser를 node.js 환경에서 import하는 경우 경로가 null임으로 모듈을 불러올 수 없습니다. 이는 msw/browser 모듈이 브라우저 전용 API를 사용하기 때문에, node.js 환경에서 실행될 경우 런타임 에러를 일으키기 때문에, 이를 사전에 방지한 것입니다. 따라서, Next.js의 번들러가 코드를 빌드 시에 이는 node.js 환경이므로 mock/browser 모듈을 불러오지 못하게 되는 것입니다.

// msw/package.json (2.7.0)
{
  "name": "msw",
  "version": "2.7.0",
  "description": "Seamless REST/GraphQL API mocking library for browser and Node.js.",
  "main": "./lib/core/index.js",
  "module": "./lib/core/index.mjs",
  "types": "./lib/core/index.d.ts",
  "exports": {
    ".": {
      "types": "./lib/core/index.d.ts",
      "require": "./lib/core/index.js",
      "import": "./lib/core/index.mjs",
      "default": "./lib/core/index.js"
    },
    "./browser": {
      "types": "./lib/browser/index.d.ts",
      "browser": {
        "require": "./lib/browser/index.js",
        "import": "./lib/browser/index.mjs"
      },
      "node": null,
      "require": "./lib/browser/index.js",
      "import": "./lib/browser/index.mjs",
      "default": "./lib/browser/index.js"
    },
...

'use client' 지시어 로 해결하지 못하는 이유

'use client' 지시어를 추가해 해당 컴포넌트가 클라이언트 컴포넌트임을 알려주었는데도 왜 문제가 발생했을까요? 이는 'use client' 지시어는 컴포넌트의 실행 환경을 클라이언트로 지정하는 것이지, 빌드 프로세스에는 영향을 주지 않기 때문입니다. Next.js의 빌드 프로세스는 다음 순서로 진행됩니다:

  1. 빌드타임: 모든 컴포넌트(서버/클라이언트)를 분석하고 각각 서버 번들과 클라이언트 번들로 분리
  2. 런타임:
    • 서버에서 정적 HTML 생성 (서버 컴포넌트)
    • 클라이언트에서 하이드레이션 수행 (클라이언트 컴포넌트)

따라서 'use client' 지시어가 있더라도 해당 컴포넌트의 정적 임포트는 빌드 시점에 분석되어야 하며, 브라우저 전용 라이브러리인 msw/browser는 빌드 시점에서 문제를 일으킵니다.

회고/아쉬웠던 점

사실은 공식 홈페이지에 문제를 예방할 수 있는 Browser intergration 가이드가 잘 작성되어있었는데. 이를 읽지 않았던것이 가장 큰 원인이었다. MSW를 도입하기전 관련 기술블로그 아티클을 잘 읽은 반면, 공식 홈페이지를 읽지 않는 기초적인 실수를 저질렀다. 하지만, 에러를 해결하면서, dynamic Import, node.js 환경과 브라우저 환경의 차이, Next.js의 빌드과정, package.json에서 export 설정과 같은 개념들을 학습할 수 있어서 나름 알찼던것 같다. 앞으로 새 라이브러리를 도입할 때 공식홈페이지를 꼭 하루이틀동안 정독하는 습관이 필요함을 느꼈다.