앱잼을 준비하면서 가장 걱정했던 건 “개발을 얼마나 빨리 하느냐”가 아니라, 팀으로 만들 때 생기는 소모였다.
기획/디자인/프론트/백엔드가 동시에 달리다 보면, 기능 하나 붙이는 데도 생각보다 시간이 많이 새는 구간이 있다.
나도 팀 프로젝트를 해보면서 느낀 건, 이런 문제들이 실력 부족 때문이 아니라 협업 구조가 불명확해서 터진다는 점이었다.
그래서 앱잼 전에 체계를 정리해놓고 싶었고, 그게 Storybook으로 이어졌다.
개인 프로젝트와 다르게 팀 프로젝트는 화면이 늘어나면서 “비슷한 UI”가 여기저기 복제된다.
기존에는 보통 이런 흐름으로 개발을 진행했다.
결국 UI를 ‘코드’로 정의하고 관리하는 기준점이 필요했다.
그게 Comfit이 Storybook을 찾게 된 첫 번째 이유다.
Storybook을 쓰기 시작하면 앞으로 흐름이 아래와 같이 변경될 것이다.
특히 앱잼처럼 UI 요구사항이 자주 바뀌는 환경에서는 페이지 안에서 수정하는 것보다 컴포넌트 단위에서 검증하는 게 훨씬 빠르다고 생각했다.
Storybook은 한 줄로 정리하면 이거였다.
UI 컴포넌트를 페이지랑 분리해서, 컴포넌트 자체를 전시하고 테스트하는 공간
나는 Storybook을 도입하기 전에, 컴포넌트가 항상 페이지에 붙어 있는 형태로만 작업했다.
그러다 보니 컴포넌트를 고칠 때마다 이런 식의 비효율이 발생했다.
Storybook은 이걸 반대로 만든다.
이 흐름이 팀 프로젝트에서 특히 유용했던 이유는 명확했다.
페이지는 여러 요소가 섞여 있어서 디테일 체크가 어렵다.
근데 Storybook에서는 버튼 하나, 카드 하나를 정확히 그 컴포넌트만 보고 검증할 수 있다.
팀 프로젝트에서 자주 놓치는 케이스가 이거였다.
이런 걸 페이지에서 다 확인하려면 시간이 정말 많이 든다.
Storybook에서는 한 화면에서 “케이스”를 정리해두면 계속 재활용할 수 있다.
결론적으로 Storybook을 도입하기로 한 결정은 “좋아 보인다”가 아니라, 앱잼의 현실적인 제약 때문에 더 확실해졌다.
짧은 기간에 MVP를 만들면, 기획과 디자인이 중간중간 계속 바뀐다.
이때 페이지에서만 UI를 관리하면, 변경이 생길 때마다 전체 화면을 다시 열어서 “어디가 영향 받는지” 추적해야 한다.
Storybook이 있으면 바뀐 컴포넌트의 영향 범위를 Stories 목록만 봐도 바로 알 수 있다.
예를 들어 팀원 A는 PrimaryButton을 만들어 쓰는데
팀원 B는 같은 버튼을 페이지에 그냥 새로 만들 수도 있다. “어디에 뭐가 있는지” 안 보여서 생기는 문제라고 생각한다.
Storybook은 “컴포넌트 카탈로그” 역할을 해서 어떤 컴포넌트를 써야 하는지 한 번에 알 수 있게 한다.
로딩, 에러, 빈 상태 처리가 흔히 “나중에”로 미뤄진다.
근데 앱잼은 데모까지 가야 해서, 이런 디테일이 결국 완성도를 가를 수 있다고 생각한다.
Storybook에서 상태를 강제로 정리해두면
개발 과정에서 “기본 상태만 있는 UI”가 되는 걸 막아줄 수 있다고 한다.
나는 “일단 빨리 붙이고, 팀에서 쓰기 좋은 구조”를 목표로 했다.
npx storybook@latest init
실행하면 알아서 프로젝트 환경에 맞게 설정해준다.
(React/Vite/Next 등 자동 감지)
npm run storybook
기본 포트는 6006
// components/Button/Button.tsx
type ButtonProps = {
label: string;
variant?: 'primary' | 'secondary';
disabled?: boolean;
onClick?: () => void;
};
export default function Button({
label,
variant = 'primary',
disabled = false,
onClick,
}: ButtonProps) {
return (
<button
disabled={disabled}
onClick={onClick}
className={`btn ${variant} ${disabled ? 'disabled' : ''}`}
>
{label}
</button>
);
}
// components/Button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import Button from './Button';
const meta: Meta<typeof Button> = {
title: 'components/Button',
component: Button,
args: {
label: '버튼',
variant: 'primary',
disabled: false,
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {};
export const Secondary: Story = { args: { variant: 'secondary' } };
export const Disabled: Story = { args: { disabled: true } };
export const LongText: Story = { args: { label: '텍스트가 엄청 길어지면 어떻게 될까 확인하기' } };
이렇게 해두면 팀이 버튼을 쓸 때 Primary/Secondary/Disabled/긴텍스트 케이스를 “페이지 실행” 없이 바로 확인 가능하다.
Storybook에서 가장 많이 쓰게 될 기능이 바로 args이다.
export const Disabled: Story = {
args: {
disabled: true,
},
};
이 한 줄이 의미하는 바는 아래와 같다.
특히 아래 상태들을 Story로 만들어두는 게 좋을 것 같다.


앱잼 프로젝트 특성상 모바일 UI 대응은 거의 필수다.
Storybook에서는 애드온 하나로 이걸 바로 확인할 수 있다고 한다.
// .storybook/preview.ts
import type { Preview } from '@storybook/react';
const preview: Preview = {
parameters: {
viewport: {
defaultViewport: 'mobile1',
},
},
};
export default preview;
이렇게 설정해두면,
를 페이지 들어가지 않고도 확인할 수 있다.


예전에는 “이 컴포넌트 고치면 어디 깨질지”가 무서웠다.
Storybook에서는 관련 story를 쭉 돌려보면서 영향 범위를 빠르게 확인할 수 있을 것이다.
“이 카드에서 padding이 조금만 더…” 같은 피드백이 들어오면,
페이지 링크보다 Story 링크를 공유하는 게 훨씬 빨랐다.
로딩/빈상태/에러상태를 story로 만들어두니까
나중에 페이지 개발할 때 “이 상태 처리부터 해야지”라는 습관이 생길 것 같다.
앱잼에서 Storybook을 그냥 설치만 해두면, 한정된 시간 안에 정신 없이 결국 안 쓰게 될 수도 있다 생각한다.
그래서 나는 운영 규칙을 최소한으로 잡는 게 중요하다고 생각한다.
Storybook을 도입하기 전엔
“이거까지 하면 시간이 더 드는 거 아닌가?”라는 생각이 컸다.
하지만 직전 기수 앱잼 팀 프로젝트를 살펴보니 그 반대였다.
이런 것들을 합치면 Storybook은 초반 투자로 전체 비용을 줄이는 도구일 것이다.
앱잼처럼 짧고 빠르게 완성해야 하는 프로젝트에서는 특히 더 효과가 크다고 느껴질 것이라 생각한다.