[Next.js] MSW API moking 방법 정리(csr, ssr)
MSW Mocking의 원리에 대해서는 아래 링크를 참고하시는 것이 좋을 것 같습니다.
https://tech.kakao.com/2021/09/29/mocking-fe/
next.js에서 mocking을 진행하려고 한다면, csr에서 msw를 사용할 것인지, ssr에서 msw를 사용할 것인지에 따라 코드를 다르게 작성해주어야 합니다.
먼저 MSW 패키지를 설치합니다.
npm install msw --save-dev
handler.ts
api 요청을 가로채고 모의 응답을 반환하는 역할을 할 handlers.ts 파일을 작성합니다.
// mocks/handlers.ts
import { rest } from 'msw'
export const handlers = [
rest.get('https://api.example.com/user', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
name: 'John Doe',
email: 'john.doe@example.com',
}),
)
}),
]
browser.ts
클라이언트 브라우저 환경에서 요청할 경우 setupWorker가 필요합니다.
import { setupWorker } from 'msw';
import { handlers } from './handler';
export const worker = setupWorker(...handlers);
server.ts
브라우저 환경 외에서 요청할 경우에는 node서버에서 요청이 진행되도록 돕는 setupServer가 필요합니다.
// mocks/server.ts
import { setupServer } from 'msw/node'
import { handlers } from './handler'
export const server = setupServer(...handlers)
저는 api 요청에 axios 라이브러리를 사용하기 때문에 아래 명령어로 따로 설치를 진행했습니다.
npm install axios
설치 도중에 아래와 같은 에러가 발생할 수 있습니다.
While resolving: msw@1.2.5 npm ERR! Found: typescript@5.2.2 npm ERR! node_modules/typescript npm ERR! peerOptional typescript@">=3.3.1" from eslint-config-next@13.4.19 npm ERR! node_modules/eslint-config-next npm ERR! eslint-config-next@"13.4.19" from the root project npm ERR! peer typescript@">=4.2.0" from ts-api-utils@1.0.2 npm ERR! node_modules/ts-api-utils npm ERR! ts-api-utils@"^1.0.1" from @typescript-eslint/typescript-estree@6.4.1 npm ERR! node_modules/@typescript-eslint/typescript-estree npm ERR! @typescript-eslint/typescript-estree@"6.4.1" from @typescript-eslint/parser@6.4.1 npm ERR! node_modules/@typescript-eslint/parser npm ERR! @typescript-eslint/parser@"^5.4.2 || ^6.0.0" from eslint-config-next@13.4.19 npm ERR! node_modules/eslint-config-next npm ERR! 1 more (the root project) npm ERR! npm ERR! Could not resolve dependency: npm ERR! peerOptional typescript@">= 4.4.x <= 5.1.x" from msw@1.2.5 npm ERR! node_modules/msw npm ERR! dev msw@"^1.2.5" from the root project npm ERR! npm ERR! Conflicting peer dependency: typescript@5.1.6 npm ERR! node_modules/typescript npm ERR! peerOptional typescript@">= 4.4.x <= 5.1.x" from msw@1.2.5 npm ERR! node_modules/msw npm ERR! dev msw@"^1.2.5" from the root project npm ERR! npm ERR! Fix the upstream dependency conflict, or retry npm ERR! this command with --force or --legacy-peer-deps npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
msw 패키지와 TypeScript 버전이 호환되지 않아서 생기는 문제임으로, 아래 명령어로 TypeScript 버전을 낮춥니다.
npm uninstall typescript
npm install typescript@5.1.x
그리고 msw 패키지를 최신버전으로 다시 설치합니다.
npm install msw@latest --save-dev
클라이언트에서(csr) msw를 사용할 것인지, ssr 방식처럼 서버에서 msw를 사용할 것인지에 따라 코드를 분기처리 해주어야 합니다.
csr은 setupWorker를, ssr은 setupServer를 사용해야 합니다.
setupWorker는 ssr 방식으로 페이지 생성을 하려고 할 때, 아래와 같은 에러가 발생할 수 있습니다.
Server Error Invariant Violation: [MSW] Failed to execute setupWorker in a non-browser environment. Consider using setupServer for Node.js environment instead.
This error happened while generating the page. Any console logs will be displayed in the terminal window.
Next.js로 서버사이드 렌더링(ssr) 방식을 사용해 mocking을 시도하려면, 페이지 생성을 Node.js 환경에서 실행할 수 있도록 코드를 작성해야 합니다.
// pages/_app.tsx
import { useEffect } from 'react';
import { AppProps } from 'next/app';
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
const { worker } = require('../mocks/browser');
worker.start();
}
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default MyApp;
typeof window !== 'undefined' 검사를 추가해서 클라이언트 환경에서 moking을 진행하려고 할 때만 setupWorker가 호출되도록 합니다.
클라이언트에서 moking을 진행하려는 경우, typeof window !== 'undefined' 조건을 제거하면 됩니다.
axios로 api 요청을 보낼 코드를 작성합니다.
(csr의 경우)
비동기적으로 데이터를 가져오는 useEffect훅을 활용했습니다.
// pages/index.tsx
import axios from 'axios';
import { useEffect, useState } from 'react';
interface User {
name: string;
email: string;
}
const IndexPage = () => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const fetchUser = async () => {
try {
const response = await axios.get<User>('https://api.example.com/user');
setUser(response.data);
} catch (error) {
console.error(error);
}
};
fetchUser();
}, []);
if (!user) return <div>Loading...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
};
export default IndexPage;
csr환경에서 진행하려면 다음 명령어로 mockServiceWorker.js 파일을 생성해야 합니다. 이 파일은 브라우저가 네트워크 요청을 가로채도록 도와줍니다.
npx msw init public/
(ssr의 경우)
// pages/index.tsx
import axios from 'axios';
import { GetServerSideProps } from 'next';
import { server } from '../mocks/server';
interface User {
name: string;
email: string;
}
interface IndexPageProps {
user: User;
}
const IndexPage = ({ user }: IndexPageProps) => {
if (!user) return <div>Loading...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
};
export const getServerSideProps: GetServerSideProps = async () => {
if (process.env.NODE_ENV === 'development') {
server.listen(); // Start the mock server
}
try {
const response = await axios.get<User>('https://api.example.com/user');
return { props: { user: response.data } };
} catch (error) {
if (process.env.NODE_ENV === 'development') {
server.close(); // Stop the mock server in case of an error
}
console.error(error);
return { props: {} };
}
};
export default IndexPage;
getServerSideProps 함수는 서버사이드에서 API 호출을 수행하고, 결과 데이터를 페이지 컴포넌트에 prop으로 전달합니다.
이렇게 하면 클라이언트 사이드에서 데이터를 가져오지 않고도 초기 렌더링 시점에 필요한 데이터가 이미 존재하게 됩니다.
위의 코드에 따라 dev 환경(개발 모드)에서 서버를 시작할 경우에서만 MSW가 활성화됩니다. 요청 후에는 반드시 server.close()를 호출하여 리소스를 정리하고 동일한 handler가 여러 번 등록되는 문제를 방지할 수 있도록 합니다.
npm run dev
ssr환경으로 구현해놓고 위와 같이 브라우저에서 결과 값을 확인하려고 한다면 csr의 경우처럼 아래 명령어로 mockServiceWorker.js파일을 생성해야 합니다.
npx msw init public/
실제 api를 사용할 때가 되면 프로덕션 환경에서는 axios의 메서드 인자를 실제 엔드포인트 URL로 변경해야합니다.
마찬가지로 MSW 관련 코드도 더 이상 필요하지 않기 때문에 제거해야 합니다.
// pages/index.tsx
import axios from 'axios';
import { GetServerSideProps } from 'next';
interface User {
name: string;
email: string;
}
interface IndexPageProps {
user: User;
}
const IndexPage = ({ user }: IndexPageProps) => {
if (!user) return <div>Loading...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
};
export const getServerSideProps: GetServerSideProps = async () => {
try {
// Replace the URL with your actual API endpoint
const response = await axios.get<User>('https://my-real-api.com/user');
return { props: { user: response.data } };
} catch (error) {
console.error(error);
return { props: {} };
}
};
export default IndexPage;
'Next.js' 카테고리의 다른 글
[Next.js] Error: ENOENT: no such file or directory 에러 해결 (0) | 2023.08.25 |
---|