MVP 빠른 구축 스킬
한국 스타트업 스타일로 필요한 기능만 빠르게 만들어 시장에 출시합니다.
핵심 원칙
1. 완벽함보다 빠른 출시
- •❌ 모든 기능을 완벽하게
- •✅ 핵심 기능 하나만 제대로
2. 코드 품질보다 속도
- •❌ 완벽한 아키텍처, 테스트 커버리지 100%
- •✅ 작동하는 코드, 나중에 리팩토링
3. 검증 후 확장
- •❌ 처음부터 확장 가능한 구조
- •✅ 일단 검증하고 필요하면 다시 만들기
MVP 체크리스트
최소 기능만 포함
markdown
# 예시: 맛집 추천 앱 ## ✅ 포함할 것 (MVP) - [ ] 카카오 로그인 - [ ] 맛집 목록 보기 - [ ] 맛집 상세 정보 - [ ] 찜하기 ## ❌ 나중에 (v2) - 회원가입/로그인 (카카오 로그인으로 충분) - 리뷰 작성 (일단 정보만) - 추천 알고리즘 (수동 큐레이션으로 시작) - 관리자 페이지 (Notion/구글 시트로 대체) - 푸시 알림 - 포인트/쿠폰
빠른 스택 선택
Next.js + Supabase (가장 빠름)
bash
# 30분 안에 인증 + DB 완성 npx create-next-app@latest my-mvp cd my-mvp npm install @supabase/supabase-js @supabase/auth-helpers-nextjs
장점:
- •인증 내장 (소셜 로그인 5분 설정)
- •DB + API 자동 생성
- •무료 플랜으로 시작
- •배포 원클릭 (Vercel)
설정
typescript
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// 로그인 (1줄)
await supabase.auth.signInWithOAuth({ provider: 'kakao' });
// 데이터 조회 (1줄)
const { data } = await supabase.from('restaurants').select('*');
// 데이터 추가 (1줄)
await supabase.from('restaurants').insert({ name: '맛집' });
빠른 구현 패턴
1. 인증은 소셜 로그인만
typescript
// ❌ 이메일 회원가입 구현 (시간 낭비)
// - 이메일 인증
// - 비밀번호 재설정
// - 유효성 검증
// - 스팸 방지
// ✅ 카카오 로그인 하나로 끝
<button onClick={() => supabase.auth.signInWithOAuth({ provider: 'kakao' })}>
카카오로 3초 시작
</button>
2. 관리자 기능은 따로 만들지 않기
typescript
// ❌ 관리자 페이지 개발 // - 권한 관리 // - UI/UX 디자인 // - CRUD 화면 // ✅ Supabase Studio 사용 // 브라우저에서 바로 데이터 수정 // 별도 개발 불필요
3. 결제는 간단하게
typescript
// ❌ 여러 결제 수단
// - 카드, 계좌이체, 휴대폰
// - 정기 결제
// - 환불 처리
// ✅ 토스페이먼츠 간편결제만
import { loadTossPayments } from '@tosspayments/payment-sdk';
const tossPayments = await loadTossPayments(clientKey);
await tossPayments.requestPayment('카드', {
amount: 10000,
orderId: generateId(),
orderName: '서비스 이용권',
successUrl: '/payment/success',
failUrl: '/payment/fail',
});
4. 이미지는 외부 서비스
typescript
// ❌ 자체 이미지 서버 구축
// - S3 설정
// - CDN 연동
// - 리사이징
// ✅ Cloudinary 무료 플랜
import { CldImage } from 'next-cloudinary';
<CldImage src="my-image" width={300} height={300} crop="fill" />
5. 이메일은 무료 서비스
typescript
// ❌ SMTP 서버 설정
// ✅ Resend (무료 100통/일)
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: 'noreply@myapp.com',
to: user.email,
subject: '환영합니다!',
html: '<p>가입을 환영합니다!</p>',
});
실전 MVP: 맛집 앱 (2시간 개발)
1단계: 프로젝트 생성 (5분)
bash
npx create-next-app@latest foodie --typescript --tailwind --app cd foodie npm install @supabase/supabase-js
2단계: Supabase 설정 (10분)
- •Supabase 회원가입
- •New Project 생성
- •Table Editor에서 테이블 생성:
sql
-- restaurants 테이블 create table restaurants ( id uuid default gen_random_uuid() primary key, name text not null, address text, image_url text, rating numeric default 0, created_at timestamp default now() ); -- favorites 테이블 create table favorites ( user_id uuid references auth.users not null, restaurant_id uuid references restaurants not null, created_at timestamp default now(), primary key (user_id, restaurant_id) );
3단계: 홈 페이지 (30분)
typescript
// app/page.tsx
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
export default async function Home() {
const supabase = createServerComponentClient({ cookies });
const { data: restaurants } = await supabase
.from('restaurants')
.select('*')
.order('rating', { ascending: false })
.limit(20);
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-8">오늘의 맛집</h1>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{restaurants?.map((restaurant) => (
<div key={restaurant.id} className="border rounded-lg p-4">
<img
src={restaurant.image_url || '/placeholder.jpg'}
alt={restaurant.name}
className="w-full h-40 object-cover rounded"
/>
<h2 className="font-bold mt-2">{restaurant.name}</h2>
<p className="text-sm text-gray-600">{restaurant.address}</p>
<div className="flex items-center mt-2">
<span className="text-yellow-500">⭐</span>
<span className="ml-1">{restaurant.rating.toFixed(1)}</span>
</div>
</div>
))}
</div>
</div>
);
}
4단계: 카카오 로그인 (15분)
typescript
// app/login/page.tsx
'use client';
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
export default function Login() {
const supabase = createClientComponentClient();
const handleLogin = async () => {
await supabase.auth.signInWithOAuth({
provider: 'kakao',
options: {
redirectTo: `${location.origin}/auth/callback`,
},
});
};
return (
<div className="flex items-center justify-center min-h-screen">
<button
onClick={handleLogin}
className="bg-yellow-400 text-black px-8 py-3 rounded-lg font-bold"
>
카카오로 3초 시작
</button>
</div>
);
}
// app/auth/callback/route.ts
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const requestUrl = new URL(request.url);
const code = requestUrl.searchParams.get('code');
if (code) {
const supabase = createRouteHandlerClient({ cookies });
await supabase.auth.exchangeCodeForSession(code);
}
return NextResponse.redirect(new URL('/', requestUrl.origin));
}
5단계: 찜하기 기능 (20분)
typescript
// components/FavoriteButton.tsx
'use client';
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import { useState } from 'react';
export function FavoriteButton({ restaurantId }: { restaurantId: string }) {
const [isFavorite, setIsFavorite] = useState(false);
const supabase = createClientComponentClient();
const toggleFavorite = async () => {
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
alert('로그인이 필요합니다!');
return;
}
if (isFavorite) {
await supabase
.from('favorites')
.delete()
.eq('user_id', user.id)
.eq('restaurant_id', restaurantId);
} else {
await supabase.from('favorites').insert({
user_id: user.id,
restaurant_id: restaurantId,
});
}
setIsFavorite(!isFavorite);
};
return (
<button onClick={toggleFavorite}>
{isFavorite ? '❤️' : '🤍'}
</button>
);
}
6단계: 배포 (5분)
bash
# GitHub에 푸시 git add . git commit -m "MVP 완성" git push # Vercel 배포 npx vercel --prod
완성! 🎉
- •✅ 맛집 목록
- •✅ 카카오 로그인
- •✅ 찜하기
- •✅ 배포 완료
MVP 이후 빠른 검증
A/B 테스트 (Google Analytics)
typescript
// 간단한 이벤트 추적
'use client';
import { useEffect } from 'react';
export function TrackView({ pageName }: { pageName: string }) {
useEffect(() => {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', 'page_view', {
page_name: pageName,
});
}
}, [pageName]);
return null;
}
피드백 수집 (Tally 무료 폼)
html
<!-- 간단한 피드백 버튼 -->
<button onclick="window.open('https://tally.so/r/your-form', '_blank')">
피드백 주기
</button>
시간 절약 팁
1. UI는 Shadcn/ui
bash
npx shadcn-ui@latest init npx shadcn-ui@latest add button card
2. 아이콘은 Lucide
typescript
import { Heart, Star, MapPin } from 'lucide-react';
<Heart className="w-5 h-5" />
3. 폰트는 Google Fonts
typescript
// app/layout.tsx
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
4. 색상은 Tailwind 기본값
typescript
// ❌ 커스텀 색상 정의 // ✅ Tailwind 기본 색상 사용 <div className="bg-blue-500 text-white">
피해야 할 것
❌ 과도한 최적화
typescript
// MVP 단계에서 불필요: // - 이미지 최적화 (나중에) // - 번들 사이즈 최적화 (나중에) // - SSR/SSG 최적화 (나중에) // - 캐싱 전략 (나중에)
❌ 완벽한 디자인
typescript
// MVP 단계에서: // - Figma 디자인 (불필요) // - 브랜드 가이드라인 (불필요) // - 반응형 완벽 대응 (모바일만) // - 애니메이션 (불필요)
❌ 확장성 고려
typescript
// MVP 단계에서: // - 마이크로서비스 (불필요) // - 복잡한 상태 관리 (불필요) // - 추상화/인터페이스 (불필요) // - 디자인 패턴 (불필요)
2주 출시 체크리스트
markdown
### Week 1 - [ ] Day 1-2: 핵심 기능 정의 (1가지만!) - [ ] Day 3: DB 스키마 설계 - [ ] Day 4-5: 프론트엔드 개발 - [ ] Day 6-7: 백엔드 API 개발 ### Week 2 - [ ] Day 8-9: 통합 테스트 - [ ] Day 10: 버그 수정 - [ ] Day 11: 배포 - [ ] Day 12: 베타 테스터 모집 - [ ] Day 13-14: 피드백 수집 및 개선
실패하더라도 빠르게
"빠르게 실패하고, 빠르게 배우고, 빠르게 피봇하기"
- •2주 만에 시장 검증
- •안되면 다음 아이디어로
- •6개월 개발하고 실패하지 않기
무료 도구 모음
- •인증: Supabase Auth
- •DB: Supabase (PostgreSQL)
- •배포: Vercel
- •도메인: .xyz (연 $1)
- •이메일: Resend (100통/일)
- •이미지: Cloudinary (25GB/월)
- •결제: 토스페이먼츠 (수수료만)
- •분석: Google Analytics
- •폼: Tally (무제한)
- •노코드 관리자: Retool (무료 플랜)