teji

tede

AI 에이전트, 트레이딩 봇 개발 중인 남편의 일기 🤖
화장품에 진심인 간호사 아내의 코덕로그 💄

전체보기

클로드 바이브코딩(Claude Code)으로 앱 만들기 2편 2026: 앱인토스(Apps in Toss) 미니앱 구현기와 토스 웹뷰 흰 화면 버그 4개 해결

최종 업데이트: 2026-09-08 (코드·명령을 텍스트 블록으로 병기)

클로드 바이브코딩 앱인토스 미니앱 구현기 2편, 토스 웹뷰 흰 화면 버그 4연타 썸네일

안드로이드 앱을 앱인토스(Apps in Toss) 미니앱으로 옮기는 데는 하루가 걸렸고, 토스 웹뷰(WebView)에서만 발생하는 버그 4개를 잡는 데 두 달이 걸렸어요. 네 버그 모두 로컬 크롬에서는 재현되지 않고 실기기에서만 깨지는 종류였어요. 이 글은 클로드코드(Claude Code) 바이브코딩으로 화장품 커뮤니티 앱 코스모(COSMO)를 앱인토스에 올리면서 겪은 구현 기록이고, 코드는 이미지와 텍스트 블록으로 함께 넣었어요.

3부작 중 2편이에요. 기획과 컨셉은 1편 아이디어부터 컨셉 확정까지에 있고, 3편은 심사 제출부터 오픈 완료까지예요.

안드로이드 앱을 웹 미니앱으로 하루 만에 옮긴 방법은?

앱인토스 미니앱 구조: 안드로이드 앱 보류, React 웹 미니앱, 토스 앱 안 웹뷰, FastAPI 백엔드 그대로

앱인토스는 네이티브 앱이 아니라 토스 안에서 열리는 웹뷰 미니앱이에요. React로 화면을 만들고 빌드하면 .ait 파일 하나(4.4MB)가 나와요. 그래서 화면만 다시 쓰고 FastAPI 백엔드는 한 줄도 바꾸지 않았어요. 바뀐 것은 CORS 허용 도메인에 토스 미니앱 주소를 추가한 한 줄뿐이에요.

6월 25일 하루에 클로드코드가 한 일은 다음과 같아요.

  1. 데이터 레이어: 기존 API를 그대로 호출하는 클라이언트 파일 하나
  2. 탭 5개 + 피드 화면 4개: 홈, 모여, 아이템, 아무말, 마이
  3. 상세 3종 + 좋아요·북마크·댓글: 누르면 즉시 반영되는 옵티미스틱 토글
  4. 글쓰기·검색·퍼스널컬러 10문항 진단: 여기까지 하루 안에 빌드 통과

가장 큰 절약은 로그인 화면을 통째로 제거한 것이에요. 안드로이드 앱에서는 카카오·구글 로그인의 키 해시 문제로 며칠을 잃었는데, 토스 안에서는 토스가 사용자를 이미 알고 있어요. 익명 기기 ID로 둘러보기, 댓글, 좋아요가 모두 동작하므로 v1에서 로그인을 뺐어요.

버그 1. 흰 화면: 에셋 경로가 절대경로였다

QR로 실기기에서 열었더니 토스 내비게이션 바만 뜨고 아래는 흰 화면이었어요. 로컬에서는 정상이었고요. 클로드코드에게 .ait 파일을 풀어 내부를 확인하게 했더니 원인이 나왔어요.

vite.config.ts base './' 상대경로 설정과 granite.config.ts webViewProps partner 설정 코드

vite.config.ts + granite.config.ts: 흰 화면 1차 원인

// 토스 웹뷰는 web/index.html 을 하위 경로에서 로드한다.
// 절대경로(/assets)면 번들 404 → 흰 화면. 상대경로로 빌드해야 한다.
export default defineConfig({
  base: "./",          // ← 기본값 "/" 였음
  plugins: [react()],
});
// granite.config.ts: 파트너 웹뷰 미니앱 (공식 예제와 동일)
webViewProps: { type: "partner" },
navigationBar: { withBackButton: true, withHomeButton: true },

토스 웹뷰는 index.html을 하위 경로에서 불러오는데, 빌드 도구(Vite) 기본값이 절대경로라서 자바스크립트 번들을 찾지 못한 거예요. base를 상대경로로 바꾸고 공식 예제와 같은 웹뷰 설정을 맞췄어요. 그런데 이걸 고쳐도 흰 화면이 계속 나왔어요. 진짜 원인은 따로 있었어요.

