storybookjs/storybook · 라이선스 MIT · 개발 버전 10.6.0-alpha(최신 안정판 10.5.x 계열) · 코어=TypeScript(Node) · UI=React 기반 매니저/프리뷰 · Yarn Berry 워크스페이스 모노레포(code/ 아래 addons·builders·frameworks·renderers·presets·lib 55개 패키지) · 소스 .ts/.tsx 약 2,500개 · 최신 커밋 2026-07-29)
일반적인 웹 개발에서 버튼 컴포넌트 하나를 확인하려면, 로그인하고 여러 페이지를 클릭해 그 버튼이 나오는 화면까지 가야 하는 경우가 많다. Storybook은 그 과정을 통째로 생략한다. 컴포넌트 옆에 Button.stories.tsx 같은 파일을 하나 두고 "기본 상태", "비활성 상태", "로딩 상태"를 각각 함수(스토리)로 적어두면, Storybook이 이를 스캔해 사이드바 목록으로 보여주고 클릭 한 번으로 그 상태만 골라 렌더링한다.
이 스토리 파일 하나가 세 가지 역할을 동시에 한다. ① 개발 중 눈으로 확인하는 미리보기, ② 자동화된 테스트 케이스(클릭·입력 같은 상호작용, 접근성 검사, 시각적 비교), ③ 팀이 공유하는 살아있는 문서(컴포넌트에 어떤 props가 있고 어떻게 조작되는지). 파일 하나를 세 번 안 써도 된다는 것이 핵심 가치다.
보통 앱을 실행해 컴포넌트를 확인하는 건 자동차를 통째로 조립해 시운전하는 것과 같다 — 헤드라이트 하나 확인하려고 엔진·타이어·문짝까지 다 붙여야 한다.
Storybook은 부품(컴포넌트) 하나만 작업대에 올려 전원을 넣고, 여러 조건(밝기·색·고장 상태)으로 바꿔가며 확인하게 해준다. 차를 다 조립하지 않아도 헤드라이트 자체를 완벽하게 다듬을 수 있다.
Storybook은 2016년부터 존재해온 이미 검증된 표준 도구다. "요즘 막 뜬 신생 프로젝트"는 아니지만, 최근 몇 개의 메이저 릴리스(9.0, 10.0)를 거치며 세 방향으로 크게 방향을 틀었고, 그 변화가 지금 다시 화제가 되는 이유다.
과거 Storybook은 보는 용도(문서·미리보기)가 중심이었다. 하지만 9.0부터는 상호작용 테스트·접근성 검사·시각적 회귀·커버리지가 정식 축으로 들어왔고, 그 실행 엔진으로 Vitest를 그대로 쓴다. 이미 작성해둔 스토리 파일이 브라우저 환경에서 바로 유닛 테스트처럼 실행되는 셈이라, "테스트를 위해 또 다른 코드를 짜야 하는" 이중 작업이 크게 줄었다.
Storybook은 오랫동안 "설치하면 무겁고 느려진다"는 평판을 들어왔다. 9.0 릴리스 노트는 스스로 "48% 더 가벼운 번들"을 핵심 성과로 내세웠고, 흩어져 있던 @storybook/addon-actions·addon-backgrounds·addon-controls·addon-viewport 등 여러 애드온을 코어 패키지 하나로 흡수·통합했다(자세한 목록은 4번 섹션). 설치할 패키지 개수 자체가 줄어든 것이다.
10.5 릴리스부터 storybook ai CLI 명령, Claude/Codex용 플러그인, "에이전틱 리뷰(agentic review)" 같은 실험적 기능이 들어왔다. AI 코딩 에이전트가 컴포넌트를 수정했을 때 시각적으로 무엇이 바뀌었는지 자동으로 요약해주는 흐름을 만드는 중이다. "AI가 UI 코드를 짜는 시대에, 그 결과를 사람이 검수하는 도구"로 스스로를 다시 자리매김하는 셈이다.
Ladle·Histoire 같은 후발주자들은 "Storybook보다 가볍고 빠르다"를 내세우며 등장했다(자세한 비교는 10번 섹션). Storybook의 대응은 애드온 통합으로 무게를 줄이는 동시에, 에코시스템 크기(프레임워크 지원 범위·애드온 생태계·Chromatic 같은 상용 연계)로 격차를 벌리는 쪽이다. "빠른 신생 도구 vs 넓고 검증된 표준"이라는 구도가 계속되고 있다.
저장소 전체는 code/ 폴더 아래에 렌더러(renderer)·빌더(builder)·프레임워크(framework)·애드온(addon)이라는 네 가지 역할로 패키지가 잘게 쪼개진 모노레포다. 이 네 단어를 먼저 구분해두면 나머지 구조가 훨씬 쉽게 읽힌다.
| 역할 | 하는 일 · 예시 |
|---|---|
| 렌더러(Renderer) | 컴포넌트를 실제 DOM에 "붙이는(mount)" 역할. @storybook/react·@storybook/vue3·@storybook/svelte 등 code/renderers/* |
| 빌더(Builder) | 소스를 번들링해 브라우저에서 돌게 만드는 역할. @storybook/builder-vite·@storybook/builder-webpack5 — code/builders/* |
| 프레임워크(Framework) | 렌더러+빌더+프레임워크별 설정을 한 번에 묶은 완성 패키지. @storybook/react-vite·@storybook/nextjs·@storybook/angular-vite 등 14종 — code/frameworks/* |
| 애드온(Addon) | 선택적으로 켜는 확장 기능. 지금은 a11y·docs·links·onboarding·pseudo-states·themes·vitest 7종만 남음 — code/addons/* |
| 코어(core) | 패키지명 storybook(npm에 이 이름으로 배포). 매니저·프리뷰·채널·CSF 인덱서·CLI가 전부 여기(code/core/src/) 안에 있음 |
| lib | 독립 배포되는 보조 도구. create-storybook(설치 CLI)·eslint-plugin-storybook·codemod 등 9종 — code/lib/* |
애드온 목록이 7개뿐이라는 게 눈에 띈다. 예전에는 addon-essentials라는 묶음 패키지 안에 액션·컨트롤·뷰포트·백그라운드·상호작용 애드온이 따로 들어 있었지만, 9.0부터 이 기능들이 전부 코어 패키지(storybook) 안으로 흡수됐다. 그래서 지금은 별도 설치 없이 기본으로 딸려온다 — "애드온이 줄었다"기보다 "많은 애드온이 코어로 승격됐다"가 정확한 표현이다.
| 요소 | 무엇 / 왜 |
|---|---|
| 프레임워크 | React 기반. 상태는 manager-api의 리듀서·컨텍스트 조합으로 관리(별도 상태 라이브러리 없이 자체 구현) |
| 스타일 | storybook/theming — emotion 기반 자체 테마 시스템. 다크/라이트 테마 전환 지원 |
| UI 컴포넌트 | storybook/internal/components — 사이드바·툴바·패널 등 자체 디자인 시스템 |
| 요소 | 무엇 / 왜 |
|---|---|
| 실행 엔진 | preview-api — 스토리 준비(prepare)·렌더링·훅(loaders·decorators) 파이프라인 |
| CSF 파싱 | csf-tools — Babel AST로 스토리 파일을 정적으로 분석(코드를 실행하지 않고 목록만 뽑아냄) |
| 테스트 | storybook/test(구 @storybook/test) — Testing Library + Vitest 기반 expect/userEvent 재노출 |
| 도구 | 역할 |
|---|---|
| Vite / Webpack 5 | 실제 번들러 둘. Storybook은 자체 번들러가 없고 이 둘을 추상화된 Builder 인터페이스 뒤로 감싸 사용(4번 섹션에서 자세히) |
| Vitest | 단위 테스트 + 스토리 기반 컴포넌트 테스트를 브라우저(@vitest/browser) 모드로 실행 |
| Playwright | E2E 테스트 및 Vitest 브라우저 모드의 실제 브라우저 구동 |
NX + yarn task | 55개 워크스페이스 패키지의 빌드 순서·캐시를 관리하는 자체 태스크 러너 |
| oxlint / oxfmt | Rust로 만들어진 초고속 린터·포매터. 최근 ESLint를 대체해 저장소 자체 린팅에 사용 |
export default(메타 정보)와 여러 개의 named export(각 스토리)로 구성된 평범한 JS/TS 모듈이다. 특별한 런타임 없이 정적 분석만으로도 목록을 뽑아낼 수 있게 설계됐다.Storybook을 열었을 때 브라우저 탭에는 하나의 웹페이지처럼 보이지만, 실제로는 두 개의 완전히 독립된 자바스크립트 실행 환경이 동시에 돌고 있다. 이 분리가 Storybook 아키텍처 전체를 관통하는 가장 중요한 설계 결정이다.
프리뷰가 매니저와 같은 창(window)에서 돌았다면, 개발 중인 컴포넌트가 무한 루프에 빠지거나 전역 CSS를 오염시킬 때 Storybook 앱 전체가 함께 죽는다. iframe으로 분리해두면 프리뷰가 아무리 망가져도 매니저(사이드바·툴바)는 살아있다 — 사용자는 다른 스토리를 클릭해 바로 복구할 수 있다. "손님이 시운전하다 부품 하나를 고장 내도 매장 전체가 멈추지 않는" 구조다.
매니저는 "안내 데스크와 로비", 프리뷰는 "유리벽 너머 실험실"이라고 생각하면 쉽다. 로비 직원(매니저)은 실험실 안에서 무슨 실험(컴포넌트 렌더링)을 하는지 직접 손대지 않고, 유리벽에 붙은 인터폰(채널)으로만 "지금 이 실험을 틀어줘", "실험 끝났어" 같은 메시지를 주고받는다. 실험실 안에서 폭발이 나도 로비는 안전하다.
channels/main.ts의 Channel 클래스는 Node의 EventEmitter와 거의 같은 API(on·emit·once)를 갖고 있지만, 실제 메시지 전달은 Transport 객체에 위임한다. 브라우저 안에서는 postMessage 기반 Transport, 원격/Node 환경에서는 WebSocket 기반 Transport를 꽂아 넣는 식이다. 애드온을 만드는 개발자는 "지금 postMessage로 가는지 WebSocket으로 가는지" 전혀 몰라도 되고, channel.emit('내이벤트', data)만 호출하면 된다.
send(event)와 setHandler(handler) 두 메서드만 구현하면 되는 최소 인터페이스. Channel은 이 인터페이스만 알고 실제 통신 수단(postMessage·WebSocket 등)은 몰라도 되게 감춘다 — "봉투에 편지를 넣어 부치기만 하면, 우체국이 배·비행기·트럭 중 뭘 쓰는지는 몰라도 되는" 구조.
스토리가 수천 개인 대형 프로젝트에서 사이드바를 켤 때마다 모든 스토리 파일을 실제로 import해서 실행한다면 매우 느리고, 파일 하나가 에러를 던지면 전체가 멈춘다. 그래서 core-server/utils/StoryIndexGenerator.ts는 각 스토리 파일을 Babel로 파싱한 AST(추상 구문 트리)만 들여다보며 "이 파일에 어떤 export가 있는지"만 정적으로 추출한다. 실제 컴포넌트 렌더링은 사용자가 그 스토리를 클릭해 선택한 순간에만 일어난다.
한 가지 흐름을 끝까지 따라가 보면: ① 개발 서버가 뜨면 main.js의 stories 설정에 맞는 파일들을 glob으로 찾는다 → ② csf-tools가 각 파일을 Babel로 파싱해 메타(제목)와 각 named export(스토리) 이름을 추출한다 → ③ 그 결과를 index.json이라는 하나의 목록으로 합쳐 매니저에 전달한다 → ④ 매니저는 이 목록만으로 사이드바를 그린다(아직 실제 컴포넌트 코드는 하나도 실행 안 됨) → ⑤ 사용자가 스토리 하나를 클릭하면 그제서야 프리뷰 iframe이 해당 모듈을 실제로 import해 렌더링한다. 에러 처리나 MDX 문서 전용 인덱싱 같은 예외 경로는 여기서는 생략한다.
Storybook은 실제 번들링을 직접 하지 않는다. 대신 Builder<Config>라는 인터페이스(getConfig·start·build 세 메서드)를 정의해두고, builder-vite와 builder-webpack5가 각자 이 계약을 구현한다. 프레임워크 패키지(예: @storybook/react-vite)는 preset.ts에서 core.builder 값으로 어떤 빌더 패키지를 쓸지만 지정하면 되고, 나머지 Storybook 코드는 "빌더가 Vite인지 Webpack인지" 신경 쓰지 않는다.
core.builder·core.renderer·Vite/Webpack 설정 조각 등)을 미리 묶어둔 조립 함수 세트. 프레임워크 패키지 하나가 "렌더러+빌더+프레임워크별 설정"을 프리셋 하나로 감싸 제공하는 방식이다.초보자가 자주 헷갈리는 지점이다. 실제 파일 변환·트리 셰이킹·HMR은 전부 Vite/Webpack이 한다. Storybook 코어는 "어떤 파일을 진입점으로 볼지, 어떤 설정 조각을 더할지"를 결정하고 빌더에 넘겨주는 조립 역할만 한다.
그래서 빌드가 느리거나 이상하게 동작할 때, 원인은 Storybook 코드가 아니라 내가 쓰는 Vite/Webpack 설정 자체에 있는 경우가 많다.
구조가 주는 교훈은 "역할별로 폴더를 쪼개되, 코어는 하나로 뭉친다"는 균형이다. 예전에는 @storybook/manager-api·@storybook/preview-api·@storybook/theming이 전부 별도 npm 패키지였지만, 지금은 전부 code/core/src/ 하나의 패키지(storybook) 안에 서브 경로(storybook/manager-api 등)로 들어있다. 패키지 개수를 줄이면서도, 폴더 단위 책임 분리는 그대로 유지한 것이다.
channels/main.ts의 Channel 클래스는 100줄 남짓한 짧은 코드지만, Transport를 갈아끼우는 것만으로 postMessage든 WebSocket이든 같은 API로 통신하게 만드는 설계를 그대로 보여준다. iframe·워커·별도 프로세스 간 통신이 필요한 어떤 프로젝트에도 응용할 수 있는 패턴이다.
StoryIndexGenerator.ts와 csf-tools/CsfFile.ts는 Babel AST만으로 수천 개 파일의 메타데이터를 빠르게 추출하는 실전 코드다. "실행 없이 구조만 분석"하는 이 기법은 코드 생성기·린터·문서 자동화 도구를 만들 때 그대로 재사용할 수 있다.
Builder<Config, Stats> 인터페이스와 builder-vite/builder-webpack5 두 구현체를 나란히 읽으면, "완전히 다른 두 도구를 같은 계약으로 감싸는" 추상화 설계를 실물로 확인할 수 있다. 데이터베이스 드라이버·클라우드 프로바이더 어댑터 등 어디서나 쓰이는 패턴이다.
addons/vitest는 @vitest/browser를 이용해 스토리를 실제 브라우저에서 테스트로 실행하면서, 동시에 그 결과를 매니저 UI 패널에 실시간으로 보여준다. "테스트 실행기"와 "개발 중 피드백 UI"를 하나로 잇는 통합 사례를 볼 수 있다.
NX + 자체 yarn task 러너로 컴파일·린트·타입체크 순서를 관리하고, 최근에는 ESLint 대신 Rust 기반 oxlint/oxfmt로 갈아타 대규모 저장소의 린팅 속도를 끌어올렸다. 대형 오픈소스가 툴체인을 어떻게 진화시키는지 보여주는 좋은 참고 사례다.
Storybook은 순수 소프트웨어 도구라 특별한 하드웨어는 필요 없지만, "내 프로젝트에 설치해 쓸 때"와 "이 저장소 자체를 클론해 기여할 때"의 요구사항이 서로 다르다. 이 둘을 섞어서 생각하면 엉뚱한 곳에서 에러를 찾게 된다.
| 항목 | 요구사항 |
|---|---|
| Node.js(사용 시) | 20.19+ 또는 22.12+(Storybook 10 기준). 플래그 없이 require(esm)를 지원하는 버전대를 요구 |
| TypeScript(peer) | 4.9 이상. 단, 저장소 자체는 IDE·API 호환을 위해 워크스페이스 TS를 6 계열로 고정하고 타입체크는 TS7 네이티브 컴파일러로 실행 |
| Vite(빌더 사용 시) | 5.0 이상(Vite 4 지원 종료). React/Vue/Svelte 등 Vite 기반 프레임워크 전부 해당 |
| 패키지 매니저(사용 시) | npm 10+ · yarn 4+ · pnpm 9+ 권장(더 낮은 버전도 동작할 수 있으나 비권장) |
| Node.js(저장소 빌드 시) | .nvmrc에 고정된 22.22.3. Yarn Berry(워크스페이스) + NX 태스크 러너 필요 |
| 브라우저(테스트 시) | Vitest 브라우저 모드·E2E 실행에 Playwright가 필요(Chromium 등 자동 설치) |
· 검색으로 찾은 예전 글이 @storybook/addon-essentials 설치를 안내한다면 더 이상 존재하지 않는 패키지다. 9.0부터 그 안의 기능(액션·컨트롤·뷰포트·상호작용)이 코어로 흡수됐으므로 별도 설치·등록이 필요 없다.
· @storybook/react처럼 "렌더러 단독" 패키지를 쓰던 예전 방식도 지금은 @storybook/react-vite 같은 "프레임워크" 패키지로 옮겨가는 중이다. import 경로가 안 맞으면 이 이동을 의심해볼 것.
npx storybook@latest init으로 아무 React/Vue 프로젝트에 Storybook을 붙여보라. 버튼 컴포넌트 하나를 골라 "기본"·"비활성"·"로딩" 세 가지 상태를 각각 별개의 스토리(named export)로 적어보고, 사이드바에 세 항목이 어떻게 뜨는지 확인하라.
브라우저 개발자도구에서 Elements 탭의 <iframe> 태그를 찾아 매니저 문서와 프리뷰 문서가 실제로 분리돼 있는지 확인하라. Console에서 프리뷰 iframe 컨텍스트를 선택해 window.parent가 매니저 창을 가리키는지도 살펴보라.
스토리 렌더 함수 안에 throw new Error('test')를 넣어 프리뷰를 고의로 깨보라. iframe 안에는 에러 화면이 뜨지만 사이드바·툴바(매니저)는 멀쩡히 동작하는지 확인하고, 다른 스토리를 클릭했을 때 바로 복구되는지 보라.
개발 서버를 띄운 채 Network 탭에서 index.json(또는 /index.json) 요청을 찾아보라. 실제 컴포넌트 코드는 하나도 안 들어있고 제목·이름·태그 같은 메타데이터만 담겨 있다는 것을 확인하면, "정적 인덱싱"이 실제로 무엇을 의미하는지 체감할 수 있다.
code/core/src/channels/main.ts를 읽고, 로컬에서 저장소를 빌드한 뒤 .storybook/manager.ts나 애드온 코드에서 channel.on('storyRendered', console.log) 같은 리스너를 추가해보라. 스토리를 전환할 때마다 어떤 이벤트가 어떤 순서로 오가는지 콘솔에서 관찰하면 심화.
| 주차 | 주제 · 목표 |
|---|---|
| 1주차 CSF와 스토리 작성 | CSF3 문법(메타+named export), args·decorators·parameters 개념. 공식 storybook.js.org "Writing stories" 가이드를 따라 직접 여러 상태의 스토리를 만들어보기 |
| 2주차 아키텍처 내부 | channels·manager-api·preview-api 코드를 읽고 이벤트 버스·정적 인덱싱을 정리. Node의 EventEmitter, postMessage API를 먼저 복습하면 이해가 빠름 |
| 3주차 테스트 통합 | Vitest 기본기(설정·브라우저 모드)를 익힌 뒤 addon-vitest로 스토리를 테스트로 실행. 상호작용 테스트(play 함수)·접근성 검사(axe-core)까지 확장 |
| 4주차 빌더 & 모노레포 운영 | Vite/Webpack 플러그인 작성법을 익히고 Builder 인터페이스와 대조. NX 태스크 그래프, Yarn 워크스페이스로 대규모 저장소를 운영하는 법 정리 |
| 도구 | Storybook과의 차이 |
|---|---|
| Ladle | Vite 전용의 초경량 대안. CSF 스토리 파일과 호환되도록 설계돼 이전이 쉽지만, 애드온 생태계·문서(MDX) 기능은 Storybook보다 훨씬 작다. "빠른 시작"이 최우선일 때 선택 |
| Histoire | Vue 생태계에서 인기 있는 Vite 기반 대안. Vue 전용 기능(Vue DevTools 연동 등)에 강점이 있지만, 프레임워크 지원 폭과 커뮤니티 규모는 Storybook에 못 미침 |
| Chromatic | Storybook의 경쟁 도구가 아니라 같은 팀(Storybook 메인테이너)이 만든 상용 SaaS. 스토리마다 스크린샷을 찍어 시각적 회귀(무엇이 픽셀 단위로 바뀌었는지)를 비교해준다 — Storybook 위에 얹어 쓰는 클라우드 서비스 |
| Playwright Component Testing | Storybook 없이 Playwright만으로 컴포넌트를 브라우저에 마운트해 테스트. "워크숍(문서·미리보기)"은 없고 순수 테스트 실행에 집중 — Storybook의 addon-vitest와 목적이 겹치지만 워크숍 UI가 빠져있는 셈 |
컴포넌트를 눈으로 보며 개발하고, 그 스토리를 팀 문서로도 공유하고 싶다면 Storybook의 넓은 애드온·프레임워크 지원이 유리하다. 반대로 "테스트 실행"만이 목적이고 UI 미리보기가 필요 없다면 Playwright CT처럼 더 가벼운 도구가 오버헤드를 줄여준다.
Storybook은 "매니저(브라우저 UI)와 프리뷰(iframe)를 완전히 분리하고 그 사이를 이벤트 채널로 잇는다"는 하나의 아이디어 위에, "코드를 실행하지 않고 정적으로 스토리 목록을 뽑는 인덱서"와 "Vite/Webpack을 같은 인터페이스로 감싸는 빌더 추상화"를 더해 지금의 모습이 됐다. 여기에 최근에는 Vitest를 엔진 삼아 "보는 도구"에서 "테스트하는 도구"로 무게중심이 옮겨가는 중이다.
이 레포는 다음과 같은 사람에게 특히 도움이 된다.