서론
이번 글은 Next App Router에서 MSW를 세팅하기 위한 방법을 정리해 본 글이다. (삽질에 삽질을 거듭하며..)

이번에 Devoops라는 서비스를 제작하면서 여러 불편함을 겪었는데 그중 하나가 백엔드 개발에 대한 프론트 의존성이었다.
개발기간이 2달 정도 주어졌다 보니 mocking 서버를 세팅하고 handler를 구현하는 것이 팀 차원에서 부담으로 느껴졌다.
그래서 우선 명세에 따라 Mock 데이터를 하드코딩 해서 작업을 하긴 했지만, 파라미터 값에 따라 달라져야 하는 데이터 같은 경우 작업하기가 어려웠고 데이터 패칭 로직으로 다시 한번 수정해야 하는 불편함이 있었다.
임시 런칭 이후, 추가작업을 하던 도중 서버가 죽어서 프론트 개발에 다시 병목이 걸리는 상황이 생겼는데, 그때마다 마냥 기다리는 시간이 아까워서 Mocking 라이브러리를 세팅하기로 했다.
다만 Next.js App Router 환경이다 보니 기본적으로 서버 환경과 브라우저 환경 둘 다 신경을 써야 했고, MSW가 클라이언트 컴포넌트에서만 동작하거나 그 반대의 경우만 동작하는 등 이슈가 꽤나 있었다.
그래서 App Router에서 MSW를 세팅하다가 에러를 맞이할 개발자 분들에게 조금이나마 도움이 되고자 작성해 봤다.
왜 MSW를 선택했나?
Next App Router를 쓰면, 노드 서버를 제공하기에 보통 Mock Data가 필요할 경우 Next.js의 노드 서버를 이용해 Mock API를 만들어서 사용한다. 다만 이 경우는 빌드 파일에 Mock API 내용이 담겨 빌드 파일 사이즈가 커지게 된다.
추가적으로 Nock의 경우 브라우저 환경에서는 사용하지 못했고, Postman Mock Server의 경우 각 개발자들이 테스트할 때마다 로컬에서 Mock 서버를 켜주어야 한다는 점이 부담으로 다가왔다.
MSW의 경우 node와 브라우저 두 환경에서 모두 돌아가 App Router 환경에서 쓰기 좋다고 느꼈고, 빌드 시 Mock Handler나 JSON 파일이 빌드 파일 내에 포함되지 않아서 빌드 파일 사이즈도 줄어들게 된다.
이러한 이유들로 MSW가 현재 상황에서 최적의 솔루션이라고 판단되어 선택하게 되었다.
MSW 란?
MSW(Mock Service Worker)란, 클라이언트가 HTTP 요청을 보내면 Service Worker가 요청을 가로챈 후 Mocking된 응답 값을 반환함으로써 서버와 통신을 하는 것 처럼 모방할 수 있는 오픈소스 라이브러리이다.
아래 접은 글은 MSW가 브라우저 환경에서 어떤 방식으로 동작하는지 간단하게 서술해 보았다.
MSW는 브라우저 환경에서 다음과 같이 동작한다.
1. 등록 및 대기
개발자가 등록한 서비스 워커(mockServiceWorker.js)가 브라우저 백그라운드에 설치되고 활성화 된다.
2. 요청 가로채기(Intercept)
브라우저에서 발생하는 모든 네트워크 요청(fetch, axios 등)은 실제 서버로 전송되기 전에 서비스 워커의 fetch 이벤트 리스너에 의해 먼저 가로채진다.
3. 핸들러 매칭
서비스 워커 안에서 실행되는 MSW 라이브러리는 가로챈 요청을 미리 정의된 Mock 핸들러 목록과 비교한다.
4. 모의 응답 반환
일치하는 핸들러를 찾으면, MSW는 실제 네트워크 통신 없이 Mock 데이터를 생성하여 브라우저에 "마치 실제 서버에서 온 것처럼" 응답한다. 일치하는 핸들러가 없으면 요청을 그대로 실제 서버로 전달할 수도 있다.

