바이브코딩 2일차: 설계 없이 시작하면 AI도 헤맨다

# 바이브코딩 2일차: 설계 없이 시작하면 AI도 헤맨다

## 시작하게 된 계기

나는 4년차 백엔드 개발자다. API를 설계하고, DB 스키마를 짜고, 서버 로직을 구현하는 건 익숙하지만 React 컴포넌트 하나 제대로 만들어본 적이 없다. Tailwind가 뭔지는 알지만 실제로 써본 적은 없고, Next.js는 이름만 들어봤다.

그런 내가 기획부터 프론트엔드 구현까지 Claude Code 하나로 진행해봤다. 결론부터 말하면 "할 수 있다"인데, 생각보다 비효율적인 부분도 많았고 중간에 접근 방식을 바꾸면서 많은 걸 배웠다.

---

## 만들려 한 것: TripLog

아이디어는 단순했다. 나는 여행을 좋아하는데, 어디를 갔었는지 돌아보기가 쉽지 않다. Google Maps Timeline이 내 이동 기록을 다 가지고 있다는 걸 알았고, 이걸 활용해서 여행을 예쁘게 기록하고 통계로 보여주는 앱을 만들고 싶었다.

요구사항을 정리하면 이렇다:

- Google Takeout에서 위치 기록 다운로드 → 앱에 업로드

- 여행 날짜를 지정하면 해당 기간의 위치 데이터를 자동으로 가져옴

- 지도, 일별 타임라인, 지출 통계를 시각화

- 모바일 중심 UI (430px)

기술 스택은 Claude에게 물어봤다. Next.js + Supabase + Vercel 조합을 추천받았고, 별다른 이견 없이 따랐다. 백엔드 개발자로서 Supabase의 PostgreSQL + RLS 구조는 익숙했고, 나머지는 Claude가 알아서 해주겠지 싶었다.

---

## 1단계: 설계 없이 바로 구현 시작

첫 번째 실수가 여기서 시작됐다.

시간 여유가 없어서 "일단 해보자"는 마인드로 Claude에게 바로 코드를 요청했다. 와이어프레임을 주고 "이렇게 만들어줘"라고 했더니 Next.js 프로젝트 구조, 컴포넌트, 페이지가 순식간에 만들어졌다. 백엔드 개발자 입장에서는 마법 같았다.

문제는 설계 문서 없이 시작했다는 것이다.

Claude는 매 대화가 독립적이다. 어제 내가 어떤 컴포넌트를 만들었는지, 무슨 색상을 쓰기로 했는지, DB 스키마가 어떻게 생겼는지를 다음 대화에서 처음부터 다시 설명해야 했다. 그러다 보니:

