AI 코딩 어시스턴트와 SaaS 만들기 ⑦: 디자인 핸드오프 파일을 랜딩 페이지 데모 쇼케이스로
시작하며
6편까지 결제가 돌고, 챗봇이 답하고, 운영 구멍까지 메운 상태였다. 그런데 정작 첫인상이 문제였다. 제품 오너(나)의 요구는 명확했다.
"홈페이지 딱 들어와서, 예시 보고 오~ 이 기능 좋은데, 느끼게 하고 싶어."
당시 랜딩은 히어로 + 기능 카드 + 통계 밴드가 전부였다. "AI가 이력서를 번역·맞춤화해줍니다"라는 문장은 있는데, 그게 실제로 어떻게 생겼는지 보여주는 화면이 없었다. 방문자는 가입하기 전까지 제품을 한 번도 못 보는 구조였던 것이다.
이번 편은 이 문제를 해결한 두 커밋(e3d00ed, ac37088) 이야기다. 새 기능은 0개, 새 코드는 전부 랜딩용 목업. 그런데 소재가 재밌다 — 몇 주 전에 받아서 구현하고 잊고 있던 디자인 핸드오프 파일이 주인공이다.
Step 1. 소재 발견 — 프로젝트에 이미 hifi 목업이 있었다
랜딩에 제품 화면을 보여주는 방법은 몇 가지가 있다. 스크린샷을 찍어 이미지로 박거나, 실제 컴포넌트를 데모 데이터로 렌더링하거나, 목업을 새로 그리거나.
그런데 이 프로젝트에는 이미 좋은 재료가 있었다. 리브랜딩 때 받았던 디자인 핸드오프 번들(design_handoff_matchda/MatchDa.dc.html) — .dc.html이라는 커스텀 "Design Component" 형식의 파일 하나에 랜딩·대시보드·워크스페이스 3개 화면의 hifi 목업이 들어 있었다. 당시 랜딩과 대시보드를 구현하는 데 쓰고는 "구현 끝났으니 볼 일 없는 문서"로 취급하고 있었다.
핸드오프 README에는 이런 원칙이 명시돼 있다.
이 번들에 포함된 파일(
MatchDa.dc.html)은 HTML로 제작한 디자인 레퍼런스입니다 — 그대로 복사해 출시할 프로덕션 코드가 아닙니다. 작업의 목표는 이 HTML 디자인을 대상 코드베이스의 기존 환경과 패턴·라이브러리를 사용해 재현하는 것입니다. 아래의 정확한 hex 값과 px 값을 그대로 사용하세요.
여기서 유혹이 하나 있었다. 이 HTML은 그 자체로 예쁘게 렌더링되니까, iframe으로 랜딩에 박으면 10분 만에 끝난다. 하지만 정적 JSX로 재현하는 쪽을 택했다.
| iframe 임베드 | 정적 JSX 재현 | |
|---|---|---|
| 구현 속도 | 빠름 | 느림 |
| 커스텀 런타임(.dc.html) 의존 | 남음 | 제거됨 |
| 반응형 대응 | 사실상 불가 | Tailwind 브레이크포인트로 자유롭게 |
| 데모 데이터 수정 | HTML 원본을 건드려야 함 | 상수 배열만 고치면 됨 |
| 코드베이스 패턴 일치 | ✗ | ✓ (핸드오프 README의 원칙 그대로) |
AI 코딩 어시스턴트가 있으니 "재현"의 비용도 크지 않다. 핸드오프 HTML을 읽혀서 "이 화면을 우리 랜딩 컴포넌트 패턴으로 재현해줘"라고 하면 되는 일이다.
Step 2. WorkspaceShowcase — 핵심 가치를 히어로 바로 아래에
첫 번째 쇼케이스는 MatchDa의 핵심 가치인 **"한국어 이력서 → 공고 맞춤 영어 이력서"**를 보여주는 워크스페이스 화면이다 (e3d00ed). 핸드오프의 isWorkspace 화면을 재현했다.
구성은 실제 워크스페이스와 동일하다.
- 상단: Spotify 공고 최적화 배너 + 매칭률 82% 배지
- 좌: 원본 이력서(한국어, "수정 없음 · 312 단어")
- 우: AI 번역·맞춤화 이력서(영어, "방금 업데이트됨 · 298 words") — 최적화된 문구 하이라이트 + AI 조정 사유 노트
디테일 중 재미있었던 게 최적화 문구 하이라이트다. 핸드오프 토큰은 "배경 #DCF5E8 + 브랜드색 밑줄"인데, border-bottom을 쓰면 줄바꿈되는 인라인 텍스트에서 밑줄이 어색해진다. box-shadow inset 트릭으로 재현했다.
function Hl({ children }: { children: React.ReactNode }) {
// 최적화된 문구 하이라이트 (핸드오프 토큰: #DCF5E8 + 브랜드 밑줄)
return (
<span className="rounded-[3px] bg-[#DCF5E8] px-[3px] [box-shadow:inset_0_-2px_0_rgba(4,108,78,0.45)]">
{children}
</span>
)
}
이 <Hl>로 감싼 문구가 "AI가 공고에 맞춰 조정한 표현"을 시각적으로 보여준다.
<li>Designed and operated a <Hl>high-throughput, distributed payments system</Hl> handling 5M daily transactions</li>
<li>Reduced response latency by 40% and led the migration to a <Hl>microservices architecture</Hl></li>
그리고 하이라이트만으로는 "왜 이렇게 바꿨는지"가 전달되지 않으니, 이력서 아래에 AI 조정 사유 노트를 붙였다. "Spotify 공고의 'distributed systems' 요구사항에 맞춰 '대규모 트래픽 결제 시스템' 경험을 강조하도록 표현을 조정했습니다." — 이 한 줄이 "번역기"가 아니라 "공고 맞춤화"라는 차별점을 설명한다.
CTA는 로그인 상태에 따라 분기한다. 랜딩 컴포넌트가 서버에서 authed를 알고 있으므로 prop으로 내려주면 끝이다.
// MatchdaLanding.tsx
<WorkspaceShowcase ctaHref={authed ? '/profile' : signupHref ?? '/login?mode=signup'} />
비로그인 방문자는 가입으로, 이미 가입한 사용자는 자기 이력서 에디터로 바로 간다. 버튼 문구는 "내 이력서로 직접 해보기" — 데모를 본 직후의 다음 행동을 그대로 이어준다.
Step 3. DiscoverShowcase — 핸드오프에 없는 화면은 실제 제품에서
두 번째 쇼케이스인 잡 탐색은 사정이 달랐다. 핸드오프가 만들어진 시점에는 /discover(추천 기업 원클릭 수집 + AI 채점)가 존재하지 않았기 때문에, 재현할 원본이 핸드오프에 없다. 그래서 이번에는 반대로, 실제 제품 UI를 목업화했다 (ac37088).
const PRESETS = ['Apple', 'Spotify', 'Stripe', 'Anthropic', 'Figma', 'Reddit']
const DEMO_JOBS = [
{
score: 82,
title: 'Product Manager, AI Growth',
meta: 'Figma · San Francisco, CA · United States',
reason: 'AI 성장 제품 매니저는 제품 전략·AI 경험·데이터 기반 우선순위 결정과 완벽 일치',
},
// ...
]
추천 기업 칩(+ Apple, + Spotify…) 옆에 이런 칩을 하나 끼워 넣었다.
<span className="inline-flex items-center rounded-full border border-[#CEEBDC] bg-[#ECFDF3] px-3.5 py-1.5 text-[13px] font-medium text-[#046C4E]">
Figma · ✓ 171건 발견
</span>
이 171건은 지어낸 숫자가 아니다. 6편에서 다뤘던, 실제로 추천 기업 수집을 며칠 굴려서 쌓인 공고 수다. 데모 목업에 실제 운영 수치를 넣으니 "방금 수집이 끝난 화면"처럼 살아있는 느낌이 난다. 공고 카드에도 점수 배지 + 매칭 사유 + "지원 관리에 추가" 버튼까지, 실제 /discover에서 보이는 요소를 그대로 축소 재현했다.
Step 4. ApplicationsShowcase — 칸반은 데이터를 상수로 분리
세 번째는 핸드오프 isDashboard 화면의 칸반 보드 재현이다. 준비 중 → 지원 완료 → 면접 진행 → 오퍼 4컬럼으로, 사용자 여정의 마지막 단계("추적")를 보여준다.
칸반 목업은 카드 요소가 많아서 JSX에 하드코딩하면 금방 지저분해진다. 데모 데이터를 타입 있는 상수 배열로 분리했다.
interface DemoCard {
initial: string
chipBg: string
role: string
company: string
location: string
salary: string
match: number
note?: { text: string; tone: 'interview' | 'offer' }
emphasized?: boolean
}
const COLUMNS: DemoColumn[] = [
{
label: '준비 중',
dot: '#98A2B3',
cards: [
{ initial: 'S', chipBg: '#1DB954', role: '백엔드 엔지니어', company: 'Spotify', location: '스톡홀름, 스웨덴', salary: '€65K–85K', match: 82 },
// ...
],
},
{
label: '면접 진행',
dot: '#B45309',
cards: [
{ initial: 'G', chipBg: '#00B14F', role: '프로덕트 매니저', company: 'Grab', location: '싱가포르', salary: 'S$110K–140K', match: 91, note: { text: '2차 면접 · 6월 30일', tone: 'interview' } },
],
},
// ...
]
렌더링은 COLUMNS.map() 한 번이면 끝이고, 나중에 데모 시나리오를 바꾸고 싶으면 이 배열만 고치면 된다. 핸드오프의 디자인 토큰도 충실히 따랐다.
- 상태별 점 색상: 준비 중
#98A2B3/ 지원 완료#1A56DB/ 면접#B45309/ 오퍼#046C4E - 면접 일정 배지(
#FEF3E2배경 +#B45309텍스트)와 오퍼 배지(#ECFDF3+#046C4E)를note.tone으로 분기 - 회사 머리글자 칩은 각 사의 브랜드 색(
Spotify #1DB954,Grab #00B14F등) — 로고 이미지 없이도 카드가 "진짜 회사"처럼 보이게 하는 값싼 트릭
Spotify 백엔드 카드는 Step 2의 워크스페이스 데모와 같은 공고·같은 매칭률(82%)로 맞췄다. 스크롤을 내리며 세 섹션을 보면 "이 사람이 Spotify 공고에 이력서를 맞추고 → 다른 공고도 탐색하고 → 지원을 추적하는" 하나의 이야기가 이어진다.
Step 5. 조립 — 사용자 여정 순 배치와 배경 리듬
세 섹션의 배치는 기능 중요도가 아니라 사용자 여정 순이다.
<SplitHero t={t} searchHref={searchHref} />
<WorkspaceShowcase ctaHref={authed ? '/profile' : signupHref ?? '/login?mode=signup'} /> {/* 1. 번역·맞춤화 */}
<DiscoverShowcase /> {/* 2. 탐색 */}
<ApplicationsShowcase /> {/* 3. 추적 */}
<FeatureCards t={t} />
<StatsBand t={t} />
핵심 가치(이력서 번역)를 히어로 바로 아래에 놓아 방문 즉시 체감하게 하고, 나머지는 제품을 쓰는 흐름대로 따라온다. 섹션 배경은 회색 #F4F6F8 → 흰색 → #F7F8FA로 교차시켜, 목업 카드(흰색)가 어느 섹션에서든 배경과 분리돼 보이도록 리듬을 만들었다.
구현 디테일 몇 가지.
- 디자인 토큰은 Tailwind arbitrary value로 그대로 이식 — 핸드오프가 "정확한 hex·px 값을 그대로 사용하라"고 했으니
rounded-[14px],text-[13px],bg-[#ECFDF3],shadow-[0_24px_60px_-28px_rgba(4,108,78,0.28)]처럼 인라인 스타일 값을 클래스로 옮겼다. 디자인 시스템 변수로 승격할지는 나중 문제고, 일단 픽셀 정확도가 우선이다. - 전부 서버 컴포넌트 — 세 쇼케이스 모두 정적 데모라
'use client'가 한 줄도 없다. 랜딩에 화면 세 개 분량의 UI를 추가했지만 클라이언트 JS는 0바이트 늘었다. - Playwright 스크린샷으로 검증 — 배포 전에 로컬에서 랜딩 전체를 스크린샷 찍어 하이라이트 밑줄, 배지 색, 칸반 정렬을 눈으로 확인했다. 목업 재현 작업은 diff 리뷰보다 렌더링 결과를 보는 게 빠르다.
자주 쓴 패턴 요약
| 상황 | 패턴 |
|---|---|
| 랜딩에 제품 화면 보여주기 | 스크린샷·iframe 대신 정적 JSX 목업 — 반응형·유지보수·코드베이스 패턴 일치 |
| 디자인 핸드오프 활용 | "구현하고 끝"이 아니라 랜딩 데모의 원본 소재로 재활용 |
| 핸드오프에 없는 화면 | 실제 제품 UI를 역으로 목업화 |
| 인라인 텍스트 하이라이트 밑줄 | [box-shadow:inset_0_-2px_0_...] — border 없이 줄바꿈에도 자연스러움 |
| 목업 데이터 관리 | 타입 있는 상수 배열(COLUMNS)로 분리, JSX는 map만 |
| 데모의 생동감 | 실제 운영 수치(171건)·일관된 시나리오(Spotify 82%)를 데모에 반영 |
| CTA | 로그인 상태 분기 (비로그인→가입, 로그인→해당 기능) |
정리
- "기능이 좋다"는 말보다 화면 하나가 세다. 히어로 문구와 기능 카드로는 전달되지 않던 가치가, 한국어↔영어 이력서 비교 화면 하나로 즉시 전달된다. 방문자가 가입 전에 제품을 볼 수 있어야 한다.
- 디자인 핸드오프는 일회용 문서가 아니다. 구현이 끝난 핸드오프 파일이 몇 주 뒤 랜딩 데모의 원본 소재로 되살아났다. hifi 목업에 든 디자인 토큰·레이아웃·데모 데이터는 마케팅 자산이기도 하다.
- 재현이 임베드보다 낫다. iframe으로 박으면 10분이지만 커스텀 런타임 의존과 반응형 문제가 남는다. AI 어시스턴트가 있으면 "코드베이스 패턴으로 재현"의 비용이 확 낮아지므로, 핸드오프 README의 원칙을 지키지 않을 이유가 없었다.
- 실제 수치가 목업을 살린다. "Figma ✓ 171건 발견"의 171은 진짜 운영 데이터다. 그럴듯하게 지어낸 숫자와 달리, 실제 수치는 데모에 "방금 돌아간 제품"의 질감을 준다.
- 데모도 코드다. 상수 배열 분리, 서버 컴포넌트 유지, 타입 정의 — 목업이라고 대충 짜면 다음에 데모 시나리오를 바꿀 때 대가를 치른다.
이제 방문자는 matchda.com에 들어오자마자 제품이 실제로 일하는 모습 세 장면을 본다. "오~ 이 기능 좋은데"까지 갔는지는 — 다음 편에서 다룰 실제 사용자 반응이 말해줄 것이다.
backtodev
40대 PM, 다시 개발자로 돌아갑니다. 실패하고 배우며 성장하는 기록.