유대선

진행 중 · 2026년 5월 31일

곁 · Beside

나를 아끼는 사람들의 응원 — 목소리·사진·다정한 한마디·짧은 영상 — 을 비공개로 품어두고 무너지는 날 꺼내 보는 설치형 PWA. 같은 코드가 외부 계정 0개 로컬에서 환경변수만으로 완전한 프로덕션 인프라로 전환.

6,822 LOC · 26 routes · local→prod by env var · Terraform

역할
Solo (Claude Code 페어 프로그래밍)
스택
Next.js 16 · React 19 · TypeScript · PostgreSQL · Drizzle · PWA · Web Push · S3 / R2 · Terraform · Tailwind v4
곁 · Beside

나를 지탱하는 것들 — 사랑하는 사람의 음성, 사진, 아끼는 책 속 한 구절, "괜찮아질 거야"라고 말해주는 20초짜리 영상 — 을 담아두고, 힘들 때 한 번에 하나씩 차분히 꺼내 보는 비공개 "보관함". 소중한 사람은 가입도 설치도 없이 링크로 한마디를 보탬. 제품은 일부러 단순하게, 진짜 작업은 그 아래 엔지니어링에 있음.

무엇을 하는가

무너지는 순간에 쓰는 제품이라 빠르게 열리고 임상적이지 않게 차분해야 함. 핵심 인사이트: 내가 직접 담을 수 있어서, 아무도 기여하지 않아도 첫날부터 쓸모 있음 — "친구에게 부탁하세요" 류 앱을 죽이는 콜드스타트 문제에 대한 의도된 답.

훔쳐갈 만한 한 가지 아이디어

같은 코드가 로컬에선 외부 계정 0개 로 동작하고(임베디드 Postgres인 PGlite·로컬 디스크 미디어·자동 생성 세션/VAPID 시크릿), 환경변수별·키별 로 완전한 프로덕션 인프라로 전환됨: DATABASE_URL → node-postgres, S3_BUCKET → S3/R2 5분 서명 URL, VAPID_* → 고정 푸시 키. 코드 변경도 재빌드도 없음. 두 DB 드라이버는 지연 동적 import라 개발 환경은 pg·AWS SDK를 번들에 안 넣고, 커넥션은 전역 메모이즈되어 Next 16 멀티워커 빌드에서도 안전.

짚을 만한 엔지니어링 포인트

솔직한 한계

아직 자동 테스트 스위트 없음(검증은 수동 — 클린 프로덕션 빌드 + 1회성 30건 동시 쓰기 점검); rate-limit·멱등은 인메모리(단일 인스턴스); ffmpeg 트랜스코딩은 영속/Docker 경로에서만 동작해 데스크톱 Chrome webm이 아이폰에서 재생 안 될 수 있음; Terraform 인프라는 개발 자세이고 강화 경로는 주석에.

프로젝트 로그

이 프로젝트를 만들면서 남긴 트러블슈팅 · 회고 · 업데이트의 시간순 기록.

14 로그됨· 판단 layer 5 결정
마지막 업데이트: 2026년 6월 21일
필터

결정 · 마일스톤

  1. 소셜 로그인에 안 막히는 자체 인증 — 이메일+비밀번호 붙이기

    결정

    2026년 6월 21일 · 4

    로그인이 계속 우리가 통제 못 하는 OAuth 콘솔 상태에 막혔다(카카오 KOE006/KOE010/KOE237, iOS 배포 인증서 Apple 2FA). 그래서 첫 단추를 '사용자를 일단 들여보내는 일'을 외부 의존 없이 하도록, scrypt 기반 이메일+비밀번호 인증을 OAuth 위에 추가로 붙였다. users에 password_hash(nullable) 컬럼을 idempotent하게 더하고, /api/auth/signup·/password-login을 새로 뒀다. 로컬 서버에 curl로 전 시나리오(가입 200/중복 409/오답 401/로그인 200/Bearer 세션 200/짧은비번 400, password_hash 비노출) 통과, root·mobile tsc 모두 깨끗.

  2. 모바일 리마인더가 진짜 알림으로 — 네이티브 푸시 붙이기

    결정

    2026년 6월 20일 · 2

    App Store 준비도 감사에서 ‘리마인더 UI는 알림을 약속하지만 모바일엔 아무것도 안 온다(웹 푸시는 RN에 도달 못 함)’가 기능 공백이었다. expo-notifications로 Expo 푸시 토큰을 받아 서버에 등록하고, 스케줄러가 구독을 platform으로 분기(웹=Web Push/VAPID, 네이티브=Expo Push API)해 실제 알림이 가도록 닫았다. root·mobile tsc 모두 통과.

  3. App Store 제출을 막던 코드 차단 3개를 정리하다

    결정

    2026년 6월 15일 · 3

    출시 준비도 감사가 짚은 App Store 하드 차단 3개를 코드로 해소. (1) 네이티브 Apple 로그인 백엔드(app/api/auth/apple, lib/oauth/apple.ts)가 커밋조차 안 돼 있어 프로덕션에서 작동 안 하던 걸 커밋, (2) 계정 생성 앱은 인앱 계정삭제를 제공해야 한다는 가이드라인 5.1.1(v) 위반 — 설정 화면에 이중확인 '계정 삭제' 추가(백엔드 DELETE /api/account 호출), (3) 출시 빌드에 노출되던 '개발용' 로그인 폼을 __DEV__ 뒤로 숨김. tsc --noEmit 통과.

  4. OCI 영구무료 이관을 검토하고, 결국 AWS에 남은 이유

    결정

    2026년 6월 4일 · 4

    백엔드를 AWS(12개월 무료)에서 Oracle Cloud 영구무료 Arm으로 옮기려 시도. OCI Terraform을 작성·적대적 검증까지 했지만 세 벽에 막힘 — VCN 개수 한도, 수동 삭제의 의존성 순서 함정, 그리고 Always-Free Ampere A1의 고질적 'Out of host capacity'. 용량을 푸는 PAYG 업그레이드마저 멈춤. 결정: 이관 보류, AWS 무료티어 유지, 검증된 OCI 설정은 나중 재시도용으로 보존.

  5. 멀티리전 DB 결정: 미국/한국 사용자, DB를 둘로 나눌까?

    결정

    2026년 6월 1일 · 6

    미국/한국 사용자를 위해 DB를 둘로 나눌지 논의. 결론은 '지금은 1개'. 데이터를 리전으로 쪼개는 건 규정(데이터 거주) 해법이지 속도 해법이 아니고, 우리 앱의 cross-user 기여 링크를 깨뜨린다. 속도는 함수/DB 코로케이션 → 미디어 CDN → (나중) 읽기 복제본으로 푼다. 측정 전 분리는 자해. (논의 메모 — 구현 아님.)