+ 서비스 워커는 브라우저에서만 사용 가능하기 때문에 노드 환경에서는 http, https, XMLHttpRequest 모듈을 확장해서 리퀘스트를 처리한다.
Next App Router 에서 MSW 적용하기
사실 MSW 적용방식 자체는 MSW 공식 문서에 잘 정리되어 있다.
Next가 아닌 다른 프레임워크일 경우 아래 방식 보단 위 공식 문서를 참고하길 권한다.
설치
우선 패키지 매니저를 통해 MSW를 설치한다.
yarn add -D msw
mockserviceworker.js 생성
이후 MSW에서 사용할 Service Worker 파일인 mockserviceworker.js를 생성해준다.
npx msw init public/ --save
위 명령어를 입력하고 나면 다음과 같이 public/mockServiceWorker.js가 생겨야 한다.

setup 코드 생성
다음으로는 mocking에 대한 파일을 관리할 디렉토리(src/mocks)를 만들어서, Service Worker와 Request Mocking Server에 대한 setup 코드를 각각 분리해서 관리할 것이다.
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import handlers from '@/mocks/handlers';
export const worker = setupWorker(...handlers);
// src/mocks/server.ts
import { setupServer } from 'msw/node';
import handlers from '@/mocks/handlers';
export const server = setupServer(...handlers);
mocks/handlers의 경우 각자의 관리 방식에 맞게 세팅하면 된다.
// src/mocks/handlers.ts
import { type HttpHandler } from 'msw';
import repositoriesHandler from '@/mocks/handlers/repositoriesHandler';
import userHandler from '@/mocks/handlers/userHandler';
const handlers: HttpHandler[] = [...userHandler, ...repositoriesHandler];
export default handlers;
서버 환경 setup 코드 실행
server.ts와 browser.ts를 정의한 다음엔 서버 환경과 브라우저 환경에 맞게 setup 코드가 실행될 수 있게 한다.
우선, 서버 환경의 경우에는 Next의 Instrumentation을 활용할 것이다.
Instrumentation 파일은 register 함수를 내보내며, Next.js 서버 인스턴스가 시작될 때 한 번 호출된다.
굳이 Instrumentation 파일을 쓸 필요는 없으나, Instrumentation 파일은 서버가 시작될 때 단 한 번만 실행 되는 것을 보장해 주기 때문에, 이런 종류의 초기화 작업을 하기에 적합하다고 생각한다.
instrumentation 파일은 src 폴더 아래에다 선언을 해주고 register 함수로 감싸주면 된다.
// src/instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { server } = await import('@/mocks/server');
server.listen();
}
}
만약 instrumentation을 사용하기에 적합하지 않은 경우에는 전역 layout 같은 곳에서 server 파일을 동적으로 import 한 다음 server.listen()을 해주면 된다.
(이 설정이 빠지면 서버 API 요청은 모킹되지 않는다.)
브라우저 환경 setup 코드 실행
브라우저 환경의 MSW는 MSWProvider라는 클라이언트 컴포넌트를 만들어 관리할 것이다.
이 컴포넌트는 MSW가 완전히 초기화된 후에만 내부에 있는 자식 컴포넌트들이 렌더링되도록 순서를 보장하여, API 요청이 모킹되기 전에 먼저 실행되는 레이스 컨디션을 방지 될 수 있도록 했다.
(일부 MSW가 초기화 되기 전에 컴포넌트가 이미 렌더링 되면, 렌더링시 호출되는 API콜을 하는 경우에 MSW가 제대로 후킹하지 못하는 경우도 있었기 때문이다.)
// src/providers/MSWProvider.tsx
'use client';
import { useState, useEffect, PropsWithChildren } from 'react';
const isMockingEnabled = process.env.NODE_ENV !== "production";
export default function MSWProvider({ children }: PropsWithChildren) {
const [mswReady, setMswReady] = useState(!isMockingEnabled);
useEffect(() => {
if (isMockingEnabled) {
const enableMocking = async () => {
const { worker } = await import('@/mocks/browser');
await worker.start();
setMswReady(true);
};
enableMocking();
}
}, []);
return mswReady ? <>{children}</> : null;
}
이 MSWProvider를 전역 layout.tsx에서 다른 Provider들과 함께 자식 컴포넌트를 감싸주면 설정이 완료된다.
<body>
<MSWComponent>{children}</MSWComponent>
</body>