버그 2. 흰 화면의 진짜 원인: 로컬에서는 데이터가 없어서 안 터졌다

React error #31 원인 코드: trending_tags 타입을 string[]로 추측했다가 TrendingTag[] 객체 배열로 수정

src/lib/api.ts: 흰 화면 진짜 원인 (React error #31)

// 실기기 에러: Objects are not valid as a React child
// 로컬은 CORS 때문에 데이터가 안 떠서 안 터졌고, 실기기는 데이터가 떠서 터짐
// 전
trending_tags?: string[];              // 문자열 배열이라고 "추측"
{tags.map(t => <Chip>#{t}</Chip>)}    // 객체를 그대로 렌더 → 크래시
// 후 (API 응답을 실제로 찍어보고 타이핑)
interface TrendingTag { tag: string; count: number; is_trending: boolean }
trending_tags?: TrendingTag[];
{tags.map(t => <Chip>#{t.tag}</Chip>)}
# 교훈 : API 응답은 추측 말고 실제 응답으로 타입을 잡고, 데이터가 뜨는 조건에서 테스트할 것

홈 화면의 인기 태그를 문자열 배열로 추측해서 타이핑했는데, 실제 API는 객체 배열을 주고 있었어요. React는 객체를 화면에 그리려 하면 그 자리에서 앱 전체가 중단돼요(React error #31, Objects are not valid as a React child). 이것이 흰 화면의 정체였어요.

로컬에서 터지지 않은 이유는 CORS 때문에 데이터가 아예 뜨지 않았기 때문이에요. 데이터가 없으니 렌더할 객체도 없어 정상처럼 보였고, 실기기는 데이터가 떠서 바로 중단됐어요. 이 원인을 찾는 데 하루를 썼고, 교훈은 “API 응답은 추측하지 말고 실제 응답으로 타입을 잡자”예요.

“고쳤는데 그대로예요”: 코드가 아니라 배포가 문제였다

8월 말 실기기 확인을 부탁했더니 “여전히 안 고쳐졌다”는 답이 왔어요. 클로드코드가 빌드 결과물을 뒤져 수정이 포함된 것을 증명했는데도 화면은 그대로였어요.

원인은 코드가 아니었어요. 앱인토스는 파일을 콘솔에 올릴 때마다 새 배포 ID가 생기고 테스트 QR도 새로 만들어져요. 이전 QR을 다시 스캔하면 이전 버전이 실행돼요. 저희는 계속 옛 QR을 찍고 있었던 거예요.

그래서 클로드코드가 빌드 시각을 앱 안에 새기는 지문을 만들었어요. 마이 탭 맨 아래에 “build 260830T1343” 같은 문자열이 찍히고, 이것으로 실행 중인 빌드가 최신인지 2초 만에 확인할 수 있어요. 이 지문이 없었으면 다음 버그 두 개도 잡지 못했을 거예요.

버그 3. 스크롤하면 이미지가 갑자기 커진다

실기기 캡처예요. 아이템 탭에서 스크롤하다 보면 왼쪽 이미지가 커지면서 오른쪽 컬럼이 화면 밖으로 밀려나요.

토스 웹뷰 실기기 그리드 블로우아웃 증상: 왼쪽 상품 이미지가 확대되고 오른쪽 컬럼이 밀려난 화면

이것도 최신 데스크톱 크롬에서는 재현되지 않았고, 그래서 로컬 검증을 통과했던 거예요. 원인은 CSS 그리드의 오래된 함정이에요. 컬럼을 1fr로 나누면 최소 폭이 내용물 크기(min-width: auto)로 잡혀서, 이미지가 늦게 로딩되는 순간 원본 폭이 컬럼을 밀어내요.

CSS grid 블로우아웃 수정 코드: gridTemplateColumns 1fr 1fr 을 minmax(0,1fr) minmax(0,1fr) 로 변경

src/screens/ItemsScreen.tsx: 그리드 블로우아웃

// 증상: 스크롤 중 왼쪽 이미지가 갑자기 커지고 오른쪽 컬럼이 화면 밖으로 밀림
// 원인: 1fr 의 min-width:auto 함정. 이미지 lazy-load 완료 시 원본 폭이 컬럼을 밀어냄
// 최신 데스크톱 크롬에선 재현 안 됨 → 그래서 로컬 검증을 통과했었음
gridTemplateColumns: "1fr 1fr",                     // 전
gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)",  // 후
// + 카드 래퍼 minWidth: 0, 상품명 wordBreak: "break-all"
# 교훈 : 토스 웹뷰 UI 버그는 로컬 크롬 재현 실패가 무죄 증명이 아님
# grid/flex 는 처음부터 minmax(0,1fr) / min-width:0

minmax(0, 1fr)로 바꾸고 카드에 min-width: 0을 주니 컬럼이 항상 정확히 50%로 고정됐어요. 클로드코드 메모리에 규칙으로 남겼어요. “토스 웹뷰 UI 버그는 로컬 크롬 재현 실패가 무죄 증명이 아니다.”

버그 4. 탭바가 너무 크다: 안전영역이 두 번 붙었다

같은 날 온 두 번째 캡처예요. 하단 탭바가 84px로 내용 대비 두 배 넘게 높았어요.

토스 웹뷰 실기기 탭바 과다 높이 증상 캡처, 하단 탭바 84px

이건 실측이 필요했어요. 클로드코드가 마이 탭 하단 지문에 안전영역(safe area) 값을 함께 찍게 해서 실기기에서 숫자를 회수했어요. 결과는 토스 변수 0px, 브라우저 표준 변수 env(safe-area-inset-bottom) 48px이었어요.

토스 미니앱 safe-area 수정 코드: max(var(--toss-safe-area-bottom), env(safe-area-inset-bottom)) 대신 토스 변수만 사용, 탭바 84px에서 36px

src/lib/theme.ts: 탭바가 84px 이던 이유

// 실기기 실측 (build 260830T1252) : sab 48px (toss 0 / env 48)
// 토스 웹뷰는 홈 인디케이터를 네이티브가 처리해서 --toss-safe-area-bottom: 0px 을 주는데
// env(safe-area-inset-bottom) 은 48px 을 보고 → max() 로 섞으면 48px 이 이중으로 붙음
export const TAB_BAR_HEIGHT = 36;   // 아이콘 22 + gap 2 + 라벨 11 + 보더 1
// 전 : max(var(--toss-safe-area-bottom,0px), env(safe-area-inset-bottom,0px))
export const SAFE_AREA_BOTTOM =
  "var(--toss-safe-area-bottom, 0px)";   // 후 : 토스 변수만 신뢰
# 결과 : 탭바 84px → 36px. 토스 미니앱 하단 인셋은 토스 변수만, env() 금지
# 진단용으로 마이 탭 하단에 "build 260830T1343 | sab 0px" 를 찍어서 실측값을 회수했어요

토스 웹뷰는 홈 인디케이터 여백을 네이티브가 이미 처리해서 자기 변수로는 0을 주는데, 브라우저 표준 변수는 48px을 보고해요. 둘 중 큰 값을 쓰도록 만들어 두었더니 48px이 이중으로 붙은 거예요. 토스 변수만 신뢰하도록 바꾸니 탭바가 84px에서 36px로 내려왔어요.

클로드코드가 매번 읽는 규칙 문서는 무엇인가요?

DEPLOY.md 앱인토스 비게임 출시 체크리스트 마크다운 표: 내비게이션 바, 뒤로가기 중복 제거, 라이트 모드, 핀치 줌, 로그인, 결제

tossapp/DEPLOY.md: 비게임 출시 체크리스트 준수 현황

| 항목                          | 상태
|-------------------------------|--------------------------------------------
| 내비게이션 바(뒤로/홈)         | ✅ granite.config navigationBar → 토스 네이티브 바
| 자체 뒤로가기 버튼 중복 제거   | ✅ 화면 내 ‹ 버튼 모두 제거 (제목만 노출)
| 라이트 모드                    | ✅ 흰 배경 라이트 단일
| 진입 시 바텀시트 자동 오픈 금지 | ✅ 없음
| 핀치 줌 비활성화               | ✅ viewport user-scalable=no
| 재진입 시 데이터 유지          | ✅ device_id localStorage + 서버 상태
| 로그인                         | ✅ 자사·타사 로그인 미제공 (익명 device_id)
| 결제                           | ✅ 없음 (커뮤니티)
| 외부 링크 / 자사앱 유도        | ✅ 구매처는 비클릭 텍스트, 앱 설치 유도 없음
빌드 : npm run build → cosmo.ait (4.4MB, 한도 100MB) → 콘솔 업로드 → 테스트하기 → QR → 검토 요청

앱인토스 비게임 출시 가이드를 표로 옮겨 프로젝트 안에 마크다운 문서로 두었어요. 화면을 고칠 때마다 클로드코드가 이 표에 맞춰 스스로 점검하고, “뒤로가기 버튼이 두 개다” 같은 반려 사유를 코드 작성 전에 걸러줘요.

버그를 잡으면서 규칙도 늘었어요. 가장 도움이 된 세 줄이에요.

  1. 조사 없이 수정 없음: 증상 기록, 가설 3개, 증거 수집, 원인 확정, 수정 순서. 흰 화면 때 이 순서를 지키지 않아 하루를 잃었어요.
  2. 3번 실패하면 멈추기: 같은 버그 수정 3회 실패 시 사람에게 묻기. 무한 루프 방지예요.
  3. “고쳤는데 안 보임”은 코드보다 배포 상태 먼저: 새 QR을 스캔했는지, 빌드 지문이 최신인지부터 확인해요.

토스 웹뷰 버그 4개 비교

버그 증상 원인 로컬 재현 수정
1. 흰 화면 내비바만 뜨고 흰 화면 에셋 절대경로, 번들 404 안 됨 상대경로 빌드(base: “./”)
2. 흰 화면 위와 동일 API 응답 타입 추측, 객체 렌더 크래시 안 됨(데이터 없음) 실제 응답 기준 타입
3. 그리드 왼쪽 이미지 확대, 오른쪽 밀림 1fr min-width: auto 함정 안 됨(최신 크롬) minmax(0, 1fr)
4. 탭바 높이 84px 안전영역 이중 적용 안 됨 토스 변수만 신뢰, 36px

네 개 모두 로컬에서 재현되지 않았다는 것이 핵심이에요. 웹뷰 미니앱은 실기기 테스트가 곧 테스트예요. 빌드 지문과 실측값 회수 장치를 앱 안에 넣어둔 것이 결정적이었어요.

자주 묻는 질문

Q. 앱인토스 미니앱은 어떤 기술로 만드나요?
React 같은 웹 기술로 만들어요. 토스가 제공하는 웹 프레임워크와 디자인 시스템(TDS)을 쓰고, 빌드하면 .ait 파일 하나가 나와 콘솔에 올려요.

Q. 기존 앱 코드는 얼마나 재사용했나요?
백엔드는 100%, 화면은 0%예요. API 서버는 그대로 두고 허용 도메인만 추가했고, 화면은 웹으로 전부 다시 썼어요. 디자인 시스템(색, 폰트, 로고)은 그대로 옮겼어요.

Q. 토스 웹뷰 버그는 어떻게 디버깅하나요?
실기기에서 숫자를 회수하는 장치를 앱 안에 넣어요. 마이 탭 하단에 빌드 시각과 안전영역 값을 찍어 실기기 캡처로 받았어요. 로컬 크롬 재현은 참고만 하세요.

Q. 클로드코드가 혼자 다 고쳤나요?
원인 분석과 코드는 클로드코드가, 실기기 확인은 사람이 했어요. “새 QR을 스캔하지 않았다” 같은 배포 문제는 코드를 아무리 봐도 나오지 않아 사람이 콘솔 상태를 확인해야 했어요.

다음 편

3편은 앱인토스 심사 제출부터 오픈 완료까지예요. 콘솔 등록에 필요한 것, 검토 요청 후 돌아온 것, 출시 버튼을 누르기까지 남은 일을 정리해요. 코드리뷰 스킬로 배포 직전 문제를 잡은 기록은 gstack /review 실측에, 클로드 자체 오류 대응은 클로드 오류 해결 방법 2026에 있어요.

이 글은 개인 프로젝트 기록이고 특정 도구나 플랫폼의 광고가 아니에요. 수치는 2026년 8월 30일 실기기 실측 기준이에요.

Comments

One response to “클로드 바이브코딩(Claude Code)으로 앱 만들기 2편 2026: 앱인토스(Apps in Toss) 미니앱 구현기와 토스 웹뷰 흰 화면 버그 4개 해결”

  1. […] 3편은 로그인과 백엔드 연동이에요. 카카오·구글 소셜 로그인, JWT 토큰을 보안 저장소에 넣고 401 이 오면 자동으로 갱신하는 인터셉터, FCM 푸시까지 코드로 보여드려요. 앞선 글: Flutter 앱 만들기 (1) 폴더 구조·Riverpod·go_router · 앱인토스 개발기 (2) 탭바 안전영역 버그 […]

Leave a Reply

Your email address will not be published. Required fields are marked *