빌드 로그

2026년 6월 1일 주2 · 1 트러블슈팅 · 1 사용성 회고
  1. Vercel + AWS 백엔드 배포기: RDS, S3, 그리고 부딪힌 벽들 순서대로

    트러블슈팅

    2026년 6월 4일 · 6

    첫 실 백엔드 배포 — Terraform으로 RDS(us-east-1) + 비공개 S3 버킷을 만들고, 기존 Vercel 앱에 환경변수만으로 연결. 부딪힌 벽들을 순서대로: 프리티어 백업 한도, RDS SSL 핸드셰이크(sslmode=require는 verify-full), 카카오 KOE006(플랫폼 도메인 누락), KOE237 레이트 리밋, 그리고 OAuth 계정 분리.

  2. 디자인 패스: 로켓 아트워크를 입히고, 카피를 '위기'에서 '응원'으로

    사용성 회고

    2026년 6월 1일 · 7

    나노바나나로 뽑은 로켓 일러스트 6종을 앱 전체에 연결(sharp로 최적화)하고, 앱 아이콘이 기본 favicon에 가려지던 문제를 잡고, 카피의 정서를 '무너질 때'라는 위기 프레임에서 '나를 아끼는 사람들의 응원'으로 갈아엎었다. 자살예방 핫라인을 뺀 것과 같은 이유 — 환자 취급이 아니라 곁의 친구.

2026년 5월 25일 주7 · 1 업데이트 · 1 트러블슈팅 · 4 기술 회고 · 1 사용성 회고
  1. 적대적 보안 리뷰: 11개 확인 결함과 무엇을 지금 고쳤나

    기술 회고

    2026년 5월 31일 · 3

    빌드 후 다중 에이전트 적대적 리뷰(보안/인증/데이터/캡처/PWA/스펙)를 돌려 11개 결함을 확인. 데모/스펙을 깨는 것 위주로 즉시 수정, 프로덕션 스케일 항목 2개는 문서화.

  2. 카카오 로그인 실연동: KOE006 → KOE010 까지, 그리고 직접 OAuth 결정

    트러블슈팅

    2026년 5월 31일 · 3

    사양의 '카카오는 제너릭 OAuth로'를 Better Auth 없이 직접 구현해 lib/auth 인터페이스에 끼움. 실제 키로 KOE006(redirect 미등록)·KOE010(client_secret 누락) 두 관문을 넘어 로그인 성공.

  3. 버팀목: 하룻밤 자율 MVP 빌드 — 사양서 한 장에서 동작하는 앱까지

    업데이트

    2026년 5월 31일 · 4

    work.md 사양서 하나를 받아 하룻밤에 '버팀목'(무너질 때 꺼내보는 비공개 보관함) PWA MVP를 끝까지 빌드한 기록. 외부 계정 0개로 즉시 실행되도록 설계.

  4. PGlite 임베디드 DB: 동시성·손상·빌드 충돌과 그 해결의 전말

    기술 회고

    2026년 5월 31일 · 4

    외부 DB 0개를 위해 택한 PGlite(WASM PostgreSQL)에서 부딪힌 세 가지 함정 — 동시 쿼리, 디스크 손상, 빌드 다중워커 충돌 — 과 lazy-init + 프로세스 가드로의 수렴.

  5. 스택 결정 전부: 무엇을, 왜, 대안은, 그리고 프로덕션 전환점

    기술 회고

    2026년 5월 31일 · 5

    버팀목 MVP에서 내린 모든 기술 스택 결정을 근거·대안·프로덕션 전환점과 함께 정리. 핵심 패턴은 '로컬 기본값을 인터페이스 뒤에 두고, 프로덕션 키는 한 파일만 갈아끼우기'.

  6. 신뢰는 암호화 설명이 아니라 '느낌·통제·관계'로 — 그리고 멀티테넌시

    사용성 회고

    2026년 5월 31일 · 4

    민감한 제품에서 '내 데이터가 외부에 저장되는 게 무섭다'를 어떻게 다룰지에 대한 논의와 결정. E2EE의 자리, 느껴지는 신뢰 우선, 내보내기/전체삭제 구현, 멀티테넌시 격리 실증.

  7. 영상을 4번째 콘텐츠 타입으로 — §1.1 단순성에서 의도적으로 벗어난 결정

    기술 회고

    2026년 5월 31일 · 3

    owner 결정으로 음성/사진/글에 더해 짧은 영상(20초)을 추가. 스펙의 단순성 가드레일에서 의도적 이탈임을 플래그하고, 기존 캡처 패턴을 그대로 확장. 미해결 재생 호환성 caveat 명시.