문제 발생
환경
•
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() 사용하기
헤더에 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 서버로 날아가는 것을 확인 할 수 있습니다.