AgentSkillsCN

mvp-quick-build

快速构建MVP——以韩国初创企业的风格,迅速打造出仅包含核心功能的最小可行产品。

SKILL.md
--- frontmatter
name: mvp-quick-build
description: MVP 빠른 구축 - 한국 스타트업 스타일로 최소 기능만 빠르게 만들기

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분)

  1. Supabase 회원가입
  2. New Project 생성
  3. 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 (무료 플랜)