아직은 MVP를 만들기 위한 과정
어떤 주제로 MVP를 만들지 오래 고민했다. 육아 기록 앱으로 정하고, 과제 마감에 맞춰 하루 동안 Claude Code로 기획부터 배포까지 한 구간을 이어봤다.
아직 MVP는 아니다. 아래는 MVP를 만들어가는 과정에서 오늘 진행한 부분의 기록이다.
오늘 확인하려던 것은 기능의 완성도가 아니라 경로가 끝까지 이어지는지였다.
기획 → 구현 → 배포 → 실제 가입 · 로그인 · 기록 저장
경로는 이어졌다. 배포된 주소에서 가입, 이메일 인증, 로그인, 아기 등록, 수유 기록까지 동작한다. 다만 MVP 범위로 잡은 것 중 절반가량은 아직 만들지 않았다.
| 항목 | 값 |
|---|---|
| 대상 | 육아 기록 웹앱 (수유 · 수면 · 기저귀 · 성장) |
| 스택 | Next.js 16 · TypeScript · Tailwind CSS 4 · Supabase · Vercel |
| 오늘 진행 | 계획한 24개 작업 단위 중 10개 |
| 커밋 | 31개 |
| 변경 | 82개 파일 (+8,785 / −1,335) |
| 문서 | 10종 약 3,800줄 |
| 테스트 | 69건 |
무엇을 만들려는가
기록을 기억에 의존하면 “방금 먹였는지”를 매번 되묻게 되고, 종이나 메모앱에 적어도 집계가 되지 않는다. 수치가 쌓여도 그게 정상 범위인지 판단할 근거가 없다.
그래서 두 가지를 목표로 잡았다.
- 입력이 짧을 것 — 탭 두 번, 3초 안에 기록이 끝나야 한다
- 숫자에 맥락을 줄 것 — 같은 월령 아기들의 중간 범위와 함께 보여준다
두 번째는 아직 만들지 않았다. 오늘은 첫 번째와 그 아래 깔릴 기반까지 진행했다.
오늘 진행한 것
1. 기획 문서
코드 작성 전에 제품 요구사항 문서와 기술 명세서를 먼저 만들었다. 이후 결정 사항 9건을 확정했다.
| 항목 | 결정 |
|---|---|
| 이메일 인증 | 필수 |
| 아기 여러 명 등록 | 포함 (최대 5명) |
| 커뮤니티(사진·글) | 포함 |
| 비로그인 열람 | 불가 |
| 얼굴 블러 | 제공하되 자체 구현하지 않음 |
| 결제 | 이번 범위 제외 |
전체 구현 범위는 24개 작업 단위로 나눴다. 한 단위의 상한은 파일 8개, 새 코드 400줄, 60분으로 정하고 단위마다 검증하도록 했다.
2. 저장소와 배포 연결
기능 구현 전에 GitHub 저장소와 Vercel 배포를 먼저 연결했다. 이 시점에 올라간 것은 랜딩과 로그인 화면뿐이다.
첫 푸시는 GitHub 이메일 비공개 설정 때문에 거부됐고, 커밋 작성자를 noreply 주소로 바꿔 해결했다.
3. 기능 구현
| 기능 | 내용 |
|---|---|
| 인증 | 가입 · 로그인 · 비밀번호 재설정 · 이메일 인증 대기 화면 |
| 아기 | 등록 · 수정 · 여러 명 전환 · 일령/월령 계산 |
| 기록 | 수유(분유·모유·이유식) · 수면(시작/종료) · 기저귀 · 성장 |
| 조회 | 홈 상태 위젯 · 타임라인 · 하루 요약 |
| 법적 고지 | 이용약관 · 개인정보처리방침 · 커뮤니티 가이드라인 (초안) |
4. Supabase 연결
로그인은 직접 구현하지 않고 Supabase Auth를 사용했다.
기존에 쓰던 Supabase 프로젝트를 공유할지 검토했으나 전용 프로젝트를 새로 만들었다. Supabase는 로그인 사용자 정보가 프로젝트당 하나의 테이블에 모이기 때문에, 두 앱이 한 프로젝트를 쓰면 한쪽의 회원 탈퇴가 다른 앱 데이터까지 연쇄 삭제한다. 인증 설정도 프로젝트 단위라 앱별로 분리할 수 없다.
데이터베이스는 테이블 8개, RLS 정책 16개로 구성했고 모든 테이블에 행 수준 보안을 켰다.
5. 실제 동작 확인
배포된 주소에서 새 계정으로 가입 → 이메일 인증 → 로그인 → 아기 등록 → 수유 기록까지 확인했다. 로컬 개발 서버를 끈 상태로 진행해 배포본 자체의 동작임을 확인했다.
발생한 문제와 조치
인증 메일 링크가 localhost로 연결됨
가입 후 인증 메일의 링크를 누르면 ERR_CONNECTION_REFUSED가 발생했다.
원인은 Supabase의 Redirect URL 허용 목록이 비어 있던 것이다. 앱이 보낸 복귀 주소가 허용 목록에 없으면 Supabase는 오류를 반환하지 않고 기본값(Site URL)으로 대체한다. 기본값이 localhost:3000이었다.
로컬 개발 서버가 켜져 있으면 정상 동작하므로 개발 중에는 드러나지 않고 배포본에서만 재현된다. 에러 로그도 남지 않는다.
조치 — Site URL을 배포 주소로 바꾸고 Redirect URL 3종(배포 주소, 로컬, 배포별 고유 URL 와일드카드)을 등록했다. 재발 확인용 스크립트를 추가했다.
보안 헤더 5종 누락
배포본 점검 결과 X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, HSTS가 없었다. Next.js는 이 헤더들을 기본으로 추가하지 않는다.
조치 — next.config.ts에 헤더 설정을 추가하고 재배포 후 적용을 확인했다.
입력 필드 테두리 대비 기준 미달
WCAG 기준으로 계산한 결과 입력 필드 테두리의 명도 대비가 1.23 대 1이었다. 비텍스트 요소 기준은 3 대 1이다.
조치 — 조작 요소 전용 색상을 분리해 라이트 3.37, 다크 3.67로 조정했다. 같이 점검하다 outline-none이 키보드 포커스 표시를 제거하고 있던 것도 발견해 포커스 링을 일괄 적용했다.
환경변수 등록 스크립트가 실패를 표시하지 않음
스크립트가 완료로 출력했으나 실제로는 하나도 등록되지 않았다. 등록 명령의 출력을 버리고 무조건 성공 표시를 찍고 있었다.
조치 — 항목별로 성공·실패를 판정하고 실패 시 원인을 출력하며 종료 코드 1을 반환하도록 고쳤다.
검증
완료 판정은 실행 가능한 명령으로 정의했다.
1
npm run build && npx vitest run && npx eslint .
반복 확인을 위해 검증 스크립트 6개를 만들었다.
| 대상 | 항목 수 | 결과 |
|---|---|---|
| 배포본 보안 (헤더 · 시크릿 · 비로그인 접근) | 22 | 통과 |
| Supabase 연결 · 스키마 · RLS | 15 | 통과 |
| 가입 · 로그인 · 연결 안정성 | 9 | 통과 |
| 인증 리다이렉트 허용 목록 | 3 | 통과 |
| 단위 테스트 | 69 | 통과 |
연결 안정성은 연속 8회 요청에서 실패 0건, 응답 시간 중앙값 86ms였다. 로그인 실패 시 “없는 계정”과 “비밀번호 오류”의 응답이 동일해 계정 존재 여부가 노출되지 않는 것도 확인했다.
MVP까지 남은 것
지금 동작하는 것
- 가입 · 이메일 인증 · 로그인 · 로그아웃
- 아기 등록과 여러 명 전환
- 수유 · 수면 · 기저귀 · 성장 기록
- 타임라인과 하루 요약
남은 것
| 항목 | 비고 |
|---|---|
| 계정 간 데이터 격리 검증 | 다른 계정의 기록에 접근되는지 아직 확인하지 않음 |
| CSP 적용 | 인증 흐름과 함께 넣어야 함 |
| 또래 비교 통계 | 월령 구간별 집계. 표본 20명 미만이면 미표시 |
| 커뮤니티 | 사진 · 글 공유 |
| 기록 수정 · 삭제, 주간 차트 | |
| PWA | 홈 화면 추가 |
| 법적 고지 확정 | 현재 초안. 법률 검토 전 |
작업 단위 기준으로 24개 중 10개를 진행했다. 다음은 계정 간 데이터 격리 검증과 CSP 적용부터 이어간다.