📕
Storybook(Next.js)에 대해 알아보고 프로젝트에 적용해 보자!
2025.06.13
Storybook의 핵심 개념
컴포넌트 단위 개발
- Storybook은 UI 컴포넌트 하나하나를 “Story”라는 단위로 분리해서 개발합니다.
- 각 Story는 컴포넌트의 다양한 상태(예: 버튼의 크기, 색상, 비활성화 등)를 독립적으로 보여줍니다.
실시간 미리보기 & 테스트
- Storybook UI에서 각 Story를 클릭하면,
해당 상태의 컴포넌트를 실시간으로 미리보고, 조작(Props 변경)하며 테스트할 수 있습니다.
문서화
- 각 Story는 자동으로 문서화되어,
디자인 시스템, 개발자 문서, QA 가이드 등으로 활용할 수 있습니다.
Storybook의 주요 기능에는 뭐가 있을까?
Stories
-
컴포넌트의 다양한 상태/조합을 코드로 정의합니다.
-
예시
export const Primary = { args: { variant: 'primary', children: 'Primary' } }; export const Disabled = { args: { disabled: true, children: 'Disabled' } };
Controls (Args/ArgTypes)
- Storybook UI에서 props(Args)를 실시간으로 조작할 수 있습니다.
- argTypes로 props의 타입, 설명, 컨트롤 타입(슬라이더, 드롭다운 등)을 지정할 수 있습니다.
Docs
- 각 컴포넌트/Story에 한글/영문 설명, 사용법, 예시를 추가할 수 있습니다.
- 자동으로 props 테이블, 예시 코드, 미리보기 등이 생성됩니다.
Addons
- 접근성(a11y), 테스트, 디자인 연동, 스냅샷, 다크모드 등 다양한 기능을 플러그인처럼 확장할 수 있습니다.
Mocking & Decorators
- API, 라우터, 상태관리(zustand, recoil 등)도 Storybook에서 mock하여 실제 서비스와 유사한 환경을 만들 수 있습니다.
Storybook의 장점
- UI 컴포넌트 개발 속도 향상: 페이지 전체가 아니라, 컴포넌트만 빠르게 개발/테스트 가능
- 디자인-개발 협업 강화: 디자이너, 기획자, QA가 Storybook에서 UI를 직접 확인/피드백
- 문서화 자동화: 컴포넌트 사용법, props, 예시가 자동으로 문서화
- 회귀 테스트/품질 보장: 스냅샷, 접근성, 인터랙션 테스트 등 다양한 품질 도구와 연동
세팅하는법 (Next.js(v15) + Storybook(v9))
npm create storybook@latest
- 설치하면 자동으로 플러그인 설치도 설치해줍니다.
하지만 설치하고 storybook을 실행보니 에러가 발생합니다.
=> Failed to build the preview
SB_FRAMEWORK_NEXTJS_0003 (IncompatiblePostCssConfigError): Incompatible PostCSS configuration format detected.
Next.js uses an array-based format for plugins which is not compatible with Vite:
// ❌ Incompatible format (used by Next.js)
const config = {
plugins: ["@tailwindcss/postcss"],
};
Please transform your PostCSS config to use the object-based format, which is compatible with Next.js and Vite:
// ✅ Compatible format (works with Next.js and Vite)
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
Original error: Invalid PostCSS Plugin found at: plugins[0]
(@./postcss.config.mjs)
- 에러에 대해서 살펴보니 @tailwindcss/postcss plugins를 @tailwindcss/postcss”: {} 이런식으로 바꿔달라는 에러인거 같다.
const config = {
plugins: ['@tailwindcss/postcss'],
};
export default config;
- ❌
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
};
export default config;
- ✅ 으로 바꿔주면 실행이됩니다.
스토리북 tailwindCSS 설정 (Next.js)
npm install tailwindcss @tailwindcss/vite
// vite.config.ts
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [tailwindcss()],
});
import type { Preview } from '@storybook/nextjs-vite';
import '../src/app/globals.css';
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
a11y: {
test: 'todo',
},
},
};
export default preview;
- preview 에 import ‘../src/app/globals.css’; 추가해주셔야 tailwindcss가 적용됩니다.
예시
저희 HOBBi 프로젝트에 스토리북 적용 예시 입니다.
import React, { ButtonHTMLAttributes } from 'react';
import clsx from 'clsx';
type ButtonVariant = 'primary' | 'outline' | 'secondary';
type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
const sizeStyles = {
sm: 'text-sm',
md: 'text-base',
lg: 'text-lg',
xl: 'text-xl',
};
const variantStyles = {
primary: 'bg-primary text-primary-b80 hover:bg-primary/80',
outline: 'border border-primary-b60 bg-grayscale-0 text-primary-b60 hover:bg-primary-b60 hover:text-grayscale-0',
secondary: 'bg-grayscale-10 text-grayscale-50',
};
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
fullWidth?: boolean;
children: React.ReactNode;
className?: string;
disabled?: boolean;
}
export default function Button({
variant = 'primary',
size = 'xl',
fullWidth = false,
children,
className = '',
disabled = false,
...props
}: ButtonProps) {
const currentVariant = disabled ? 'secondary' : variant;
return (
<button
className={clsx(
'py-3 px-4 rounded-lg button_transition font-semibold text-center cursor-pointer disabled:cursor-not-allowed h-[60px] max-md:h-[48px] whitespace-nowrap',
sizeStyles[size],
variantStyles[currentVariant],
{ 'w-full': fullWidth },
className,
)}
disabled={disabled}
{...props}
>
{children}
</button>
);
}
- 기존에 공동컴포넌트 였던 Button으로 예시를 들겠습니다.
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import React from 'react';
import Button from '../components/common/button';
const meta: Meta<typeof Button> = {
title: 'Button/Button',
component: Button,
parameters: {
layout: 'centered',
docs: {
description: {
component:
'공통적으로 사용하는 버튼 컴포넌트입니다. variant, size, fullWidth, disabled 등 다양한 옵션을 지원합니다.',
},
},
},
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'outline', 'secondary'],
description: '버튼의 스타일(색상/테두리 등)을 지정합니다. (primary: 메인, outline: 외곽선, secondary: 보조)',
},
size: {
control: 'select',
options: ['sm', 'md', 'lg', 'xl'],
description: '버튼의 크기를 설정합니다. (sm: 작음, md: 보통, lg: 큼, xl: 매우 큼)',
},
fullWidth: {
control: 'boolean',
description: '버튼을 부모의 가로 전체로 확장할지 여부를 설정합니다.',
},
disabled: {
control: 'boolean',
description: '버튼을 비활성화할지 여부를 설정합니다.',
},
children: {
control: 'text',
description: '버튼에 표시될 텍스트 또는 요소입니다.',
},
},
args: {
children: 'Button',
variant: 'primary',
size: 'xl',
fullWidth: false,
disabled: false,
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Primary Button',
},
};
export const Outline: Story = {
args: {
variant: 'outline',
children: 'Outline Button',
},
};
export const Secondary: Story = {
args: {
variant: 'secondary',
children: 'Secondary Button',
},
};
export const Disabled: Story = {
args: {
disabled: true,
children: 'Disabled Button',
},
};
export const FullWidth: Story = {
args: {
fullWidth: true,
children: 'Full Width Button',
},
};
export const Sizes: Story = {
render: (args) => {
return (
<div
style={{
display: 'flex',
gap: 12,
flexDirection: 'column',
width: 220,
}}
>
<Button {...args} size='sm'>
Small
</Button>
<Button {...args} size='md'>
Medium
</Button>
<Button {...args} size='lg'>
Large
</Button>
<Button {...args} size='xl'>
XLarge
</Button>
</div>
);
},
};
meta란?
- meta는 해당 컴포넌트의 스토리북 전체 설정/정보를 담는 객체입니다.
- 각 스토리 파일(Button.stories.tsx)의 최상단에 선언하며, 이 객체를
export default meta;
로 내보내야 스토리북이 인식합니다.
meta의 주요 속성
title
- 스토리북 좌측 네비게이션에 표시될 컴포넌트/스토리의 경로입니다.
title: 'Button/Button';
→ “Button” 폴더 아래 “Button” 컴포넌트로 표시
component
- 스토리북에서 미리볼 컴포넌트를 지정합니다.
parameters
- 스토리북 전체 설정/문서화/레이아웃 등을 지정합니다.
- 주요 예시
- layout: ‘centered’, ‘fullscreen’, ‘padded’ 등 미리보기 레이아웃
- docs.description.component: 컴포넌트 전체 설명(문서화)
- 기타: actions, backgrounds, viewport 등 다양한 글로벌 설정 가능
tags
- 스토리북의 자동 문서화(autodocs) 등에서 활용되는 태그입니다.
argTypes
- props(Args)의 타입, 설명, 컨트롤 UI를 지정합니다.
- 각 prop별로
- control: 스토리북 UI에서 조작할 수 있는 컨트롤 타입(예: select, boolean, text 등)
- options: select 컨트롤의 옵션 값
- description: 한글/영문 설명(문서화에 자동 반영)
args
- 스토리북에서 각 스토리의 기본값(초기 props) 을 지정합니다.
스토리북을 프로젝트에 적용해보았는데, 사실 처음 프로젝트할 때 시간이 촉박할거같아서 스토리북은 뒤에 나중에 적용하자고 했지만 막상 사용해보고, 공통컴포넌트 만들어 놓으니깐 어려운 작업은 아니였던거같다.
다음에는 스토리북을 배포하는 방법에 대해서 작성해보도록 하겠습니다.