최근 Next.js 15 버전에서 MSW(Mock Service Worker)의 적용 방법에 대해 깊이 고민하였습니다.
이 글에서 Next.js 환경에서 MSW를 도입하는 과정에서 겪었던 트러블 슈팅 경험을 공유하고자 합니다.
그냥 설치하고 깔면 되는거 아님?
안타깝게도 그렇지 않았습니다.
MSW는 기본적으로 브라우저의 Service Worker API를 사용하는데, 이 덕분에 네트워크 요청을 가로채서 개발할때 네트워크 탭을 열어 로그를 확인해볼 수 있는 것입니다.
하지만 서버(Node.js) 환경과 클라이언트(브라우저)환경이 혼재하는 Next.js에서는 이게 병목으로 작용했습니다.
Next.js는 클라이언트/서버 환경이 혼재되어 있기 때문에 별도 분리 필요
일반적으로 js 파일을 만들고 아래와 같이 setUpWorker 함수를 임포트받아 모킹 핸들러를 등록하고,
npx로 설치한 mockServiceWorker.js 파일을 등록하여 실행하는게 일반적이다.
하지만 Node.js 환경에서는 setUpWorker 대신 setUpServer 함수를 사용해야 합니다.
왜냐하면 서버(Node.js 환경)에서는 브라우저 API (Service Worker)를 사용할 수 없기 때문입니다.
따라서 setUpServer는 http 모듈을 감싸는 방식으로 동작합니다.
이를 통해 서버컴포넌트에서도 MSW가 동작할 수 있도록 합니다.
래퍼런스: https://mswjs.io/docs/api/setup-server
// 클라이언트용 코드 (mocks/browser.js)
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
// 사용처 예시
await worker.start({
serviceWorker: { url: '/mockServiceWorker.js' },
onUnhandledRequest: 'bypass',
});
// 서버용 코드 (mocks/server.js)
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
/** 서버 컴포넌트(Node.js 레벨)에서 모킹을 위함 */
export const server = setupServer(...handlers);
export const startMSWServer = async () => {
if (process.env.NODE_ENV === 'development') return server.listen();
};
그리고 클라이언트 환경에서 실행되는 것을 보장하기 위해 다음과 같이 Provider를 만들고, 루트 레이아웃 컴포넌트에서 children으로 감싸주면 전역으로 적용이 된다.
'use client';
import { useEffect } from 'react';
export default function MSWClientProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
const init = async () => {
// 개발 환경에서만 실행되도록 보장
if (process.env.NODE_ENV === 'development') {
const { worker } = await import('./browser');
await worker.start({
serviceWorker: { url: '/mockServiceWorker.js' },
onUnhandledRequest: 'bypass',
});
}
};
init();
}, []);
return children;
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
<MSWClientProvider>{children}</MSWClientProvider>
</body>
</html>
);
Service Worker 등록에 걸리는 시간 고려
여기까지 했음에도 불구하고, 클라이언트 컴포넌트에서 fetch요청을 보내 모킹이 제대로 동작하는지 테스트 해보면 404가 뜨는걸 볼 수 있다. 이유는 worker.start() 함수는 비동기이며, 등록되는데 시간이 좀 필요하기 때문이다. (수십 ~ 수백 ms 정도로 추정)
따라서 등록되기 전의 fetch 요청은 모킹되지 않기에 그 사이 요청은 무시되거나 404가 발생할 수 있습니다.
따라서 이러한 시간차를 고려하여 다음과 같이 플래그 상태를 만들어, MSW가 준비되었을때 비로소 children을 렌더링하도록하여 MSW가 정상적으로 동작하도록 보장할 수 있다.
'use client';
import { useState, useEffect } from 'react';
export default function MSWClientProvider({ children }: { children: React.ReactNode }) {
const [readyToUseMSW, setReadyToUseMSW] = useState(false);
useEffect(() => {
const init = async () => {
if (process.env.NODE_ENV === 'development') {
const { worker } = await import('./browser');
await worker.start({
serviceWorker: { url: '/mockServiceWorker.js' },
onUnhandledRequest: 'bypass',
});
setReadyToUseMSW(true);
}
};
init();
}, []);
return readyToUseMSW ? children : null;
}
결론
더 좋은 방법에 대한 제시는 언제나 환영하며 이 글이 Next.js에서 MSW 세팅 중 어려움을 겪는 이들에게 도움이 되었으면 좋겠습니다.
'프론트엔드' 카테고리의 다른 글
[프론트엔드] 접근성을 고려한 컴포넌트 설계 (Dropdown) (0) | 2025.06.24 |
---|---|
[프론트엔드] AWS에 Next.js 앱을 배포하기 위한 옵션들 (1) | 2025.06.13 |
[프론트엔드] 웹 접근성 및 UX 1편 - Button 컴포넌트 (1) | 2025.05.28 |
[프론트엔드] 워터폴 현상 & 문제가 되는 이유 (2) | 2025.05.16 |
[프론트엔드] 왜 프론트엔드에서는 MVC를 잘 안 쓸까? (0) | 2025.05.09 |