Storybook 8 부터 React Server Component (RSC)를 지원하게 되었습니다.
이번 글에서는 Next.js 14 기반의 RSC를 Storybook을 통해 테스트하는 방법에 대해 알아보겠습니다.
들어가기전에
RSC가 뭔가요?
RSC는 서버에서 렌더링되는 컴포넌트로, 클라이언트에서 렌더링되는 컴포넌트와는 다르게 서버에서 렌더링되어 클라이언트로 전달됩니다. 이렇게 들으면 SSR과 차이가 없어보입니다.
SSR은 초기 페이지 로드에 중점을 두고 있습니다. 사용자는 HTML을 빨리 얻을 수 있지만, 앱과 바로 상호작용할 수는 없습니다.
- 컴포넌트가 표시되기 전에, 서버에서 페이지 전체의 데이터를 가져오기까지 기다려야합니다. 이를 방지하기 위해 일부 데이터를
client-side
에서useEffect()
Hooks를 통해 가져오는 것인데, 이는 Round-Trip이 길며, 컴포넌트가 렌더링되고Hydrate
이후에 일어납니다. 이는 긴Waterfall
을 유발합니다. - 브라우저에서 완성된 HTML과 JS를 받게되면
React.createElement
를 통해 DOM을 다시 렌더링하고, 이벤트 리스너를 연결하는 Hydration 과정이 필요합니다.
SSR은 SEO 최적화, FCP(Fist Contentful Paint)를 개선하는데 도움이 되었지만, 결국 클라이언트 환경에서 많은 작업의 수행을 필요로 했습니다.
반면, RSC는 Hydration 과정이 없어집니다. 서버에서 완전히 렌더링한 후 React Server Component Payload (RSC Payload)라는 특수 데이터 형식으로 클라이언트에 전달됩니다.
- Server Component의 코드는 클라이언트에 전달되지 않습니다. 반면 SSR은 JS 번들을 통해 클라이언트에 전달됩니다.
- Server Component는 서버에 직접 접근(Database, FileSystem, 내부 서비스)할 수 있습니다.
- 결과, 다운로드할 JS Bundle도 없고, 클라이언트측 리렌더링을 요구하지 않으므로 성능이 향상됩니다.
- 단, 서버 컴포넌트는 정적인 처리만 가능합니다. 즉
useState
,useEffect
와 같은 상태,DOM Web API
를 사용할 수 없습니다.
RSC Payload가 뭔가요?
React Server Component Tree의 압축된 바이너리 표현입니다. 이는 클라이언트의 React에서 DOM을 업데이트하는데 사용됩니다. RSC Payload는 다음이 포함되어 있습니다.
- Server Compoennt의 렌더 결과
- Client Component를 렌더링해야하는 위치, JS 파일 참조를 위한 Placeholder
- Server Component에서 Client Component로 전달되는 Props
실제 개발시에는 서버 컴포넌트가 필요한 영역과 클라이언트 컴포넌트가 필요한 영역을 구분하는 설계 작업이 필요합니다.
서버 컴포넌트는 하위로 클라이언트 컴포넌트를 포함시킬 수 있습니다.
state
변경이나 DOM Web API
가 필요한 영역에 서버 컴포넌트의 결과를 클라이언트 컴포넌트로 props
를 통해 전달하여 사용할 수 있습니다.
Next.js 에서는 app router
로 변경되며 RSC의 사용성이 개선되었습니다.
기존엔 페이지의 최상단에 getServerSideProps()
를 선언하여 props로 전달해야만 했지만, 지금은 컴포넌트를 비동기 async
로 선언하면 됩니다.
자세한 사용 방법은 공식 문서를 참고하세요.
왜 Storybook으로 인터랙션을 테스트하나요?
Jest나 Playwright 등을 통해 컴포넌트가 출력된 값이 예상대로 나오는지 테스트할 수 있습니다. 하지만 위 방식만으론 UI 버그를 예방하긴 충분하지 않습니다.
사용자의 인터랙션은 테스트할 수 있지만, Node.js 환경에서만 동작하기에 시각적 테스트를 할 수는 없습니다.
반면 스토리북은 컴포넌트 단위로 UI를 테스트할 수 있으며, 브라우저 환경에서 동작하기에 시각적 테스트도 가능합니다. 또한, 사용자의 인터랙션을 자동으로 실행하는 테스트 시나리오를 작성할 수 있습니다.
이 글을 통해 스토리북의 Next.js 14 내의 환경 구성 및 RSC를 테스트하는 방법에 대해 알아보겠습니다.
Storybook 환경 구성
먼저, Next.js 프로젝트에 Storybook을 추가합니다.
이 후 Storybook의 main.ts에 새로운 RSC 기능을 사용하기 위한 설정을 추가합니다.
또한, tsconfig의 paths
alias을 Storybook에서도 사용할 수 있도록 설정합니다.
이제 Stories를 작성하여 RSC를 테스트할 수 있습니다. 이 블로그의 페이지는 RSC로 구성되어 있습니다. 그러므로 PostPage
컴포넌트를 예시로 들겠습니다.
RSC가 작동하는 것은 확인했지만, 스타일과 필요한 Provider가 누락되어있음을 확인할 수 있습니다.
./storybook/preview.tsx
의 decorators
를 통해 필요한 Provider를 추가할 수 있습니다.
- globals.css에서 tailwindcss 설정을 가져옵니다.
- Next.js의 localfont는 사용할 수 없으니, font-face 를 따로 생성하였습니다.
tailwind.config.ts
에서 content 경로를 추가합니다.
이제 원하던 화면이 출력되는 것을 확인할 수 있습니다.
MSW를 통한 API Mocking
MSW를 활용하여 데이터를 제어하며 다양한 상태를 테스트해볼 수 있습니다.
Next.js의 App Dir는 현재 MSW와 호환되지 않기에, 다소 복잡한 과정을 필요로합니다.
하지만 Storybook은 브라우저 환경에서 실행되기 때문에, Next.js 프로젝트더라도 문제없이 사용할 수 있습니다.
먼저, MSW와 msw-storybook-addon을 설치합니다. msw-storybook-addon이 MSW의 2.0.0 까지만 지원하기에, 그 이상은 설치할 경우 에러가 발생합니다.
Issue#121이 해결되기 전까진 아래 버전을 지켜주세요.
설치가 무사히 진행되었다면, public/mockServiceWorker.js
파일이 생성됨과, .storybook/main.ts
의 addons에 msw-storybook-addon
가 추가되어있음을 확인할 수 있습니다.
다음, .storybook/preview.tsx
에 MSW 초기화 및 onUnhandledRequest
옵션을 추가합니다.
이제 MSW를 통해 API 요청을 제어할 수 있습니다. 이 블로그의 링크 카드는 OpenGraph API를 통해 데이터를 가져오고 있어서, 이를 테스트해보겠습니다.
먼저, API Mocking을 위한 src/mocks/handlers.ts
를 작성합니다.
이제, stories 파일에 handler를 등록하여 API를 인터셉트할 수 있습니다.
이 상태에서 결과를 확인하면 Mocking된 데이터로 응답받은 것을 확인할 수 있습니다.
컴포넌트 테스트
테스트를 할 수 있는 환경이 다 갖춰졌습니다. 이제 Storybook의 play
함수를 사용하여 컴포넌트의 상태를 변경하며 테스트를 진행할 수 있습니다.
사용자의 상호작용이나, 스토리가 렌더링된 후 expect
를 통해 렌더링된 결과를 확인할 수 있습니다.
마치며
곧 다가올 React v19와 RSC의 대중화로, RSC를 테스트하는 방법에 대한 관심이 높아질 것으로 예상됩니다.
그에 대비하여 RSC를 Storybook을 통해 테스트하는 방법에 대해 알아보았습니다.
Storybook은 Cypress
나 playwright
가 Node.js
에서 동작하는 것과 달리, 브라우저 환경에서 동작하기에 시각적으로 테스트를 할 수 있었습니다.
이는 요즘 복잡해지는 UI를 테스트하는데도 유용하고, 결과를 디자이너나 퍼블리셔에게 보여주기에도 좋은 도구입니다.
또한 사용자의 인터렉션을 테스트 할 때 개발자나 디자이너가 웹에 접속하여 하나씩 클릭해볼 필요가 없어지고,
play
를 작성하여 자동화된 테스트를 진행한다면 더 효과적인 테스트를 할 수 있을 것입니다.
참조