본문 바로가기
SPA/Next

[Next] Next13 이후로 MSW 사용하기 (1) - Browser 환경 설정

by J4J 2024. 1. 5.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 next13 이후로 msw 사용하기 첫 번째인 browser 환경 설정하는 방법에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

관련 글

 

[React] MSW로 API Mocking 하기 (1) - MSW란?

[React] MSW로 API Mocking 하기 (2) - Browser 환경에서 사용하기

 

 

반응형

 

 

들어가기에 앞서

 

들어가기에 앞서 msw가 무엇이고 msw에서 말하는 browser 환경이 무엇인지는 위의 관련 글 링크를 참고해 주시면 됩니다.

 

해당 글에서는 next에서 msw 사용을 위한 설정 방법에 대해서만 다루도록 하겠습니다.

 

 

 

그리고 next에서는 SSR, SSG 등의 데이터 fetching과 next 13 이후로 등장한 RSC와 같이 서버 단에서 발생되는 API mocking에 대해서도 생각해봐야 합니다.

 

그래서 msw 설정을 할 때 browser 환경을 목적으로 구성하더라도 server 환경인 node 설정에 대해서도 함께 고려를 해야 합니다.

 

하지만 아직까지 서버 단에서 msw를 이용한 API mocking이 정상적으로 수행 되지 않고 있습니다.

 

결과적으로 현재 기준으로는 browser 환경을 설정할 때 server 환경 설정이 필요 없는 것으로 판단되어 서버 단에서 발생되는 API mocking에 대한 고려 없이 설정해 보겠습니다.

 

 

 

 

Browser 환경 설정 방법

 

[ 1. public directory에 msw 초기 설정 ]

 

$ npx msw init {public directory} --save // ex) npx msw init public/ --save

 

 

 

명령어를 이용하여 msw 초기 설정을 수행하면 다음과 같은 변경점들을 확인할 수 있습니다.

 

먼저 프로젝트 public 경로에 다음과 같이 mockServiceWorker 파일이 생성됩니다.

 

해당 파일에는 msw 동작을 위한 다양한 기능들이 담겨 있고 해당 파일이 실행되어야 msw를 올바르게 사용할 수 있습니다.

 

mockServiceWorker 생성

 

 

 

다음은 package.json입니다.

 

package.json에서 다음과 같이 msw의 workerDirectory가 설정되는 것을 볼 수 있습니다.

 

// package.json
{
  "msw": {
    "workerDirectory": "public"
  }
}

 

 

 

 

[ 2. 패키지 설치 ]

 

현재 기준으로 msw는 2 버전이 출시되었습니다.

 

다만 개인적으로 느꼈을 때 아직까지 stable 한지 체감하지 못하고 있어서 1.3.2 버전으로 대체하도록 하겠습니다.

 

$ npm install -D msw@1.3.2

 

 

 

[ 3. handler 설정 ]

 

handler는 특정 API를 호출하게 될 경우 어떤 방식으로 mocking을 수행할지에 대해 정의합니다.

 

API를 호출했을 때 엔드포인트는 무엇이고 응답 데이터 및 응답 코드는 무엇인지에 대해 다음과 같이 작성해 주시면 됩니다.

 

// src/mocks/handlers/person-handler.ts
import { rest } from 'msw';

export const personHandlers = [
    // GET method인 /get-names를 호출하면 다음과 같이 response가 되도록 mocking 정의
    rest.get('/get-names', (req, res, ctx) => {
        const names = ['jimi', 'john', 'scott', 'queen'];
        return res(ctx.status(200), ctx.json(names));
    }),
];


// src/mocks/handlers/index.ts
import { personHandlers } from './person-handler';

// 여러 handler를 한 곳에 묶어서 return
export const handlers = [...personHandlers];
 

 

 

[ 4. worker 설정 ]

 

browser 환경에서 msw 동작이 수행되도록 하기 위해서는 worker를 등록해줘야 합니다.

 

worker는 다음과 같이 작성해 주시면 됩니다.

 

// src/mocks/browser.ts
import { setupWorker } from 'msw';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);

 

 

 

 

[ 5. provider 설정 ]

 

next13 이후로 app directory를 많이 사용하고 있을 것으로 예상됩니다.

 

그러다 보니 위에서 설정한 worker를 동작시키는 컴포넌트는 RSC가 아닌 RCC가 되어야 해서 wrapper 목적의 파일을 하나 생성하도록 하겠습니다.

 

다음과 같이 provider를 생성하고 root layout에 덮어주시면 됩니다.

 

// src/app/(provider)/MswProvider.tsx
'use client';

import { ReactNode, useEffect, useState } from 'react';

interface Props {
    children: ReactNode;
}

