클릭비 — 제안서·포트폴리오 운영 어드민(제안서 자동 구조화·발행)
제안서를 데이터와 렌더러로 분리해 입력만으로 고유 URL을 발행하는 운영 시스템 구축
역할 어드민 Frontend 단독 개발 · 제안서 데이터 × 렌더러 설계
Key Result
원문을 12개 섹션 데이터로 구조화해 고정 렌더러로 발행 · 조판 과정 제거로 작성 시간 단축·품질 상향 평준화 · AI 없이도 동작하는 폴백
핵심 요약
영업 담당자가 제안서 원문을 붙여넣으면 백엔드 AI가 12개 섹션으로 구조화하고, 어드민에서 섹션을 검수·보정해 고정 렌더러로 고유 URL에 발행하는 운영 도구입니다. 제안서를 데이터(내용) × 렌더러(디자인)로 분리해, 매번의 수동 조판을 없애고 건별 품질 편차를 줄였습니다. Vite SPA에 TanStack Router 타입세이프 라우팅, 서버/UI/폼 상태 3분할, Feature-Sliced Design을 적용했습니다.
프로젝트 배경
영업 건마다 제안서를 새로 디자인·조판해 시간이 들고 담당자·건별 품질 편차가 컸으며, 산출물이 PDF/PPT라 모바일 열람과 포트폴리오 연계가 불편했습니다. 발주 플랫폼 규정상 회사명·연락처 노출도 금지돼, '글만 준비하면 보낼 수 있는 웹 제안서가 나온다'를 컨셉으로 제안서를 데이터(내용) × 렌더러(디자인)로 분리해 템플릿화했습니다.
담당 범위
- 제안서 섹션 에디터 — 12개 섹션 구조화·검수·완성도 체크
- 제안서 발행·미리보기 및 공개 URL(proposals/[slug])
- 포트폴리오·프로젝트 CRUD 및 발행 연동
- AI 프롬프트 관리(prompts) — 구조화 프롬프트 운영
- TanStack Router 타입세이프 라우팅·인증 가드·공통 API 계층
문제 정의
- 형식이 제각각인 원문 — 제안서 원문 형식이 건마다 달라 사람이 매번 섹션을 나누고 조판해야 했습니다. 이 수작업이 작성 시간과 품질 편차의 근원이었습니다.
- 상태의 혼재 — 서버 데이터(제안서·포트폴리오·프로젝트), 편집 UI 상태, 폼 입력이 뒤섞이면 화면이 커질수록 "지금 무엇이 원본인지" 추적이 어려워집니다.
- AI를 그대로 믿을 수 없음 — AI 구조화 결과에는 오분류·누락이 섞입니다. 사람이 검수·보정할 편집 계층 없이 바로 발행하면 신뢰할 수 없는 산출물이 나갑니다.
기술적 의사결정
1. 제안서를 데이터 × 렌더러로 분리
- 배경 — 바뀌는 것은 섹션 데이터뿐이고 레이아웃·디자인은 고정입니다.
- 선택 — 12개 섹션마다 전용 에디터·렌더러가 해당 데이터만 다루고, 섹션 완성도 체크로 빠진 항목을 드러냈습니다. 결과물은 파일이 아니라 고유 URL(proposals/[slug])로 발행해 모바일 열람·재전송 없는 수정·포트폴리오 연계를 자연스럽게 했고, 회사명·연락처 같은 신원 정보는 기본값에서 구조적으로 배제했습니다.
- 트레이드오프 — 렌더러를 자유 편집형으로 열면 표현력은 늘지만 품질 편차라는 원래 문제가 되살아납니다. 디자인을 고정하고 데이터만 바꾸는 제약을 택해 품질을 상향 평준화했습니다.
2. 서버 · UI · 폼 상태 3분할
- 배경 — 세 종류의 상태를 한 도구로 다루면 서버 데이터가 클라이언트 Store의 사본으로 흩어집니다.
- 선택 — 원격 데이터의 조회·캐시·무효화는 TanStack Query가, 화면 인터랙션 상태는 Jotai가, 폼 입력·검증은 React Hook Form + Zod가 맡도록 책임을 갈랐습니다. 편집·발행 흐름의 상태를 예측 가능하게 유지했습니다.
3. TanStack Router 타입세이프 라우팅 + FSD
- 배경 — 기능이 늘어도 같은 패턴으로 확장하고, 경로·파라미터를 런타임이 아닌 타입 수준에서 보장하고 싶었습니다.
- 선택 — 라우터 플러그인이 생성하는 routeTree로 경로·파라미터를 타입 수준에서 보장하고 진입 가드로 인증을 처리했습니다. entities·features·widgets·shared 계층으로 제안서·포트폴리오·프로젝트·프롬프트 도메인을 나눴습니다.
4. AI는 '생성'이 아니라 '구조화 + 검수 대상'으로 한정
- 배경 — AI가 내용을 지어내면 사실·수치가 틀어지고, 외부 API 장애가 곧 발행 중단이 됩니다.
- 선택 — 사실·수치·의미는 원문 그대로 두고, 형식이 제각각인 원문을 정해진 섹션으로 분해하는 데만 AI를 썼습니다. 구조화는 백엔드가 수행하고 어드민은 프롬프트를 관리·튜닝하며 결과 섹션을 사람이 검수·보정합니다.
- 트레이드오프 / 보완 — AI 자동화율을 높이면 사람 손은 줄지만 오류 위험이 커집니다. AI는 초벌 분해까지, 최종 책임은 사람으로 경계를 긋고, AI 장애·거부·빈 입력에도 발행이 멈추지 않도록 규칙 기반 폴백을 뒀습니다.
Before → After
| 관점 | Before | After |
|---|---|---|
| 산출물 | PDF/PPT 파일 | 고유 URL 웹 제안서 |
| 제작 방식 | 건별 수동 디자인·조판 | 원문 → 섹션 구조화·검수·렌더 |
| 수정·전달 | 수정 후 파일 재전송 | 같은 링크에 그대로 반영 |
| 상태 관리 | 서버·UI·폼 상태 혼재 | Query·Jotai·RHF 3분할 |
| 신원 노출 | 실수로 노출 위험 | 비노출을 기본값으로 구조적 차단 |
결과
- 원문 입력 → 섹션 구조화 → 검수 → 발행 전 과정 동작
- 제안서 조판 과정 제거로 작성 시간 단축·품질 상향 평준화
- 서버/UI/폼 상태 3분할로 편집·발행 흐름 상태 예측 가능화
- AI 구조화 + 규칙 기반 폴백으로 AI 없이도 발행 지속
- 회사명·연락처 비노출을 기본값으로 구조적 차단
회고
AI를 붙일 때 가장 중요한 결정은 "AI에게 어디까지 맡기지 않을 것인가"였습니다. 생성까지 맡기면 편해 보이지만 제안서에서 사실·수치가 틀어지는 건 치명적이라, AI를 '초벌 구조화'로만 한정하고 최종 판단은 사람과 규칙 기반 폴백에 남겼습니다. 그 덕에 외부 API 상태와 무관하게 발행이 멈추지 않는 시스템이 됐습니다. "데이터 × 렌더러" 분리도 같은 사고의 연장으로, 바뀌는 것과 고정할 것을 먼저 가르면 자동화의 경계가 저절로 보인다는 점을 확인했습니다.