Aigoo 프로젝트에서 모의 데이터를 사용하기 위해 MSW를 도입했습니다. React 환경에서는 MSW를 쉽게 사용할 수 있었지만, Next.js의 서버 컴포넌트에서 MSW를 사용할 때 예상치 못한 문제가 발생했습니다. 서버 컴포넌트에서 MSW가 undefined
값을 반환하는 문제를 해결하기 위해 Next.js의 서버 렌더링 과정을 파악하고 해결 방안을 찾아 보았습니다.
Next.js
MSW(Mock Service Worker)
React Query(tanstack query)
undefined
반환서버 컴포넌트 환경에서 MSW를 사용해 모의 데이터를 가져오려고 했을 때 문제가 발생했습니다. MSW는 정상적으로 API 요청을 intercept했으나, 서버 컴포넌트에서 데이터 페칭 결과가 undefined
로 반환되었습니다.
아래 원인들을 분석해본 결과, 한마디로 서버 컴포넌트 랜더링 과정에서 MSW가 요청을 intercept하는 방식이 맞지 않는다는 것이었다. 서버에서 네트워크 요청을 가로채는 서버 사이드 모킹 솔루션을 적용해야 했습니다.
Next.js 서버 컴포넌트는 서버에서 실행되고, 클라이언트 컴포넌트는 브라우저에서 실행됩니다.
MSW는 브라우저 환경에서 네트워크 요청을 가로채도록 설계되었습니다.
서버 컴포넌트에서 useQuery를 사용할 때 데이터 페칭은 페이지가 클라이언트로 전달되기 전에 서버에서 발생합니다. 이 시점에서는 MSW가 브라우저 환경에 존재하지 않기 때문에 활성화되지 않습니다.
MSW는 자바스크립트가 브라우저에서 실행될 때 네트워크 요청 가로채기 메커니즘을 설정합니다. 이는 서버가 이미 컴포넌트를 렌더링하고 데이터를 페칭한 이후에 발생합니다.
React Query의 useQuery 훅은 클라이언트 사이드 데이터 페칭을 위해 설계되었습니다. 이를 서버 컴포넌트에서 사용하면 기대한 대로 작동하지 않을 수 있습니다.
이 문제를 해결하기 위해 서버 컴포넌트의 렌더링 과정에 대한 이해가 필요했습니다. 서버 사이드 렌더링, 디하이드레이션, 하이드레이션 과정을 이해하며 어떻게 문제를 해결해야 할지 인사이트를 얻었습니다.
서버 사이드 렌더링
Dehydration (서버에서 렌더링 후 발생)
초기 클라이언트 로드 (브라우저에서 발생)
Hydration (JavaScript 번들이 로드된 후 클라이언트에서 발생)
하이드레이션 과정
1) 서버에서 렌더링된 정적 DOM에 JavaScript 이벤트 리스너를 연결한다.
2) 서버에서 제공된 HTML과 클라이언트 동기화합니다.
3) 서버에서 직렬화된 데이터를 클라이언트로 가져와 컴포넌트 상태에 반영한다.
React Query의useQuery
대신 prefetchQuery
를 사용하여 미리 서버에서 데이터를 가져온 후, dehydrate와 hydrate를 통해 데이터를 직렬화/역직렬화해 처리하는 방식을 사용했습니다.
QueryClient
를 생성합니다.prefetchQuery
메서드를 사용하여 데이터를 서버에서 미리 가져옵니다.dehydrate
하여 클라이언트 측에 전달합니다.hydrate
하여 화면에 보여줍니다.// hook/queries/useLoaderData.ts
import { getStudyMateirals } from "@/lib/api/api";
import { dehydrate, DehydratedState, QueryClient } from "@tanstack/react-query";
interface LoaderData {
dehydratedState: DehydratedState;
}
const useLoaderData = async (): Promise<LoaderData> => {
const queryClient = new QueryClient();
await queryClient.prefetchQuery({
queryKey: ["material"],
queryFn: async () => {
const data = await getStudyMateirals();
return data.data.studyDatas;
},
});
return {
dehydratedState: dehydrate(queryClient),
};
};
export default useLoaderData;
HydrationBoundary
를 Root에 감싸주고, state에 queryClient를 dehydrate(직렬화)시켜 전달합니다.
이렇게 되면 HydrationBoundary의 children 컴포넌트들은 모두 prefetch된 post 데이터를 사용할 수 있습니다.
// app/notes.page.tsx
import Materials from "@/components/material/Materials";
import useLoaderData from "@/hooks/queries/useLoaderData";
import { HydrationBoundary } from "@tanstack/react-query";
const Material = async () => {
const { dehydratedState } = await useLoaderData();
return (
<HydrationBoundary state={dehydratedState}>
<Materials />
</HydrationBoundary>
);
};
export default Material;
MSW Service worker로 부터 데이터를 응답 받을 것을 확인할 수 있었습니다.
이 문제를 해결하기 위해 서버 컴포넌트에서 API 요청을 모의하는 방법을 변경해야 합니다.
여러 글을 참고하며 적용해보았지만, 잘 해결되지 않았다. 추후에 다시 한번 시도해보기 위해서 간략하게 내용을 남겨 놓았습니다.
1. next의 instrumentation을 이용
src/instumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { server } = await import("./mocks/server");
server.listen();
}
}
next.config.mjs
experimental: { instrumentationHook: true },
webpack(config, { isServer }) {
if (isServer) {
if (Array.isArray(config.resolve.alias)) {
config.resolve.alias.push({ name: "msw/browser", alias: false });
} else {
config.resolve.alias["msw/browser"] = false;
}
} 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. express 서버 구축해서 서버사이드에서 일어나는 데이터 패칭 로직을 가로채기
src/mocks/http.ts
import { createMiddleware } from '@mswjs/http-middleware';
import express from 'express';
import cors from 'cors';
import { handlers } from './handlers';
const app = express();
const port = 9090;
app.use(cors({ origin: 'http://localhost:3000', optionsSuccessStatus: 200, credentials: true }));
app.use(express.json());
app.use(createMiddleware(...handlers));
app.listen(port, () => console.log(`Mock server is running on port: ${port}`));
package.json
{
"scripts": {
"dev": "next dev",
"mock": "npx tsx ./mocks/http.ts",
}
}
axios settings:
axios.defaults.baseURL = 'http://localhost:9090';
start mock server yarn mock, start dev server yarn dev
https://github.com/mswjs/msw/issues/1644#issuecomment-1750722052
이번 문제를 해결하면서 Next.js 서버 컴포넌트와 MSW의 동작 방식을 이해하게 되었습니다. React query의 prefetchQuery를 사용해서 서버사이드에서 데이터를 Prefetch하고 클라이언트에서 Hydration을 통해 서버 사이드에서 데이터 처리하는 방식도 배울 수 있었습니다.
공식 문서들을 참고하고, 여러 관련 글을 읽어보면서 정확한 내용을 정리하려고 노력했지만, 미흡한 부분이 있을 수 있습니다. 혹시 글을 읽으시면서 잘못된 부분이 있거나, 보완이 필요한 부분이 있다면 댓글 남겨주시면 도움이 될 것 같습니다.
좋은 글 잘 읽었습니다 도움이 되었습니다 감사합니다.