개발

    감춰진 UI를 ‘보이게’ 만든 스토리 — 올라핀테크 프론트개발팀의 스토리북 도입기

    2026.07.21

    스토리북 도입으로 감춰진 UI를 시각화하고 컴포넌트 중복과 협업 문제를 해결한 올라핀테크 프론트개발팀의 실제 적용 사례. UI 컴포넌트 문서화, 모킹 전략, 개발 생산성 향상 과정을 상세히 다룹니다.


    “작은 불편에서 출발해, 모두가 편해지는 순간까지. 올라핀테크 팀의 이야기를 공유합니다.”


    서론: 보이지 않는 UI는 기술부채가 된다

    프론트엔드 개발을 하다 보면 “눈에 보이지 않는 UI”가 생각보다 무섭습니다.
    특히 여러 서비스가 동시 운영되는 환경에서는 컴포넌트 중복, 파편화된 UI, 레거시 코드들이 개발 속도를 잡아먹곤 하죠.

    올라핀테크는 ‘올라’, ‘세이비’, ‘오핀’, ‘오월’과 같은 다양한 서비스를 운영하고 있고,
    각 서비스별로 UI 스타일·페이지 구조·상태 관리 방식까지 모두 다르게 성장해 왔습니다.

    이런 상황에서 새로운 페이지를 만들 때마다 우리는 과거의 UI를 뒤져야 했고,
    어떤 컴포넌트는 실제 화면으로 들어가기 전까지 “존재하는지조차 알 수 없는” 상태였어요.

    그래서 우리는 결심했습니다.
    UI를 눈앞에 드러내고, 개발자·디자이너·기획자가 모두 같은 화면을 보며 협업할 수 있는 환경을 만들자.

    이 결론이 바로 스토리북 도입으로 이어졌습니다.


    ❓왜 스토리북인가

    👀 “UI가 보이지 않는다”는 문제에서 출발한 고민

    “이거 어디서 본 버튼인데…”
    “아, 내가 비슷한 걸 만들었던 것 같은데…”

    빠르게 성장하는 스타트업에서는 이런 말이 자주 들립니다.
    저희 팀도 예외는 아니었습니다.
    팀원들이 각자 맡은 화면을 개발하다 보면 비슷한 컴포넌트를 다시 만들거나 나중에서야 중복을 발견하는 일이 반복되었습니다.
    심지어 같은 기능을 하는 버튼이 스타일만 다르게 세 가지나 존재한 적도 있었습니다.

    사실 더 큰 문제는 확인하기 어려운 UI 상태였습니다.

    “이 버튼의 로딩 상태는 어떻게 보이지?”
    “에러가 나면 어떤 화면이 나타나지?”

    이런 엣지 케이스를 확인하려면 실제 상황을 직접 재현해야 했습니다.

    예를 들어 “급여 신청 완료” 화면의 에러 상태를 확인하려면,

    1. 로그인
    2. 회원가입
    3. 본인인증
    4. 급여 조회
    5. 신청 금액 입력
    6. 계좌 정보 입력
    7. 마지막 단계에서 일부러 잘못된 데이터를 입력해 에러 발생

    이 과정을 매번 반복해야 했습니다.

    한 번 상태를 확인하는 데 많은 시간이 걸렸고, 결국 개발 속도는 느려지고 테스트 과정은 불필요하게 복잡해졌습니다.
    문제는 신규 프로젝트뿐만 아니라 레거시 프로젝트 유지보수에서도 드러났습니다.
    코드를 봐도 어떤 화면이 그려질지 직접 실행해보기 전에는 알 수 없었고,
    어떤 코드가 어디에서 무슨 역할을 하는지 파악하기 어려웠습니다.

    이 경험을 통해 저희는 하나의 결론에 도달했습니다.

    “UI 상태를 눈으로 바로 확인할 수 있는 환경이 필요하다.”


    💡스토리북 도입기 — 코드를 시각화하다

    🎯 단순 ‘문서화 도구’가 아니라 ‘협업 도구’로

    위의 교훈을 얻은 저희 팀은 스토리북(Storybook) 도입을 결정했습니다.

    하지만 도입 과정은 마냥 순조롭지는 않았습니다.

    스토리북의 생태계를 이해해야 했고, 팀 내에서 효율적인 관리 방식을 정의하는 일도 필요했습니다.

    그럼에도 여러 시행착오를 거치며 점차 방향을 잡아갔고,

    결국 우리 팀에 가장 잘 맞는 형태의 스토리북 구조를 완성할 수 있었습니다.

    이번 포스팅에서는 시행착오 결과 얻을 수 있었던 인사이트 및 스토리북 구조를 여러분들께 공유해드리려 합니다.

    🧱스토리북 구조

    1. 프론트엔드 아키텍처

    모든 공사는 터파기부터 시작합니다.
    프론트엔드 개발도 마찬가지입니다.
    기초 구조가 탄탄해야 기능 확장과 유지보수가 유리합니다.
    과거 올라의 레거시 프로젝트에는 이런 구조가 거의 없었습니다.

    기능과 컴포넌트가 복잡하게 얽혀 있었고, 테스트를 진행하기도 쉽지 않았습니다.
    같은 실수를 반복하지 않기 위해,
    V2에서는 Shared(UI) → Widgets → Views로 이어지는 명확한 계층 구조를 설계했습니다.

    각 레이어의 책임이 분리되면서 코드의 응집도와 유지보수성이 크게 향상되었고, 이 구조는 스토리북의 구성에도 자연스럽게 반영되었습니다.

    프론트엔드 아키텍처 - 올라핀테크

    프론트엔드 아키텍처 — 올라핀테크

    2. 컴포넌트 스토리

    컴포넌트는 여러 가지 상태를 가질 수 있습니다.
    하지만 코드만으로는 그 모든 상태를 파악하기가 쉽지 않습니다.

    이에 저희 팀은 각 컴포넌트마다 하나의 스토리를 생성하고,
    그 안에서 컴포넌트의 주요 상태를 모두 표현하는 방식으로 팀 컨벤션을 정립했습니다.

    모든 UI는 자신의 상태를 스토리북에 명시해야 한다.

    예를 들어 특정 모달이 호출하는 API에 따라 다양한 UI를 보여준다고 가정해보겠습니다.
    그렇다면 그 모달이 가질 수 있는 경우의 수를 모두 스토리북에 나열한 뒤, API를 모킹하여 각각 스토리에 따로 전달합니다.

    export const Default: Story = {
      name: '기본',
      decorators: [
        () => {
          const { openModal } = useWidgetModal()
          return (
            <div>
              <Button onClick={() => openModal('PRODUCT_MARGIN_BULK_UPLOAD')}>
                열기
              </Button>
            </div>
          )
        },
      ],
    }
    
    export const 양식_다운로드_성공: Story = {
      decorators: [
        () => {
          createMock(
            downloadProductMarginUploadTemplate,
            'downloadProductMarginUploadTemplate'
          ).mockResolvedValue(
            DOWNLOAD_PRODUCT_MARGIN_UPLOAD_TEMPLATE_FIXTURE.SUCCESS
          )
          const { openModal } = useWidgetModal()
          return (
            <div>
              <Button onClick={() => openModal('PRODUCT_MARGIN_BULK_UPLOAD')}>
                열기
              </Button>
            </div>
          )
        },
      ],
    }
    
    export const 양식_다운로드_상품없음: Story = {
      decorators: [
        () => {
          createMock(
            downloadProductMarginUploadTemplate,
            'downloadProductMarginUploadTemplate'
          ).mockResolvedValue(
            DOWNLOAD_PRODUCT_MARGIN_UPLOAD_TEMPLATE_FIXTURE.NO_EXIST_DATA
          )
    
          const { openModal } = useWidgetModal()
          return (
            <div>
              <Button onClick={() => openModal('PRODUCT_MARGIN_BULK_UPLOAD')}>
                열기
              </Button>
            </div>
          )
        },
      ],
    }
    
    // ...
    

    컴포넌트 스토리 - 올라핀테크

    컴포넌트 스토리 — 올라핀테크

    이로써 일괄 업로드 모달이 어떤 상태값을 가지는지 한눈에 파악할 수 있게 되었습니다.
    개발 당시에는 각 상태가 머릿속에 남아 있지만, 일주일만 지나도 대부분 잊어버리기 마련입니다.
    하지만 이렇게 스토리북에 모든 UI 상태를 기록해두면, 유지보수 시 컨텍스트 전환 비용을 크게 줄일 수 있고, 신규 구성원도 손쉽게 온보딩할 수 있는 환경을 만들 수 있습니다.

    3. 뷰 스토리

    뷰(View)는 여러 컴포넌트와 위젯이 모여 구성된 하나의 화면 단위입니다.
    저희는 이러한 완성된 화면 단위 역시 스토리북에 스토리로 추가했습니다.
    이를 통해 개발자가 아닌 구성원들도 서비스의 화면 구조와 흐름을 손쉽게 파악할 수 있게 되었습니다.
    덕분에 디자이너 동료 역시 스토리북을 통해 실제 화면의 구성을 직관적으로 검토할 수 있었습니다.

    export const Coupang: Story = {
      name: '쿠팡',
      parameters: {
        nextjs: {
          navigation: {
            query: { tab: 'coupang' },
          },
        },
      },
    }
    
    export const Smartstore: Story = {
      name: '스마트스토어',
      parameters: {
        nextjs: {
          navigation: { query: { tab: 'smartstore' } },
        },
      },
    }
    
    export const Lotteon: Story = {
      name: '롯데온',
      parameters: {
        nextjs: {
          navigation: { query: { tab: 'lotteon' } },
        },
      },
    }
    
    export const BucketPlace: Story = {
      name: '오늘의집',
      parameters: {
        nextjs: {
          navigation: { query: { tab: 'bucket-place' } },
        },
      },
    }
    
    export const Eleven: Story = {
      name: '11번가',
      parameters: {
        nextjs: {
          navigation: { query: { tab: '11st' } },
        },
      },
    }
    

    4. API 모킹

    API에 의존하는 컴포넌트를 표현하기 위해서는 API 응답을 모킹(mocking) 하는 과정이 필요합니다.
    스토리북은 이를 지원하는 플러그인을 제공하므로, 간단하게 모킹을 적용할 수 있습니다.

    // .storybook/main.ts
    
    import type { StorybookConfig } from '@storybook/nextjs'
    
    const config: StorybookConfig = {
      stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
      addons: [
        '@storybook/addon-links',
        '@storybook/addon-essentials',
        '@storybook/addon-interactions',
        {
          name: 'storybook-addon-module-mock', // 모킹 모듈 추가!
          options: {
            include: ['**/actions/**'],
            exclude: ['**/node_modules/**'],
          },
        },
      ],
      // ...
    }
    export default config
    

    모킹을 보다 안정적으로 관리하기 위해
    저희는 각 API의 반환값을 Zod 기반의 Fixture 로 정의했습니다.

    // schema.ts
    export const getBlogListItemSchema = z.object({
      id: z.number(),
      title: z.string(),
      category: z.enum(['TREND', 'TIP', 'GUIDE', 'NEWS', 'EXPERIENCE']),
      thumbnailImageUrl: z.string().nullish(),
      publishAt: z.string(),
      showCount: z.number(),
      createdAt: z.string(),
      updatedAt: z.string(),
    })
    
    export type BlogListItem = z.infer<typeof getBlogListItemSchema>
    ``````ts
    // fixture.ts
    export const GET_BLOG_LIST_FIXTURE: GetBlogListFixture = {
      성공: {
        type: 'success',
        data: {
          meta: {
            code: 'SUCCESS',
            message: '성공',
          },
          data: {
            list: [
              {
                id: 148,
                title:
                  '올라레터|네이버 쇼핑, 네이버 플러스스토어로 개편된다! 쇼핑몰 셀러들이 확인해야 할 점은?',
                category: 'TREND',
                thumbnailImageUrl:
                  'https://allra-image.com/blog/media_20241007180421.png',
                publishAt: '2024-10-07T10:00:00',
                showCount: 456,
                createdAt: '2024-10-07T18:04:21',
                updatedAt: '2025-03-13T17:52:48',
              },
              {
                id: 147,
                title:
                  '올라레터|가을 유행 아이템은 무엇? 아이템 소싱에 참고하세요! 🍂',
                category: 'TREND',
                thumbnailImageUrl:
                  'https://allra-image.com/blog/media_20240923175049.jpg',
                publishAt: '2024-09-23T10:00:00',
                showCount: 428,
                createdAt: '2024-09-23T17:50:49',
                updatedAt: '2025-01-21T16:02:46',
              },
              {
                id: 146,
                title:
                  "상품 등록을 쉽고 빠르게 하고 싶다면? 쇼핑몰통합관리솔루션 '넥스트엔진'",
                category: 'TIP',
                thumbnailImageUrl:
                  'https://allra-image.com/blog/media_20240920180117.png',
                publishAt: '2024-09-20T10:00:00',
                showCount: 121,
                createdAt: '2024-09-20T18:01:17',
                updatedAt: '2025-01-21T15:50:37',
              },
              {
                id: 145,
                title:
                  'G마켓 · 옥션 정산 주기 및 수수료 산정 방식 한눈에 보기! (판매예치금/계좌송금 등등)',
                category: 'TIP',
                thumbnailImageUrl:
                  'https://allra-image.com/blog/media_20240913182205.png',
                publishAt: '2024-09-13T10:00:00',
                showCount: 191,
                createdAt: '2024-09-13T18:22:05',
                updatedAt: '2024-10-11T21:20:30',
              },        
            ],
            page: 1,
            pageSize: 4,
            totalPage: 8,
            total: 147,
          },
        },
      },
      빈리스트: {
        type: 'success',
        data: {
          meta: {
            code: 'SUCCESS',
            message: '성공',
          },
          data: {
            list: [],
            page: 1,
            pageSize: 4,
            totalPage: 0,
            total: 0,
          },
        },
      },
    }
    

    픽스쳐를 잘 정의하면 모킹은 매우 쉽습니다. 아래와 같이 만들어놓은 픽스쳐를 차례대로 나열하면 됩니다.

    export const Default: Story = {
      name: '페이지',
      decorators: [
        (Story) => {
          createMock(getBlogList, 'getBlogList').mockResolvedValue(
            GET_BLOG_LIST_FIXTURE.성공
          )
    
          return <Story />
        },
      ],
    }
    export const 검색결과없음: Story = {
      parameters: {
        nextjs: {
          navigation: {
            query: { term: '검색어' },
          },
        },
      },
      decorators: [
        (Story) => {
          createMock(getBlogList, 'getBlogList').mockResolvedValue(
            GET_BLOG_LIST_FIXTURE.빈리스트
          )
    
          return <Story />
        },
      ],
    }
    

    이를 통해 스토리북에서 표현되는 상태가 실제 서비스의 동작과 일관되게 유지되도록 했습니다.

    API모킹 - 올라핀테크

    성공 픽스쳐를 모킹한 스토리입니다.

    성공픽스쳐를 모킹한 스토리 - 올라핀테크

    성공픽스쳐를 모킹한 스토리 — 올라핀테크


    빈리스트 픽스쳐를 모킹한 스토리입니다.

    빈리스트 픽스쳐를 모킹한 스토리 - 올라핀테크


    빈리스트 픽스쳐를 모킹한 스토리 — 올라핀테크

    이렇게 구축된 스토리북 관련 코드들은 단순히 스토리북 내부에서만 사용하는 코드가 아니라,
    E2E 테스트 등 프로젝트 전반에서 관리 가능한 코드 자산으로 발전하게 되었습니다.


    🚀 스토리북 도입 후 변화

    1. 협업 효율 개선

    스토리북을 통해 디자이너는 직접 UI 상태를 확인하고,
    기획자는 화면의 흐름을 사전에 검토할 수 있습니다.
    이 과정에서 피드백 주기가 짧아지고, 커뮤니케이션의 속도와 정확도가 높아졌습니다.

    2. 클린 코드

    응집도가 높고 결합도가 낮은 컴포넌트여야 스토리북에서 명확하게 표현할 수 있습니다.
    이 구조적 요구사항이 자연스럽게 리팩토링을 유도했고, 결과적으로 코드의 전반적인 품질이 향상되었습니다.
    스토리북은 단순한 문서화 도구를 넘어, 클린 코드를 만들어가는 구조적 기반이 되었습니다.

    3. 버그 감소

    스토리북에서 컴포넌트의 모든 상태를 한눈에 확인할 수 있게 되면서
    이전에는 놓치기 쉬웠던 엣지 케이스를 사전에 발견할 수 있었습니다.
    덕분에 QA 단계에서 잡히는 버그가 줄었고,
    기획과 디자인 단계에서도 보다 세밀한 검증이 가능해졌습니다.
    이제 스토리북은 단순한 개발 도구가 아니라,
    팀 전체가 함께 활용하는 시각적 검토 환경으로 자리 잡았습니다.

    🖼 올라핀테크 팀의 스토리북

    현재 올라핀테크의 모든 프론트엔드 프로젝트는 모두 스토리북을 이용하고 있습니다!

    초간편 자금관리 서비스 올라

    초간편 자금관리 서비스 올라는 올라핀테크에서 스토리북을 처음 도입한 프로젝트입니다.
    스토리북을 통해 디자인시스템을 관리하고, 이를 기반으로 SHARED → IDGETS → VIEWS 구조를 체계화했습니다.
    특히 계약하기, 조회하기 등 복잡한 비즈니스 로직을 가진 컴포넌트의 모든 상태를 시각화함으로써 에러 발생 가능성을 크게 줄이고 유지보수 효율을 높였습니다.
    그 결과, 올라팀은 더 나은 서비스를 기획하고 개선할 수 있는 환경을 갖추게 되었습니다.

    초간편 자금관리 서비스 올라 - 올라핀테크

    초간편 자금관리 서비스 올라 — 올라핀테크

    대표님만을 위한 쿠팡 맞춤 코칭, 세이비

    대표님만을 위한 쿠팡 맞춤 코칭, 세이비 역시 스토리북을 적극 활용해 개발·디자인·QA 전 과정의 효율을 높이고 있습니다.

    쿠팡 계정 연동은 운영 환경에서 순차적으로 단계를 진행해야 하지만, 스토리북에서는 각 단계를 독립적인 스토리로 분리해 개별 확인이 가능합니다. 실제 계정이나 네트워크 연결 없이도 동일한 상황을 안정적으로 재현할 수 있어, 테스트와 이슈 재현이 빠르고 신뢰성 있게 이뤄집니다. 또한 공통 레이아웃을 유지해 실제 화면과의 차이를 최소화했으며, 기획·디자인·QA가 동일한 사용 경험을 공유하도록 구성했습니다.

    후불결제로 매출을 키우는 가장 간단한 방법, 오핀

    올라핀테크의 BNPL(Buy Now Pay Later) 서비스 ‘오핀’은 스토리북을 활용해 8주 만에 배포했습니다.

    디자인, 프론트엔드, 백엔드 개발을 병렬로 진행했으며, 백엔드 API 완성 전에 mock 데이터를 넣어 UI 인터랙션 구현과 QA를 완료했습니다. 백엔드 API 완성 후에는 mock 데이터를 실제 API로 교체하는 것으로 개발 속도를 향상시켜 서비스를 일정에 맞춰 오픈할 수 있었습니다.

    오늘 미리 받는 내 월급, 오월

    올라에서 처음 시도 하는 앱서비스 “오늘 미리 받는 내 월급, 오월”에서도 스토리북을 도입했습니다. 비교적 빌드 속도가 느린 앱에서 스토리북을 활용하여 개발 속도를 더욱 향상 시킬 수 있었습니다.

    오늘 미리 받는 내 월급, 오월 - 올라핀테크

    오늘 미리 받는 내 월급, 오월 — 올라핀테크

    🌅 르네상스 — 드러난 UI의 시대

    스토리북을 도입한 이후,
    코드와 화면의 관계를 명확하게 이해할 수 있는 환경이 만들어졌습니다.
    컴포넌트의 상태를 시각적으로 관리하게 되면서, 코드는 한층 더 체계적이고 예측 가능한 형태로 발전했습니다.

    보이는 코드는 관리가 쉬워지고관리되는 코드는 자연스럽게 모듈화되며모듈화된 코드는 테스트와 확장이 용이해졌습니다.

    이제 스토리북은 올라핀테크 프론트엔드팀의 표준 개발 환경으로 자리 잡았습니다. 그 과정에서 E2E 테스트 도입도 자연스럽게 이어졌고, 팀원 모두가 공유 가능한 개발 방식을 함께 만들어가고 있습니다.

    스토리북은 단순한 도구를 넘어, 더 나은 협업과 더 나은 코드를 위한 기반이 되었습니다.

    올라핀테크 프론트엔드팀은 앞으로도 더 나은 개발 환경을 고민하며유저에게 안정적이고 즐거운 서비스를 제공하기 위해 꾸준히 노력하겠습니다.

    읽어주셔서 감사합니다.

    “올라핀테크 팀은 앞으로도 문제를 해결한 과정을 꾸준히 기록하고 나누겠습니다!”

    write. 프론트엔드개발팀 김태수

    올라핀테크 팀의 이전 이야기👇

    (주)올라핀테크

    사업자등록번호 : 509-86-01645

    통신판매업신고 : 제2022-서울강남-02369호

    주소 : 서울특별시 강남구 봉은사로 524, B1층 B132호 (스파크플러스 코엑스점)