Next.js app directory의 서버 컴포넌트에서 msw 사용하기

순민·2024년 1월 4일
2
post-thumbnail

본 글에선 MSW에 대한 구체적인 설명이나 기본적인 설정 방법에 대해서는 다루지 않습니다.

시작하며

아직까지(2024.01.04) Next.js 13에서 새롭게 추가된 App Directory 기능을 MSW가 지원하지 않고 있는 상황입니다.(깃헙 이슈)
그 이유는 Node.js에서 MSW가 작동하기 위해서는 전역 Node 모듈(http, https 등)을 패치해야 하는데, Next.js의 현재 프로세스 구조상 전역적인 곳에서 패치를 하는것이 아니라 개별 레이아웃마다 패치하기 때문에 어렵다고 합니다.

그럼 App Directory에서는 node환경에서 msw를 사용하지 못하는 걸까요?
아닙니다. next프로젝트와 상관이 없는 독립적인 서버를 구축하고 next의 서버사이드측에서 api요청을 보낼때 middleware를 사용하여 요청을 가로채면 위 문제가 해결이 됩니다.

물론 임시적인 해결책이고 Next.js에서 정상적으로 지원을 해주면 방식을 바꿔야합니다.

문제의 코드

보통 node와 browser환경에서 msw설정을 하기 위해서 다음과 같은 코드를 작성했을 것입니다.

export async function init() {
  if(typeof window === 'undefined') {
    const { server } = await import('./server');
    server.listen();
  } else {
    const { worker } = await import('./browser');
    worker.start();
  }
}

위와 같은 방식을 server component에서 사용하게 되면, data fetching이 msw가 활성화되는 시점보다 먼저 발생해서 msw가 동작하지 않습니다.

서버사이드에서 정상적으로 msw동작시키기

독립적인 서버(express)를 띄워서 서버사이드에서 일어나는 데이터 패칭 로직을 가로채는 과정을 살펴보겠습니다.

express 서버 구축

@mswjs/http-middlewareexpress 를 의존성에 추가를 해주고 코드를 다음과 같이 작성해줍니다.

// src/mocks/http.ts
import express from 'express'
import { createMiddleware } from '@mswjs/http-middleware'
import { handlers } from './handlers'

const app = express()
const port = 9090

app.use(express.json())
app.use(createMiddleware(...handlers))

app.listen(port, () => console.log(`Mock server is running on port: ${port}`))

참고자료 : https://github.com/mswjs/msw/issues/1644#issuecomment-1750722052

pacakge.json에 express server실행을 위한 script를 다음과 같이 정의를 해줍니다.

"scripts": {
	"mock": "npx tsx watch ./src/mocks/http.ts",
    "dev": "next dev"
}

next의 dev서버를 실행시킬때 mock server실행을 위한 mock script도 같이 실행을 시켜주면 됩니다.

위 코드 구성을 완료했으면 msw에서 정의한 handler를 server component에서 사용할 수 있게 됩니다.

동작과정

// server component 예시
async function getUser() {
  const response = await fetch('http://localhost:9090/user')
  const json = await response.json()

  return json
}

const UserPage = async () => {
  const user = await getUser()
  console.log('user', user)
  return <div></div>
}

export default UserPage
  1. 위의 server component에서 http요청을 생성하여 http://localhost:9090/user에 보냅니다.
  2. express서버에서 http://localhost:9090에 대한 요청을 수신합니다.
  3. @mswjs/http-middleware 라이브러리로 정의된 msw 핸들러가 이 요청을 가로채고 처리합니다. msw는 설정된 핸들러를 통해 요청을 확인하고 mock 데이터를 사용하여 응답을 생성합니다.
  4. msw는 설정된 핸들러(handlers)에서 정의된 mock 데이터 또는 응답을 사용하여 요청에 대한 가짜 응답을 생성합니다. 이 mock 응답이 express 서버로 반환됩니다.
  5. express 서버는 msw로부터 받은 mock 응답을 클라이언트에 반환합니다.

baseURL의 문제점

하지만, 실제 코드에 적용하기엔 약간 부족한 점이 보입니다.
보통 프론트 개발서버에서는 개발 api서버(https://dev.api/user)를 호출하고 있을 것이고 baseUrl로 설정이 되어있을 것입니다.

next 개발서버를 실행시킬때 mock api서버를 띄워야하는지 판단해주는 환경변수를 주입해서 baseUrl을 localhost로 설정할지에 대한 판단을 해주면 됩니다.

매번 next dev server서버와 express서버를 실행시키기는 귀찮으므로 concurrently라는 라이브러리를 활용해서 next dev server와 express서버를 한 번에 실행을 시키고 next프로젝트에 NEXT_PUBLIC_API_MOCKING이라는 환경변수를 주입해줍니다.

"scripts": {
	"dev:mock": "concurrently --kill-others \"NEXT_PUBLIC_API_MOCKING=enable next dev\" \"npx tsx watch ./src/mocks/http.ts\"",
    //...
}

그리고 프로젝트에선 다음과 같이 설정해주면 됩니다.

const baseUrl = process.env.NEXT_PUBLIC_API_MOCKING === 'enable' ? 'http://localhost:9090' : 환경별baseURL판단로직

마치며

생각보다 간편한 방법으로 App Directory에서 msw를 사용할 수 있지만, express서버를 별도로 띄워야 하므로 불편한 느낌이 조금은 있습니다.
msw측에서 next의 instrumentation을 이용하여 해결하는 방법을 제시했는데 빨리 지원이 됐으면 좋겠습니다.
(관련 pr 입니다. https://github.com/mswjs/examples-new/pull/7)
혹시 다른 좋은 방법 있으시면 의견 부탁드립니다!

profile
개발을 재밌게!

0개의 댓글