- 같은 색상값을 여러 방식으로 사용 (`blue-500` vs #3b82f6)

- 더미 데이터를 화면에 넣어놓고 나중에 실제 데이터로 교체하는 작업을 반복

- 비슷한 버그를 여러 곳에서 각각 수정

특히 더미 데이터 문제가 컸다. UI를 먼저 만들면서 임시로 하드코딩한 데이터들이 나중에 실제 데이터 연동 작업을 할 때 하나씩 발목을 잡았다. 여행 목록, 홈 대시보드 통계, 여행 상세 페이지 각각에서 "이게 왜 더미 데이터야?"를 반복해서 발견하게 됐다.

---

## 2단계: Supabase 연동과 Google Takeout 파싱

백엔드는 내가 잘 아는 영역이라 비교적 수월했다. Supabase 프로젝트를 만들고 스키마를 짜는 건 Claude와 함께 빠르게 진행됐다.

```

trips → locations → expenses

```

이 3개 테이블로 여행 기록을 구성하는 구조는 명확했다. RLS 정책도 설정했다. 다만 로그인 구현 전이라 당장 데이터를 넣을 수 없어서 임시로 RLS를 비활성화하고 user_id를 nullable로 변경했다. 기술 부채가 하나 생긴 거다.

Google Takeout 파싱은 예상보다 복잡했다.

처음에는 Google의 위치 기록 파일 포맷이 하나일 거라고 생각했다. 하지만 실제로 파일을 열어보니 2024년 이후 포맷이 완전히 바뀌어 있었다.

구형 포맷 (2023 이전):

```json

{ "locations": [{ "latitudeE7": 374220000, "longitudeE7": 1268520000 }] }

```

신형 포맷 (2024+):

```json

[{ "startTime": "...", "visit": { "topCandidate": { "placeLocation": "geo:37.422,126.852" } } }]

```

좌표 표현 방식도 다르고(E7 vs geo:lat,lng), 구조 자체가 달랐다. Claude가 처음에 구형 포맷 기준으로 파서를 짰는데, 내 실제 파일이 신형 포맷이라 아무것도 파싱이 안 됐다. 파일 구조를 직접 열어보고 Claude에게 보여주면서 새 포맷 지원을 추가했다.

---

## 3단계: 반복되는 버그들

구현을 진행하면서 같은 종류의 버그가 여러 번 등장했다. 대표적인 게 타임존 문제다.

여행 날짜를 new Date('2026-02-12')로 파싱하면 UTC 자정이 된다. 한국(KST +9)에서는 이게 2월 12일 오전 9시다. setHours(0,0,0,0)으로 로컬 자정으로 맞추면 2월 12일 00:00 KST = 2월 11일 15:00 UTC가 된다. 결과적으로 2월 11일 데이터까지 포함되는 버그가 생겼다.

이 버그가 세 군데에서 각각 다른 증상으로 나타났다:

1. 새 여행 저장 시 → 날짜 범위 밖 위치 데이터가 저장됨

2. 여행 상세 페이지 → "Day -1", "Day 0" 표시

3. 위치 개수 불일치 → 목록에서 66곳, 상세에서 57곳

설계 단계에서 "날짜는 항상 로컬 파싱" 규칙을 정했다면 한 번만 고쳤을 문제를 세 번 고쳤다.

---

## 4단계: 장소명 문제와 해결 과정

위치 데이터는 좌표밖에 없었다. 화면에 "위치 1", "위치 2"만 표시되는 건 쓸모가 없었다.

해결 방법을 Claude와 논의했다:

- Google Places API (place_id 활용, 월 $200 무료 크레딧)

- OpenStreetMap Nominatim (완전 무료, rate limit 있음)

Google Places API를 선택하고, 구현 방식을 고민했다. 처음에는 상세 페이지 로딩 시 API를 호출하는 방식으로 구현했는데, 이건 진짜 백그라운드가 아니었다. 페이지가 로드되고 "장소명을 불러오는 중..." 배너가 뜨면서 이름이 채워지는 방식이었다.

더 나은 방법으로 Supabase Edge Function + Database Webhook을 선택했다.

```

locations 테이블 INSERT 발생

↓ (Supabase 자동 감지)

Database Webhook 발동

↓

Edge Function 실행 (Deno 런타임)

↓

Google Places API 호출

↓

locations.name 업데이트

```

여행을 저장하는 순간부터 백그라운드에서 장소명이 채워지고, 다음에 페이지를 열면 이미 이름이 들어있다. API 호출도 1회만 하고 DB에 캐시되니 비용도 절약된다.

다만 초기에 Geocoding API를 폴백으로 사용했더니 "495-2" 같은 번지수가 반환됐다. Places Nearby Search (반경 100m)로 교체하니 실제 장소명이 나왔다.

---

## 5단계: 뒤늦게 깨달은 것들

중반부에 이런 글을 읽었다: [바이브코딩으로 AI 서비스 개발하기](https://www.gpters.org/dev/post/developing-service-using-vibe-kw6PTCRn4Yfhjwa)

핵심은 AI에게 규칙을 먼저 알려주고 코딩하게 하라는 거였다. 설계 문서, 디자인 시스템, 코딩 컨벤션을 먼저 정의하고, Skill과 Hook으로 AI가 항상 그 규칙을 따르게 만드는 방식이다.

뒤늦게 CLAUDE.md를 만들었다. 내용은 이렇다:

- 프로젝트 구조와 파일 역할

- DB 스키마 요약

- 디자인 시스템 (컬러, 컴포넌트 패턴)

- 코딩 컨벤션 (특히 날짜 처리 규칙)

- 알려진 이슈와 해결 방법

Claude Code는 프로젝트 루트의 CLAUDE.md를 매 대화 시작 시 자동으로 로드한다. 이 파일 하나가 있었다면 같은 버그를 세 번 고치는 일은 없었을 거다.

TODO.md도 만들어서 완료/진행중/대기 작업을 관리하기 시작했다. .claude/settings.json에 Hook을 설정해서 파일 수정 시마다 핵심 규칙을 리마인드하도록 했다.

---

## 비효율적이었던 것들

1. 설계 없이 시작한 것

와이어프레임만 주고 "만들어줘"를 반복했다. 색상 시스템, 날짜 처리 규칙, 컴포넌트 패턴 같은 것들을 사전에 정의했다면 나중에 수정할 일이 훨씬 줄었을 것이다.

2. 더미 데이터 전략

UI 작업 때 더미 데이터를 넣고 "나중에 실제 데이터로 교체하자"고 미뤘는데, 이게 두고두고 발목을 잡았다. 처음부터 빈 상태(empty state)로 시작하고 API 연동과 함께 진행했다면 더 효율적이었을 것이다.

3. 컨텍스트를 매번 다시 설명

"이 프로젝트는 Next.js + Supabase를 쓰고, 모바일 430px 레이아웃이에요"를 대화할 때마다 반복했다. CLAUDE.md가 있었다면 필요 없는 작업이었다.

4. CLI 도구 설치 이슈

Supabase CLI를 npm으로 설치하려다 실패하고, Homebrew를 시도하다 macOS 베타 버전 문제로 또 막혔다. 결국 Dashboard에서 직접 배포했는데, 이런 환경 이슈는 미리 파악해두는 게 좋다.

---

## 잘 된 것들

1. 백엔드 지식이 있으면 훨씬 유리하다

Supabase 스키마 설계, RLS 정책, Edge Function 로직은 백엔드 지식이 있으니 Claude의 코드를 검토하고 방향을 잡는 게 수월했다. "이 구조는 별로인데"를 캐치할 수 있었다.

2. 모르는 기술 스택의 빠른 학습

Next.js App Router의 서버/클라이언트 컴포넌트 구분, Tailwind CSS 클래스 패턴 같은 것들을 구현하면서 자연스럽게 익혔다. 문서를 읽고 공부하는 것보다 훨씬 빨랐다.

3. 복잡한 파싱 로직

Google Takeout의 다양한 포맷 지원, Haversine 공식을 이용한 위치 중복 제거, 타임존을 고려한 날짜 필터링 같은 복잡한 로직을 빠르게 구현할 수 있었다.

4. 아키텍처 선택

장소명 채우기를 브라우저에서 처리하다가 Supabase Edge Function + Database Webhook으로 이전한 것. 서버리스 백그라운드 처리를 AI와 함께 빠르게 구현했다.

---

## 지금까지 구현된 것

약 2일의 작업으로 완성된 것들:

- ✅ 모바일 웹앱 전체 레이아웃 (홈, 여행 목록, 새 여행, 상세 페이지)

- ✅ Supabase DB 연동 (trips, locations, expenses)

- ✅ Google Takeout 파싱 (2024+ 신형식 포함)

- ✅ 위치 중복 제거 알고리즘 (Haversine)

- ✅ 장소명 자동 채우기 (Edge Function + Google Places API)

- ✅ 실제 데이터 기반 통계 (방문 국가, 총 여행 수, 총 지출)

- ✅ CLAUDE.md / TODO.md / Hook 기반 개발 환경

남은 작업: Google 로그인, 지출 기능, 지도 연동, 배포

---

## 결론: 이 프로젝트에서 컨텍스트 엔지니어링은 거의 없었다

솔직히 말하면 이번 프로젝트는 컨텍스트 엔지니어링이 거의 적용되지 않은 상태로 진행됐다.

컨텍스트 엔지니어링이란 AI가 더 잘 동작하도록 맥락을 설계하는 것이다. 단순히 "이렇게 만들어줘"가 아니라, AI가 일관된 규칙 안에서 움직이도록 환경 자체를 세팅하는 것이다. Claude Code에서는 이게 구체적으로 이런 것들이다:

- CLAUDE.md: 프로젝트 구조, 컨벤션, 디자인 시스템을 AI가 매번 자동으로 읽는 문서

- Skill: 반복 작업을 슬래시 커맨드로 정의 (`/new-page`, /supabase-query 등)

- Hook: 코드 작성 전/후에 자동으로 실행되는 규칙 체크

- Memory: 프로젝트를 넘어서 축적되는 AI의 장기 기억

이번 프로젝트에서 한 건 개발이 거의 끝난 시점에 CLAUDE.md와 기본적인 Hook 하나를 만든 것뿐이다. 그것도 같은 버그를 세 번 고치고, 더미 데이터를 반복해서 교체하고, 매 대화마다 프로젝트 구조를 다시 설명한 뒤에야 필요성을 깨달았다.

---

## 이어서 진행하고 싶은 것

컨텍스트 엔지니어링을 처음부터 적용하면 어떻게 달라질지 생각해봤다.

1. 코딩 시작 전에 CLAUDE.md 먼저 작성

구현에 들어가기 전에 AI와 함께 설계 문서를 먼저 만든다. 기술 스택, 파일 구조, DB 스키마, 디자인 토큰, 코딩 컨벤션을 전부 정리한다. 이번에 타임존 버그를 세 번 고친 것처럼, 규칙이 없으면 같은 실수가 반복된다.

2. Skill로 반복 패턴 정의

새 페이지를 만들 때마다 "서버 컴포넌트로 만들고, Supabase는 server 클라이언트 쓰고, 빈 상태 화면도 포함하고..."를 매번 말하는 대신, /new-page 슬래시 커맨드를 만들어두면 된다. 이 프로젝트에서 반복됐던 패턴들이 꽤 있었다.

3. Hook으로 자동 검증

코드 작성 후 자동으로 코드 리뷰가 돌도록 PostToolUse Hook을 설정한다. "날짜를 new Date('YYYY-MM-DD')로 파싱했는지", "서버 컴포넌트에서 클라이언트 훅을 쓰지 않는지" 같은 체크를 사람이 직접 하지 않아도 된다.

4. 더미 데이터 없이 시작

UI와 데이터 연동을 분리하지 않는다. 빈 상태(empty state) 화면을 먼저 만들고, 실제 API 연동과 함께 진행한다. 더미 데이터는 나중에 치우는 비용이 생각보다 크다.

---

AI 코딩 도구는 빠르게 발전하고 있고, 프롬프트를 잘 쓰는 것보다 컨텍스트를 잘 설계하는 것이 점점 더 중요해지고 있다. 이번 프로젝트는 그걸 몸으로 배운 과정이었다. 다음 프로젝트에서는 처음부터 제대로 해볼 생각이다.

1
지피터스 뉴스레터

이번 주 AI, 딱 쓸 것만

매주 월요일 오후,
읽고 바로 써먹을 AI 소식만