app
pages
는 Next.js의 예약 디렉토리 이름과 겹치기에 아래와 같이 수정하였습니다.app
→ core
pages
→ screens
// package.json
{
"dependencies": {
"react": "^18.3.1",
"next": "14.1.3",
"msw": "^2.7.0",
"typescript": "^5"
}
}
1-1. MSW 설치
pnpm install msw -D
1-2. MSW 초기화
// Next.js는 정적 폴더가 /public 이므로 /public 하위 mock 설정 파일을 설치한다.
npx msw init ./public
1-3. package.json 설정 (1-2 에서 명령어로 이미 설치했다면 생략)
{
// ... dependencies
"msw": {
"workerDirectory": [
"public"
]
}
}
2-1. next.config.mjs 파일 설정
const nextConfig = {
// ...
experimental: {
instrumentationHook: true, // instrumentation.ts 파일을 사용할 것이므로 true로 설정한다.
},
webpack: (config, { isServer }) => {
// Webpack의 resolve.alias를 사용하여, 서버와 클라이언트 환경에서 각각
// msw/browser 또는 msw/node 모듈을 로드하지 않도록 설정합니다.
// 서버 환경에서는 msw/browser 모듈을 사용하지 않도록 설정합니다.
if (isServer) {
if (Array.isArray(config.resolve.alias)) {
config.resolve.alias.push({ name: 'msw/browser', alias: false });
} else {
config.resolve.alias['msw/browser'] = false;
}
}
// 클라이언트 환경에서는 msw/node 모듈을 사용하지 않도록 설정합니다.
else {
if (Array.isArray(config.resolve.alias)) {
config.resolve.alias.push({ name: 'msw/node', alias: false });
} else {
config.resolve.alias['msw/node'] = false;
}
}
return config;
}
}
2-2. src/middleware.ts 파일 설정 (미들웨어를 사용하지 않았거나 굳이 필요하지 않으면 생략 가능)
export async function middleware(req) {
// ...
}
// 매칭된 패턴의 라우트는 미들웨어를 거치지 않음
export const config = {
// /mockServiceWorker.js 경로가 포함된 요청은 Middleware에서 제외한다.
matcher: ['/((?!mockServiceWorker.js).*)'],
};
3-1. 핸들러 설정
// src/core/Mock/handlers/handler.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get(`https://your_api_domain/api/users`, () => {
return HttpResponse.json({
code: 200,
status: 'OK',
message: 'OK',
data: {
id: '1',
name: 'Dunkun',
email: 'example@example.com',
},
});
}),
];
3-2. msw setup
// src/core/Mock/handlers/server.ts
// 서버 환경(node) 에서는 'msw/node' 의 모듈을 사용합니다.
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// src/core/Mock/handlers/browser.ts
// 클라이언트 환경(browser) 에서는 'msw/browser' 의 모듈을 사용합니다.
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
3-3. 서버 msw 환경 설정
// src/instrumentation.ts
// 런타임 환경이 'node.js' 라면 직전에 설정한 Mocks/server 에서 worker를 가져와 mocking을 설정합니다.
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { server } = await import('./core/Mock/handlers/server');
server.listen({
// MSW가 모킹(mocking)되지 않은 API 요청이 발생하면
// 이를 그대로 실제 네트워크 요청으로 보내기 위해 'bypass'를 설정합니다.
// 'bypass': 모킹되지 않은 요청은 실제 서버로 요청됨 (기본 API 동작 유지)
// 'warn': 콘솔에 경고 메시지를 출력하지만 요청은 실제 서버로 전달됨
// 'error': MSW가 처리하지 않는 요청이 발생하면 오류를 발생시킴
onUnhandledRequest: 'bypass',
});
}
}
3-4. 클라이언트 msw 환경 설정
// src/core/Mock/providers/MockBrowser.provider.ts
'use client';
let triggered = false;
async function initMock() {
// window가 undefiend가 아니라면 직전에 설정한 Mocks/browser 에서 worker를 가져와 mocking을 설정합니다.
if (typeof window !== 'undefined') {
const { worker } = await import('../handlers/browser');
await worker.start({
onUnhandledRequest: 'bypass',
});
}
}
export const MockProvider = () => {
if (!triggered) {
triggered = true;
throw initMock();
}
return null;
};
// src/app/layout.tsx
import { PropsWithChildren } from 'react';
import { MockProvider } from '@/core/Mock/providers/MockBrowser.provider.ts';
export default function RootLayout({ children }: PropsWithChildren) {
return (
<html>
<body>
<MockProvider />
{children}
</body>
</html>
);
}
next를 개발 환경에서 실행하여 콘솔에 [MSW] Mocking enabled
이 찍히는지 확인한다.
handlers에 설정한 URL에 요청했을 때, Mocking한 json이 응답오는지 확인한다.
만약 위 두가지 모두 성공적으로 응답이 온다면 MSW를 정상적으로 설치한 것이다.
이후에는 원하는 handler를 handlers에 추가하면 된다.