Search

[Next.js + MSW] 같은 URL GET 요청 분리하기

문제 발생

환경

Next 14.0.4
React 18.2.0
MSW 2.2.12
React Query 5.17.15

현상

Next.js 환경에서 페이지를 제작하던 중 GET 요청이 정상적으로 들어가지 않는 현상을 발견했습니다. 하지만 POST 요청의 경우 정상적으로 요청이 가고 있었습니다.
"use client" export const Page = ({ id }) => { const { data, isSuccess } = useQuery({ queryFn: () => axios.get(`${process.env.NEXT_PUBLIC_API_KEY}/book?id=${id}`), }); const { mutate } = useMutation({ mutationFn: async (rq) => await axios.post(`${process.env.NEXT_PUBLIC_API_KEY}/book/edit`, rq) }); ... }
JavaScript
복사
에러를 뱉고 있진 않지만, 원하는 값이 아닌 HTML 페이지를 리턴하고 있었습니다.

문제 파악

1. GET의 에러 분석

POST 요청은 정상동작하지만, GET 요청의 경우 MSW의 /book?id=1 이 아닌 Next page router의 /book?id=1 페이지가 응답값으로 오고 있었습니다.
이를 미루어보아 POST 요청은 MSW에서 처리되었지만, GET 요청은 비정상적으로 처리된 것으로 파악되었습니다.

2. 요청의 흐름 파악하기

제 생각으론 아래와 같이 MSW에서 모든 요청이 처리되어 Mocking 되어야 했습니다.
하지만 실제로 Mocking이 정상적으로 발생하지 않고 Page Server에서 응답값이 돌아오고 있었습니다.
정말로 MSW를 거치지 않는지 파악하기 위해 mockServiceWorker.js에 로깅을 걸어보았습니다.
// mockServiceWorker.js self.addEventListener("fetch", function (event) { const { request } = event; ... if (request.url.includes("/book")) { console.log( "fetch event msw", request.url, activeClientIds, activeClientIds.size, ); } if (activeClientIds.size === 0) { return; } ... });
JavaScript
복사

3. 요청의 흐름 파악하기

콘솔이 찍힌 것을 보니 GET 요청이 Mocking이 시작하기 전에 발생해 activeClientIds.size 의 값이 0이 되어 아래 조건문에서 걸려 MSW를 통과하고 있었습니다.
이와 달리 POST는 Mocking이 시작된 후 발생해 activeClientIds.size 값이 1이 되어 MSW에서 정상적으로 처리되고 있던 것입니다.
즉 MSW는 거치고 있지만, Mocking 서버가 실행되기 전에 fetch event가 발생해 Next Page 서버로 요청이 넘어가게 된 것입니다.

해결하기

1. MSW.start를 빨리하기

[MSW] Mocking Enabled 라는 문구가 발생하는 것을 보아 이 시점을 땡겨주면 문제가 해결될 것이라 생각했습니다.
우선 Provider를 통해서 worker가 start할 때까지 await 하도록 수정해주었습니다.
기존엔 worker start 부분이 즉시실행 함수 + useEffect로 되어있어 동작이 늦게 실행 되던 부분을 고쳤습니다.
// msw.tsx "use client"; /** * 참조 링크 * https://github.com/mswjs/msw/issues/1644 * draft pr : https://github.com/mswjs/examples/pull/101 */ export function MockProvider({ children, }: Readonly<{ children: React.ReactNode; }>) { useEffect(() => { (async () => { if (typeof window === "undefined") { console.log("working server"); const { server } = await import("../mocks/server"); server.listen(); } else { console.log("working client"); const { worker } = await import("../mocks/browser"); console.log("working client1"); await worker.start(); console.log("working client2"); } })(); }, []); // -> if (typeof window === "undefined") { console.log("working server"); const { server } = await import("../mocks/server"); server.listen(); } else { console.log("working client"); const { worker } = await import("../mocks/browser"); console.log("working client1"); await worker.start(); console.log("working client2"); } return <>{children}</>; }
JavaScript
복사
다음 로깅을 해보았을 때 결과입니다.
정상적으로 API가 호출된 것을 확인할 수 있습니다.

2. MSW의 요청 시점 조절은 완전한 해결책이 아님을 깨달음

페이지 URL과 API가 경로가 같았을 때 실제 서비스단에선 서버 URL과 Page 요청 URL이 다를 수 있어 문제가 없겠지만, Local에선 페이지를 요청해도 MSW에 Mocking되어 페이지가 아닌 API 리턴값인 JSON 값이 응답으로 올것이란 생각이 들었습니다.
URL에 직접 접속하는 경우엔 문제가 발생하지 않습니다. 왜냐하면 MockServer가 켜지기 전 요청이 발생하기 때문입니다.
하지만 페이지내에서 a 태그나 router를 사용해서 이동했을 때는 MSW로 요청이 Mocking 되어 rsc 코드를 가져오는데 에러가 발생하게 됩니다.

3. passthrough() 사용하기

MSW 공식문서를 보면 passthrough를 통해서 모킹된 API를 스킵할 수 있습니다. [참조]
헤더에 x-msw-intention 옵션을 넣어줘 여부에 따라 passthrough를 실행시켜줄 겁니다.
// axios.ts instance.interceptors.request.use( (config) => { config.headers = { ...config.headers, "x-msw-intention": "1", } as unknown as AxiosRequestHeaders; return config; }, );
JavaScript
복사
// handler.ts http.get("*", async ({ request }) => { if (!request.headers.get("x-msw-intention")) { return passthrough(); } }),
JavaScript
복사
이를 통해서 rsc를 호출하는 요청들을 정상적으로 next local 서버로 날아가는 것을 확인 할 수 있습니다.