export default function MswProvider({ children }: Props) {
    const [isInit, setIsInit] = useState<boolean>(false);

    /**
     * msw 환경 설정
     */
    const configMsw = async () => {
        // next dev를 이용하여 개발 환경에서 동작했을 때만 msw 동작
        if (process.env.NODE_ENV === 'development') {
            const { worker } = await import('@/mocks/browser');
            await worker.start();
        }
    };

    useEffect(() => {
        // msw 초기화가 수행되지 않은 경우 msw 초기화 설정 수행
        // 해당 설정을 하지 않고 바로 msw가 사용되는 페이지에 접근할 경우 msw 설정이 되기 전 API가 호출되어 mocking이 올바르게 수행되지 않음
        if (!isInit) {
            (async () => {
                await configMsw();
                setIsInit(true);
            })();
        }
    }, [isInit]);

    // msw 초기화가 되지 않은 경우 자식 컴포넌트를 반환하지 않도록 함
    // 해당 설정을 하지 않고 바로 msw가 사용되는 페이지에 접근할 경우 msw 설정이 되기 전 API가 호출되어 mocking이 올바르게 수행되지 않음
    // 이 방식을 사용할 경우 발생될 수 있는 문제점은 msw 설정은 CSR로 동작되고 그 이후 컴포넌트들이 보이기 때문에 "SEO를 위해서는 적합한 방식이 아님"
    if (!isInit) {
        return null;
    }

    return <>{children}</>;
}


// src/app/layout.tsx
import MswProvider from './(provider)/MswProvider';

export default function RootLayout({ children }: { children: ReactNode }) {
    return (
        <html lang="en">
            <body className={inter.className}>
                <MswProvider>{children}</MswProvider>
            </body>
        </html>
    );
}

 

 

 

 

여기서 한 가지 주의하셔야 될 점은 SEO 부분입니다.

 

위의 코드를 확인해 보면 msw가 설정이 완료될 때까지 자식 컴포넌트들을 return 하지 않는 것을 볼 수 있습니다.

 

msw 설정은 client에서 렌더링 되는 부분이기 때문에 client에서 렌더링 되기 전에 html 파일들을 크롤링하는 크롤링 봇들이 확인하는 html 파일들에는 어떠한 정보도 담기지 않게 됩니다.

 

즉, html 파일에서 확인되는 정보가 없어서 SEO에 불리한 상황이 발생될 것으로 예상하니 SEO를 중요하게 여기는 서비스들은 다음과 같이 MswProvider를 변경해주셔야 합니다.

 

// src/app/(provider)/MswProvider.tsx
'use client';

import { ReactNode, useEffect } from 'react';

interface Props {
    children: ReactNode;
}

export default function MswProvider({ children }: Props) {
    /**
     * msw 환경 설정
     */
    const configMsw = async () => {
        // next dev를 이용하여 개발 환경에서 동작했을 때만 msw 동작
        if (process.env.NODE_ENV === 'development') {
            const { worker } = await import('@/mocks/browser');
            await worker.start();
        }
    };

    useEffect(() => {
        configMsw();
    }, []);

    return <>{children}</>;
}

 

 

 

 

위와 같이 설정했을 때 발생될 수 있는 문제점은 msw를 이용하여 API mocking이 되는 페이지에 바로 접근했을 때 다음과 같이 에러가 발생된다는 것입니다.

 

msw API mocking issue

 

 

 

이 부분은 다른 페이지로 넘어갔다가 다시 해당 페이지로 돌아오면 msw가 올바르게 동작되어 정상적으로 데이터가 보이는 것을 확인할 수 있습니다.

 

이런 상황은 불편한 요소로 남을 수 있는 부분이지만 아직까지 SEO를 고려하면서 올바르게 적용할 수 있는 방법이 있는지 확인이 안 되고 있어 더 높게 보는 가치가 무엇인지를 생각하며 설정해야 할 것 같습니다.

 

 

 

 

테스트

 

모든 설정이 올바르게 되었다면 테스트를 해보겠습니다.

 

다음과 같이 handler에 등록한 API를 호출하는 코드를 작성하고 서버를 실행하면 handler에 등록한 대로 올바르게 데이터가 넘어오는 것을 확인할 수 있습니다.

 

// src/app/page.tsx
'use client';

import { useEffect, useState } from 'react';

export default function RootPage() {
    const [names, setNames] = useState<string[]>([]);

    const apiGetNames = async () => {
        const res = await fetch('/get-names');
        if (res) {
            setNames(await res.json());
        }
    };

    useEffect(() => {
        apiGetNames();
    }, []);

    return (
        <main className="m-4">
            <h2 className="text-2xl font-bold">Browser Env Names ...</h2>

            <div>
                <ul>
                    {names.map((name) => (
                        <li key={name}>{name}</li>
                    ))}
                </ul>
            </div>
        </main>
    );
}

 

msw API mocking 확인

 

 

 

 

 

 

 

 

이상으로 next13 이후로 msw 사용하기 첫 번째인 browser 환경 설정에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글