최종 업데이트: 2026-09-08 (코드·명령을 텍스트 블록으로 병기)
![]()
안드로이드 앱을 앱인토스(Apps in Toss) 미니앱으로 옮기는 데는 하루가 걸렸고, 토스 웹뷰(WebView)에서만 발생하는 버그 4개를 잡는 데 두 달이 걸렸어요. 네 버그 모두 로컬 크롬에서는 재현되지 않고 실기기에서만 깨지는 종류였어요. 이 글은 클로드코드(Claude Code) 바이브코딩으로 화장품 커뮤니티 앱 코스모(COSMO)를 앱인토스에 올리면서 겪은 구현 기록이고, 코드는 이미지와 텍스트 블록으로 함께 넣었어요.
3부작 중 2편이에요. 기획과 컨셉은 1편 아이디어부터 컨셉 확정까지에 있고, 3편은 심사 제출부터 오픈 완료까지예요.
안드로이드 앱을 웹 미니앱으로 하루 만에 옮긴 방법은?

앱인토스는 네이티브 앱이 아니라 토스 안에서 열리는 웹뷰 미니앱이에요. React로 화면을 만들고 빌드하면 .ait 파일 하나(4.4MB)가 나와요. 그래서 화면만 다시 쓰고 FastAPI 백엔드는 한 줄도 바꾸지 않았어요. 바뀐 것은 CORS 허용 도메인에 토스 미니앱 주소를 추가한 한 줄뿐이에요.
6월 25일 하루에 클로드코드가 한 일은 다음과 같아요.
- 데이터 레이어: 기존 API를 그대로 호출하는 클라이언트 파일 하나
- 탭 5개 + 피드 화면 4개: 홈, 모여, 아이템, 아무말, 마이
- 상세 3종 + 좋아요·북마크·댓글: 누르면 즉시 반영되는 옵티미스틱 토글
- 글쓰기·검색·퍼스널컬러 10문항 진단: 여기까지 하루 안에 빌드 통과
가장 큰 절약은 로그인 화면을 통째로 제거한 것이에요. 안드로이드 앱에서는 카카오·구글 로그인의 키 해시 문제로 며칠을 잃었는데, 토스 안에서는 토스가 사용자를 이미 알고 있어요. 익명 기기 ID로 둘러보기, 댓글, 좋아요가 모두 동작하므로 v1에서 로그인을 뺐어요.
버그 1. 흰 화면: 에셋 경로가 절대경로였다
QR로 실기기에서 열었더니 토스 내비게이션 바만 뜨고 아래는 흰 화면이었어요. 로컬에서는 정상이었고요. 클로드코드에게 .ait 파일을 풀어 내부를 확인하게 했더니 원인이 나왔어요.

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[] 객체 배열로 수정](https://tede.fyi/wp-content/uploads/2026/09/code_react31.png)
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)로 잡혀서, 이미지가 늦게 로딩되는 순간 원본 폭이 컬럼을 밀어내요.

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로 내용 대비 두 배 넘게 높았어요.

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

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로 내려왔어요.
클로드코드가 매번 읽는 규칙 문서는 무엇인가요?

tossapp/DEPLOY.md: 비게임 출시 체크리스트 준수 현황
| 항목 | 상태
|-------------------------------|--------------------------------------------
| 내비게이션 바(뒤로/홈) | ✅ granite.config navigationBar → 토스 네이티브 바
| 자체 뒤로가기 버튼 중복 제거 | ✅ 화면 내 ‹ 버튼 모두 제거 (제목만 노출)
| 라이트 모드 | ✅ 흰 배경 라이트 단일
| 진입 시 바텀시트 자동 오픈 금지 | ✅ 없음
| 핀치 줌 비활성화 | ✅ viewport user-scalable=no
| 재진입 시 데이터 유지 | ✅ device_id localStorage + 서버 상태
| 로그인 | ✅ 자사·타사 로그인 미제공 (익명 device_id)
| 결제 | ✅ 없음 (커뮤니티)
| 외부 링크 / 자사앱 유도 | ✅ 구매처는 비클릭 텍스트, 앱 설치 유도 없음
빌드 : npm run build → cosmo.ait (4.4MB, 한도 100MB) → 콘솔 업로드 → 테스트하기 → QR → 검토 요청
앱인토스 비게임 출시 가이드를 표로 옮겨 프로젝트 안에 마크다운 문서로 두었어요. 화면을 고칠 때마다 클로드코드가 이 표에 맞춰 스스로 점검하고, “뒤로가기 버튼이 두 개다” 같은 반려 사유를 코드 작성 전에 걸러줘요.
버그를 잡으면서 규칙도 늘었어요. 가장 도움이 된 세 줄이에요.
- 조사 없이 수정 없음: 증상 기록, 가설 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일 실기기 실측 기준이에요.

Leave a Reply