+ Mocking 되지 않은 요청을 핸들링 하고 싶다면?
MSW는 Mocking 되지 않은 API 요청이 발생하면, 기본적으로 실제 네트워크로 요청을 보낸다.
이런 동작을 원치 않을 개발자를 위해 MSW에서는 onUnhandledRequest란 옵션을 제공한다.
server.listen({
// 'bypass': Mocking되지 않은 요청은 실제 서버로 요청된다. (기본 API 동작 유지)
// 'warn': 콘솔에 경고 메시지를 출력하지만 요청은 실제 서버로 전달된다.
// 'error': MSW가 처리하지 않는 요청이 발생하면 오류를 발생시킨다,
onUnhandledRequest: 'bypass',
});
이는 server.listen() 뿐만 아니라 worker.start() 에도 동일하게 적용할 수 있다. 상황에 따라서 세팅을 해주면 좋을 것 같다.
후기
세팅 방식에 대해서 이리저리 고민하다 보니 꽤나 글이 간단해진 것 같다.
돌이켜 보면 별거아닌 세팅인 것 같지만, 이런 결론을 내리기 까지 수많은 시도를 해본 것 같다.
처음엔 렌더링 방식으로 발생한 문제인지, 데이터 패칭 방식에 따른 문제인지 구별이 잘 되지 않아 꽤나 골머리를 앓았다.
문제를 해결하면서 조금 더 정확한 개념에 대한 이해가 필요했고, 하나씩 이해하다 보니 불필요한 코드들이 자연스레 정리되고 문제도 해결된 것 같다.
MSW를 활용해보니 네트워크 계층에서 API를 Mocking 한다는 것이 정말 큰 이점으로 다가왔다.
기존의 Mock 데이터를 하드코딩 해서 작업하는 방식의 경우 로직자체를 한 번 데이터 패칭을 위한 로직으로 바꿔줘야 해서 부가적인 리소스가 든다는 점이 아쉬웠는데, 네트워트 계층에서 API를 Mocking 하니 로직을 거의 변경할 필요가 없다는 것이 좋았다.
초기비용이 조금 더 들긴 하지만, 일일히 Mock 데이터를 제거하며 로직을 수정하는 것 보단 나은 것 같다.
뿐만 아니라, 내가 원하는 데이터를 쉽게 받아볼 수 있어, 특정 상황을 재현하고 디버깅 하는데도 유용했던 것 같다.
만약 API가 안될때 마다 백엔드를 재촉해야 하거나 백엔드에 대한 프론트 의존성을 줄이고 싶은 개발자라면 도입을 적극 추천한다.
참고해보면 좋은 자료
세팅에 대한 문제는 각자의 환경이나 기술스택에 따라 다르게 나타날수도 있고 다른 해결방법이 존재할 수도 있을 것 같다.
그래서 참고한 타 블로그들을 남겨둔다.
Next.js 서버 컴포넌트에서 MSW로 모킹한 데이터 React-query로 가져오기
Next.js 서버 컴포넌트에서 MSW로 모킹한 데이터 React-query로 가져오기
Aigoo 프로젝트에 MSW를 사용하기 위한 과정
velog.io
Next.js에서 MSW(Mock Service Worker)로 네트워크 Mocking하기
Next.js에서 MSW(Mock Service Worker)로 네트워크 Mocking하기 | 올리브영 테크블로그
네트워크 Mocking을 위해 고민했던 삽질기
oliveyoung.tech
MSW로 API Mocking으로 개발환경을 높이고 Next.js RSC환경에서도 MSW 적용하기
MSW로 API Mocking으로 개발환경을 높이고 Next.js RSC환경에서도 MSW 적용하기
MSW로 API Mocking을 하여 프론트엔드 개발 환경을 개선하고 Next.js의 App router의 RSC환경에서 MSW를 적용하는 방법을 알아봅니다.
www.handongryong.com
'개발' 카테고리의 다른 글
| 리액트 안티패턴 알아보기 (2) | 2025.08.31 |
|---|---|
| 2. 프레임워크가 렌더링에 주는 이점과 React Batching (2) | 2025.03.16 |
| 1. 브라우저 렌더링 과정 (0) | 2025.03.16 |
| 입력 값이 바뀜에 따라 요청이 계속 보내진다면? (Throttling & Debouncing) (4) | 2025.01.12 |
| "NET::ERR_CERT_DATE_INVALID" 에러 & SSL 인증서 재발급 (2) | 2024.09.13 |