일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 리액트훅
- SSR
- ErrorBoundary
- Firebase
- react-hook
- NextJs
- useLayoutEffect
- reactquery
- react-hook-form
- 클래스
- next-cookies
- 모던자바스크립트
- key
- docker
- 초기마운트
- 리액트 훅
- express
- CSR
- 리액트
- msw
- lazy()
- react
- Database
- useEffect
- Today
- Total
한우의 개발일기
[Next.js]Next.js에 MSW 적용하기 본문
MSW 관련 정리
MSW란??
- API Mocking 라이브러리이다.
- Service Worker를 통해 HTTP 요청을 가로채기 때문에 네트워크 수준에서 Mocking이 가능하다.
MSW(Mock Service Worker)는 개발 과정에서 두 가지 주요 환경을 모킹할 수 있도록 설계되었습니다
- Worker: 브라우저 환경에서 네트워크 요청을 모킹합니다. 일반적으로 React 애플리케이션 개발에서 사용됩니다.
- Server: Node.js 환경에서 네트워크 요청을 모킹합니다. 주로 서버 사이드 렌더링 또는 테스트 환경(Jest 등)에서 활용됩니다.
MOCKING이란?
- 테스트 데이터를 만들어서 활용하는 방식
Mocking 진행 방법
- 내부 로직에 직접 Mocking 하여 필요한 화면에 붙이는 방식
(JSON 으로 목데이터를 만드는방식)- 장점 : 구현이 빠르다.
- 단점 : 서비스 로직 수정, HTTP 메소드와 네트워크의 응답 상태에 따라 각각 대응하기 어렵다.
- Mock 서버를 별도로 만드는 방식
- 장점 : 서비스 로직을 수정하지 않아도 된다. (fetch 혹은 axios를 그대로 사용해서 쓰면된다)
- 단점 : 데이터 상태 구현시 시간이 많이 든다. 또한 로컬이 아니면 Mock 서버를 원격으로 추가 환경 구성 작업을 해야 한다.
MSW의 원리
- 브라우저에서 이루어지는 요청을 Service Worker가 가로챈다.
- Service Worker 요청을 복사해서 MSW에게 해당 요청과 일치하는 모의 응답을 제공받고 이를 브라우저에게 그대로 전달한다.
위에 두 설명에서 보이듯이 브라우저 환경에서 데이터 요청을하면 MSW에서 가로채기를 하여 MSW 서버에서 설정한 응답을 보여주는 형식이다
예를 들자면 팝업스토어를 받는 API 로직에서 env.local 파일안에
NEXT_PUBLIC_API_MOCKING=enabled
를 넣어 놓으는다면 로컬환경에서npm run dev
나 yarn dev
와 같은 로컬개발환경에서는 서비스워커 서버가 응답을 가로채는것이다
백엔드 서버가 열려서 백엔드에 대한 API가 있다면 disable
만해주면 바로 백엔드 서버를 이용할 수 있다
MSW 설정방법(Next.js 앱라우터)
일단 앱라우터는 서버컴포넌트 클라이언트 컴포넌트가 완벽하게 분리가 되어있어
MSW를 서버사이드 환경에서는 아직까지 사용이 용이하지 않다는 것 때문에
(MSW 깃허브 이슈에 명시되어있음)
express를 활용을 하는 방법을 찾아보았다
그래서 제 방식은 앱라우터에서 적용되는 방식으로 적었습니다.
퓨어리액트 혹은 페이지라우터 에서는 방법이 다를수 있습니다
MSW 본인들의 잘못은 아니란다
MSW 세팅
최상단 src
폴더가 있다면 바로 안에 없다면 작업환경의 최상단에 mocks
폴더를 만든후 밑에 파일들을 만든다
설치하기
yarn add msw --dev // msw 설
npx msw init public/ — save // 서비스 워커 설치
필요한 파일들
- browser.ts
import { setupWorker } from 'msw/browser'; import { handler } from './handler'; export const worker = setupWorker(...handler);
브라우저의 환경을 세팅해준다 . (브라우저 환경에서 서비스워커가 돌아가게 해준다)
- server.ts
import {setupServer} from 'msw/node'; import {handler} from './handler'; export const server = setupServer(...handler);
next서버 환경이아닌 Node.js 서버
서의 설정을 해준다.
- http.ts (앱라우터 환경에서 사용시)
import cors from "cors";
import express from "express";
import { handlers } from "./handlers";
const app = express();
const port = 9090; // express 서버의 포트번호
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}`));
위와 같이 express를 이용한 서버를 열 포트번호와 cors에러를 방지하기위한 개발환경설정을 해준다.
msw.ts (이름은 마음대로)
"use client"; import { useEffect } from "react"; export const MSWComponent = () => { useEffect(() => { if (typeof window !== "undefined") { if (process.env.NEXT_PUBLIC_API_MOCKING === "enabled") { require("./browser"); } } }, []); return null; };```
원래는 루트레이아웃에 이 설정을 해준다음 모든 페이지에서 목서버가 돌아갈때 해당 코드가 실행되어야하는데 앱라우터의 특성상 useEffect
와같은 리액트 훅이 들어간다면 클라이언트 컴포넌트가 되어버려서 파일을 분리해 주었다
- layout.tsx (최상단 루트레이아웃)
- ``import "./globals.css"; import { Metadata } from "next"; import MobileSizeWatcher from "@/components/mobile-size-watcher"; import NavBar from "@/components/nav-bar"; import ModalProvider from "@/provider/modal-provider"; import { MSWComponent } from "../mocks/msw-Initializer"; export const metadata: Metadata = { title: "POP CLOUD", description: "전시회 골라 골라", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return (
{children} ); }```
그냥 MSWComponets를 넣어주면 된다
- handler.ts
import { http, HttpResponse } from "msw"; import { PopupBasic, createDummyPopups } from "./dummy"; const allPopups = createDummyPopups(20); http.get("/api/popups/:popupsId", async ({ params }) => { try { const popupsId = parseInt(params.popupsId as string, 10); const popup = allPopups.find((p) => p.id === popupsId); if (popup) { return HttpResponse.json(popup, { status: 200 }); } else { return new HttpResponse(null, { status: 404 }); } } catch (error) { return new HttpResponse(null, { status: 500 }); } }),
핸들러 즉 내 API 응답을 주는 핸들러를 만드는 것이다
HttpResponse.json
를통해 응답을 json 형식으로 받습니다.
괄호안에 첫번째에 들어갈 것들은 목데이터
즉 리스폰스로 올 데이터
들 입니다
{
id: index + 1,
ownerId: Math.floor(Math.random() * 100) + 1,
title: `팝업이 팝업스토어 ${index + 1}`,
description: `팝업이와 함께하는 팝업스토어 ${index + 1}`,
startDate: "2024-09-01T18:55:36.052679",
endDate: "2024-09-01T19:55:36.052725",
image: "/images/luffi.jpg",
}
이런식으로 하나의 객체를 넣을 수도 있지만 저는 dummy
라는 파일에 더미데이터를 랜덤으로 생성하는 파일을 하나 만들어 만드는 함수를 createDummyPopups
이라하고 거기서 가져와 쓰고 있습니다
괄호의 두번째에는 응답의 상태 200 , 404 , 500 등 여러가지를 넣어 만들수있습니다
생성방법은
- 생성방법
import { http, HttpResponse } from 'msw' export const handlers = [ // GET 요청 처리 http.get('/api/users', () => { return HttpResponse.json([ { id: 1, name: '김철수' }, { id: 2, name: '이영희' }, ]) }), // POST 요청 처리 http.post('/api/users', async ({ request }) => { const newUser = await request.json() return HttpResponse.json({ id: 3, ...newUser }, { status: 201 }) }), // PUT 요청 처리 http.put('/api/users/:id', async ({ params, request }) => { const updatedUser = await request.json() return HttpResponse.json({ id: params.id, ...updatedUser }) }), // DELETE 요청 처리 http.delete('/api/users/:id', ({ params }) => { return new HttpResponse(null, { status: 204 }) }), // 에러 응답 처리 http.get('/api/error', () => { return HttpResponse.json( { message: '서버 에러가 발생했습니다.' }, { status: 500 } ) }), // 쿼리 파라미터 처리 http.get('/api/search', ({ request }) => { const url = new URL(request.url) const query = url.searchParams.get('q') return HttpResponse.json({ results: [`${query}에 대한 검색 결과입니다.`] }) }), ]
위와 같은 방법으로 사용하면 됩니다 .
겪은 트러블 슈팅
Next.js와 MSW 통합 시 트러블슈팅
문제 상황
- MSW 서비스 워커를 사용하여 API 요청을 모킹하려 했으나, 서비스 워커가 실행 중임에도 404 또는 500 에러가 발생함.
- 간헐적으로 성공하는 경우도 있었음.
원인 분석
- Next.js의 서버 구조: Next.js는 Node.js 서버뿐만 아니라 자체적인 서버 구현을 가지고 있음. 이는 일반적인
클라이언트 사이드 서비스 워커
의 동작 방식과 충돌할 수 있음. - 서비스 워커의 범위: 브라우저에서 실행되는 서비스 워커는 클라이언트 사이드 요청만 가로챌 수 있음. Next.js의 서버 사이드 렌더링(SSR) 중 발생하는 요청은 서비스 워커의 범위 밖일 수 있음.
- 라우팅 충돌: Next.js의 라우팅 시스템과 MSW의 요청 가로채기가 충돌할 수 있음. 특히 API 라우트를 사용하는 경우 이런 문제가 발생할 가능성이 높음.
- 환경 차이: 개발 서버, 프로덕션 빌드, 정적 내보내기 등 Next.js의 다양한 실행 모드에 따라 MSW의 동작이 달라질 수 있음.
해결 방안: Express 서버 사용
Express 서버를 사용하여 MSW를 구현하는 방식은 다음과 같은 이점이 있습니다.
- 일관성: 클라이언트와 서버 모두에서 동일한 모킹 로직을 사용할 수 있음.
- 제어력: 요청 처리 과정을 더 세밀하게 제어할 수 있음.
- 디버깅: 서버 사이드에서 발생하는 문제를 더 쉽게 추적하고 해결할 수 있음.
- 확장성: 필요에 따라 추가적인 미들웨어나 로직을 쉽게 통합할 수 있음.
구현 단계
- Express 서버 설정
- MSW 핸들러를 Express 미들웨어로 변환
- Next.js 설정에서 프록시 설정 (필요한 경우)
- 개발 환경에서 Express 서버 실행
주의사항
- 이 방식은 개발 환경에서만 사용하고, 프로덕션 환경에서는 실제 API를 사용하도록 설정해야 함. (enable 설정으로 대채가능)
루트레이아웃의 클라이언트 컴포넌트화
여러 자료를 검색하고 지피티에게 물어보면 보통 루트에이아웃에 useEffect를 통하여 MSW를 돌아가게 해야한다고 한다
하지만 위에서 말했듯이 루트레이아웃이 클라이언트 컴포넌트가 된다면 전체적인환경에서 클라이언트컴포넌트가 최상단으로 감싸지지않을까? 하는 생각에 해당파일을 분리하고 import 하는 방식을 채택했다.
'NextJS' 카테고리의 다른 글
[Next.js]next 프로젝트 도커 빌드하기(2) (0) | 2024.11.11 |
---|---|
[Next.js]next 프로젝트에 도커 적용기 (0) | 2024.11.11 |
[Next.js]Nextjs 프로젝트 도커로 빌드하기 (0) | 2024.11.11 |
[Next.js] next-cookies(서버/클라이언트 쿠키 사용기) (0) | 2024.11.11 |
[Next.js] ReactQuery 적용기 (1) | 2024.11.11 |