2026-05-05 · 사이드 프로젝트 일지

비개발자가 하루에 사이드 프로젝트 3종 라이브 배포까지

Claude Code Max 하나로 랜딩페이지·AI 챗봇·n8n 자동화 자료까지 — Supabase + Vercel만 쓰는 미니멀 스택.

1. 왜 이걸 만드나

한국 외주 시장(크몽·숨고)에서 가장 수요 큰 게 AI 챗봇 외주(9개월 1.2만 건). 그 다음이 자동화·랜딩페이지. 영업하려면 "이런 거 만들 수 있어요" 보여줄 데모가 있어야 함. 3종을 하루에 묶어서 라이브 URL로 만들기로 결정.

운영 원칙: 인프라 스택은 Vercel + Supabase 두 가지로만 한정. AWS, Firebase, Clerk, Auth0 등 다른 거 안 씀. 외부 의존 늘릴수록 디버깅 비용 늘어남. 최소 스택으로 묶어두고 Claude Code가 그 안에서 자유롭게 일하게 함.

2. Supabase MCP — 한 번 등록해두면 자동화 끝장

Claude Code에 Supabase MCP 서버를 프로젝트 스코프로 등록:

claude mcp add --scope project --transport http supabase \
  "https://mcp.supabase.com/mcp?project_ref=<project_ref>"

이거 하면 Claude가 직접 list_projects, apply_migration, execute_sql, get_publishable_keys 같은 툴을 써서 DB 만지고 마이그레이션 적용함. 비개발자가 SQL 직접 안 쳐도 됨.

MCP로 자동화 가능한 키 vs 수동

MCP 자동?
Project URL✅ 자동
Publishable / anon key✅ 자동
Service role / secret key❌ 보안상 미노출 — 수동 복사

참고로 Supabase가 2025년에 키 시스템을 개편했음. 옛날 service_role JWT는 이제 sb_secret_* 형식으로 바뀌고, 대시보드 "Secret keys" 탭에 들어있음. 환경변수 이름은 SUPABASE_SERVICE_ROLE_KEY 그대로 두고 값만 새 형식으로 넣으면 됨.

3. 마이그레이션 + RLS + 보안 advisor

3개 마이그레이션 파일을 한 번에 적용:

모든 테이블 RLS(Row-Level Security) 활성화. 그 다음 Supabase advisor 돌리면 자동으로 보안 이슈 잡아줌:

// advisor가 잡은 거
- function_search_path_mutable on public.set_updated_at
- handle_new_user is callable via REST as anon role

// hardening 마이그레이션 한 번 더 적용
create or replace function public.set_updated_at()
  returns trigger language plpgsql
  set search_path = public, pg_temp
  as $$ ... $$;

revoke execute on function public.handle_new_user()
  from anon, authenticated, public;

4. 모델 교체 여정 — 오늘의 하이라이트

03 챗봇은 처음 Claude Sonnet 4.6으로 만들었음. 외주 데모인데 매번 토큰 비용 = 영업 ROI 안 좋음. → 무료 모델로 교체.

1차: Gemini 2.5 Flash

Google AI Studio 무료 키 (일 1500회 — 데모 충분). SDK 교체하고 role 매핑 처리:

// Anthropic role "assistant" → Gemini role "model"
const contents = messages.map((m) => ({
  role: m.role === "assistant" ? "model" : "user",
  parts: [{ text: m.content }],
}));

로컬 테스트 OK. Vercel 배포 후 첫 호출 OK. 두 번째 호출... 503 UNAVAILABLE. 반복.

진단 — 어디가 문제냐

호출 위치결과
로컬 → Google 직접 (curl)✅ 100% 성공 (3/3)
로컬 → Google streaming + system instruction✅ 성공
Vercel 서버 → Google❌ 100% 503 (3/3)

코드/모델/system prompt 문제 아님. 추정: Vercel egress IP 풀이 Google 무료 티어에서 다른 수천 사용자와 quota 공유 → throttle. 더 가벼운 gemini-2.5-flash-lite로 바꿔봐도 동일.

2차: Groq Llama 3.3 70B

완전 무료 + 신용카드 등록 불필요 + 분 30회 / 일 1000회 (데모용 차고 넘침). OpenAI 호환 API라 SDK 단순:

const apiStream = await groq.chat.completions.create({
  model: "llama-3.3-70b-versatile",
  messages: [
    { role: "system", content: SYSTEM_PROMPT },
    ...messages,
  ],
  temperature: 0.5,
  max_tokens: 500,
  stream: true,
});

배포 후 3회 연속 호출 → 3/3 모두 성공. 한국어 답변 품질도 FAQ 챗봇 용도면 충분. 안정화 완료.

교훈: Vercel + Google 무료 LLM 조합은 데모 용도로도 비현실적. Groq가 사이드 프로젝트 무료 데모에 훨씬 적합.

5. Vercel 배포 함정 3가지

① 첫 배포 자동 prod

git 미연결 프로젝트의 첫 vercel deploy는 자동으로 prod 타겟으로 감. 의도와 달리 라이브가 됨. 두 번째 배포부터는 같은 명령이 preview로 떨어지고 prod 가려면 --prod 명시 필요. 일관성 없음.

② Preview env CLI 버그

git 미연결 프로젝트에 preview 환경변수 추가가 사실상 안 됨:

$ vercel env add NEXT_PUBLIC_SUPABASE_URL preview --value "..." --yes
{
  "status": "action_required",
  "reason": "git_branch_required",
  "message": "Add to which Git branch? Pass branch as third argument..."
}

"all preview branches"로 추가하라는 제안 명령을 실행해도 같은 에러. 우회법: --build-env로 빌드 시점에 직접 주입:

vercel deploy \
  --build-env "NEXT_PUBLIC_SUPABASE_URL=$URL" \
  --build-env "NEXT_PUBLIC_SUPABASE_ANON_KEY=$KEY"

③ 셸 env가 .env.local을 덮어씀

Next.js는 이미 셸에 set된 변수는 .env.local로 덮어쓰지 않음. 옛날에 .zshrc에 export한 만료된 키가 있으면 새 키 입력해도 무시됨. dev 서버에서 503 / 인증 실패가 나올 때 의심해볼 곳:

$ env | grep -iE "(GOOGLE|GEMINI|GROQ|ANTHROPIC)_API_KEY"
GOOGLE_API_KEY=AIzaSy...<- 만료된 옛 값
$ grep "API_KEY" ~/.zshrc

6. CLAUDE.md 가드레일 — AI 폭주 방지

프로젝트 루트에 CLAUDE.md 두고 운영 정책 박아둠. 핵심 룰 3개:

  1. API 호출 실패 → 자동 우회/fallback/mock 금지. 원본 에러 그대로 사용자에게 보고하고 즉시 작업 중단.
  2. 외부 패키지/서비스 도입 → 사용자에게 먼저 묻기.
  3. 프로덕션 배포 → 사용자 명시 승인 후에만.

오늘 실수로 vercel deploy 한 번이 prod로 자동 배포됨. Claude가 즉시 "정책 위반에 가까운 상황입니다"라고 보고. 사용자가 "포트폴리오 더미는 prod 직행 OK"라는 예외 룰을 추가하고 진행. 가드레일이 실제로 작동.

7. 정리 — 하루 산출물

비개발자/주니어가 Claude Code Max 활용해 하루에 이만큼. 외주 시작 가능 상태. 다음 단계는 영업.