
지난 1편의 목표는 명확했습니다. 어떻게든 빠르게 앱 POC를 만들어내는 것이었죠. 이미 돌아가던 Next.js 모바일 웹을 SSOT(Single Source of Truth)로 삼아 모노레포를 세팅하고, mobile-design-sync 스킬을 돌려가며 리셋된 토큰을 3일 만에 다 태울 만큼 무섭게 화면을 찍어냈습니다.
그렇게 일주일 만에 그럴듯한 앱이 나왔으니, 남은 일주일 동안 가볍게만 손보면 “앱 출시까지 2주면 된다”는 말을 증명할 수 있을 줄 알았습니다. 하지만 1편 마지막에 말씀드렸듯이, 바로 출시까지 가는 데는 실패했습니다.
속도를 최우선으로 두고 달렸던 만큼, 실제 스토어에 올리려니 그때는 미처 보지 못했던 빈틈들이 한꺼번에 드러났기 때문입니다. 마크다운 문서상으로는 분명 화면들이 다 올라와 있었지만, 막상 휴대폰으로 하나씩 눌러보니 여기저기서 문제들이 쏟아졌습니다.
결국 1편이 ‘속도’를 무기로 빠르게 POC를 올린 이야기였다면, 이번 하편은 속도를 잠시 내려놓고 ‘디테일한 체크’에 집중한 기록입니다.
초벌로 찍어낸 앱을 실제 유저 손에 쥐여줘도 부끄럽지 않은 상태로 만들기 위해 무엇을 하나씩 점검하고 고쳤는지, 그리고 “앱 출시까지 2주면 된다”를 직접 테스트해 본 최종 후기를 풀어보고자 합니다.
1편에서 만든 mobile-design-sync 스킬은 모바일 웹과 앱의 스크린샷을 찍어 나란히 비교하며 화면을 맞췄습니다. 빠르게 POC를 올릴 때는 이 방식이 정말 강력했습니다. 그런데 실제 휴대폰에 앱을 빌드해서 손가락으로 눌러보는 순간, 스크린샷 비교만으로는 절대 알 수 없었던 문제들이 줄줄이 나왔습니다.
대표적인 것이 상·하단 SafeArea 여백, 키보드 올라옴 처리, 그리고 손가락 터치 영역이었습니다.
화면 캡처본에서는 멀쩡해 보이던 상단 헤더가 실제 기기에서는 노치(Notch) 영역과 겹치거나, 반대로 상단 공통 헤더가 이미 상단 여백을 먹고 있는데 화면 컨테이너가 한 번 더 SafeArea를 잡아서 상단에 광활한 빈 공간이 생기기도 했습니다. 입력 폼에서는 키보드가 올라오는 순간 하단 ‘다음’ 버튼을 덮어버렸고, iOS에서 잘 되던 키보드 회피 처리가 Android에서는 먹히지 않았습니다. 웹에서는 마우스 커서로 정확히 클릭할 수 있던 32px짜리 아이콘이나 약관 체크박스도 앱에서는 손가락에 자꾸 빗나갔죠.
처음에는 문제가 보이는 화면마다 들어가서 패딩을 고쳤는데, 화면이 수십 개다 보니 한 곳을 고치면 다른 화면과 기준이 달라졌습니다. 그래서 개별 화면을 땜질하는 대신, 웹의 공통 레이아웃에 대응하는 앱 전용 루트 셸 컴포넌트(Screen)를 만들어 화면 규칙을 한곳에서 통제하기로 했습니다.

이렇게 루트 셸에서 세 가지 디테일을 일괄로 체크했습니다.

사진상으로는 큰 차이가 없어 보일 수 있지만, 모바일 웹을 만들면서 함께 협업하는 디자이너분께 대략 350개의 디자인 QA를 받았던 저로서는 큰 수고로움 없이 이런 디테일을 일괄로 맞출 수 있다는 점이 아주 매력적이었습니다.
빠르게 POC를 찍어낼 때는 “일단 웹에 있는 페이지를 다 앱 화면으로 옮기자”는 생각으로 달렸습니다. 하지만 출시 전 디테일 체크 단계에서 냉정하게 따져보니, 유지보수 관점에서 고민되는 지점들이 보이기 시작했습니다.
예를 들어 회사소개, 자주 묻는 질문(FAQ), 블로그, 릴리즈 노트, 1:1 문의, 파트너 등록 안내 랜딩 같은 페이지들은 복잡한 네이티브 인터랙션이 없는 정보 전달용 화면입니다. 이런 화면까지 전부 React Native로 따로 들고 있으면, 문구 하나나 배너 하나를 바꿀 때마다 웹을 고치고 앱도 고쳐서 스토어 심사를 다시 받아야 합니다. 물론 이런 불편함을 줄이기 위해 Expo로 프로젝트를 관리하고 있지만, 그럼에도 단순한 문구 변경 때문에 앱 버전을 올리고 배포하는 일은 너무 번거로울 것 같았습니다.
그래서 디테일 체크 과정에서 “어디까지 웹뷰(WebView)로 두고, 어디서부터 네이티브로 전환할 것인가”라는 경계선을 명확히 그었습니다.

정적 페이지들은 과감하게 웹뷰 허용 경로(Allowlist)로 묶어 react-native-webview로 띄웠습니다. 대신 앱 안에서 웹뷰를 열었을 때 상단 헤더가 두 개(앱 헤더 + 웹 헤더)로 겹쳐 보이면 유저 입장에서는 단번에 “껍데기만 씌운 앱”처럼 느껴집니다. 이를 막기 위해 웹뷰 호출 시 식별용 쿼리(?webview=1)와 쿠키를 심어 보내고, Next.js 웹에서는 이 신호가 들어오면 웹 자체 헤더·푸터·플로팅 버튼(FAB)을 숨기도록 처리했습니다. 덕분에 상단은 앱의 네이티브 헤더가, 본문은 모바일 웹이 이질감 없이 이어졌습니다.

그런데 이렇게 만들고 나니, 다시 프론트엔드의 전통적인 고민으로 돌아가게 됩니다. “아, 하이브리드 앱으로 가도 될 것 같은데?”
참 애매했습니다. 결국 모바일 웹과 모바일 앱을 일일이 싱크하는 것보다, 모바일 웹만 관리하고 앱 껍데기를 씌우는 구조로 간다면 굳이 이런 디테일한 싱크 작업을 하지 않아도 되니 말입니다.
하지만 이내 “하이브리드보단 네이티브가 더 성능이 좋지, 장기적으로 고민하자”라는 합리화를 하고 넘어가게 됩니다. 아직 이 부분은 저도 계속 고민 중인 지점입니다.
반면, 웹뷰에서 시작하더라도 입력과 검증이 핵심인 구간에서는 즉시 네이티브 화면으로 바통을 넘기도록 설계했습니다. 가장 좋은 예가 ‘출강업체 등록’ 흐름입니다.
출강업체 등록 안내 랜딩 페이지는 긴 소개 이미지와 안내 문구가 주를 이루므로 웹뷰로 보여줍니다. 하지만 유저가 그 웹뷰 안에서 ‘출강업체 등록하기’ 버튼을 누르는 순간, 웹뷰 안에 주입해 둔 브릿지 스크립트가 가입 페이지 이동을 가로채어 앱의 네이티브 회원가입 화면으로 라우팅을 전환합니다.

약관 동의부터 아이디, 사업자등록번호 실시간 검증, 비밀번호 입력, 포커스 자동 이동까지 이어지는 다단계 가입 폼은 웹뷰보다 네이티브로 구현했을 때 키보드 반응성과 입력 경험이 훨씬 쾌적하기 때문입니다.
여기에 더해, 네이티브 가입 도중 거쳐야 하는 통신사 PASS 본인인증은 외부 본인인증 모듈 특성상 다시 전용 웹뷰로 띄우고, 인증이 끝나 리다이렉트될 때 결과 URL에서 인증 식별값만 추출해 다시 네이티브 가입 단계로 복귀시켰습니다.

이렇게 웹뷰와 네이티브의 적당한 타협과 조합으로 개발 속도와 앱다운 사용성을 동시에 잡을 수 있었습니다. 지금도 어떤 페이지를 웹뷰로 두고 어떤 페이지를 네이티브로 가져갈지 계속 고민하고 있습니다. 다만 이전과 다른 점은 개발 속도가 더 이상 큰 병목은 아니라는 것입니다.
의사결정 과정에서 네이티브가 더 좋다고 판단되면 이젠 바로 실행하면 됩니다.
두 번째로 발목을 잡은 건 ‘겉모습은 똑같은데 속 로직이 다른’ 문제였습니다.
분명 1편에서 시작할 때부터 모노레포를 구축하고 공통 패키지(api, utils)에 비즈니스 로직을 모아두었습니다. 그러니 앱에서도 당연히 똑같은 로직이 돌아갈 거라 믿었죠. 하지만 QA를 시작해 보니 웹에서는 통과되는 입력값이 앱에서는 막히거나, 에러 토스트 문구가 미묘하게 다르거나, 버튼 연타 시 중복 요청 방지 가드가 빠진 곳들이 무더기로 발견됐습니다.
원인은 명확했습니다. POC 단계에서 AI 에이전트에게 “모바일 웹 화면을 보고 앱 화면을 빠르게 만들어줘”라고 지시할 때, 에이전트가 매번 공통 유틸 패키지의 검증 함수를 찾아 연결하기보다 각 화면 컴포넌트 안에 빈 값 체크나 정규식 몇 줄을 즉석에서 인라인으로 짜 넣는 쪽을 택했기 때문입니다. 눈으로 보는 UI는 100% 똑같이 돌아가니 초벌 작업 때는 눈치채지 못했던 겁니다.
이 문제를 겪고 나서 워크플로를 뜯어고쳤습니다. 픽셀과 색상을 맞추기 전에, 반드시 웹과 앱의 입력 검증, API 호출, 상태 전이부터 대조하는 mobile-logic-sync 단계를 먼저 거치도록 강제한 것입니다.
가장 먼저 앱 화면 컴포넌트 안에 흩어져 있던 인라인 검증 코드들을 전수 검색해 걷어내고, 모든 검증 규칙과 에러 문구를 공통 유틸 패키지의 단일 함수로 끌어올렸습니다.

이렇게 공통 패키지에 정의해 두면, Next.js 웹이든 React Native 앱이든 똑같은 함수를 호출해 똑같은 에러 메시지를 보여줍니다. 또한 이 함수들에 단위 테스트(Vitest)를 붙여두어, 나중에 누군가(아마도 대부분 경우에는 AI가) 화면 UI를 수정하더라도 검증 조건이 슬그머니 바뀌는 일을 원천 차단했습니다.
동시에 플랫폼 특성상 웹과 앱이 의도적으로 다르게 동작해야 하는 부분은 각 화면의 싱크 마크다운 문서에 명시해 두었습니다. 예를 들어 웹에서는 비밀번호 입력 중 실시간 강도 검증 API를 디바운스로 호출하지만, 모바일 앱에서는 네트워크 지연을 줄이기 위해 제출 시점에 공통 검증 규칙으로 일괄 검증하도록 결정했습니다. 이런 차이를 문서에 적어두지 않으면 다음번 QA나 에이전트 실행 때 “웹이랑 로직이 다른 버그”로 오해해 다시 덮어쓰는 일이 반복되기 때문입니다.
1편 도입부에서 말씀드렸듯, 애초에 모바일 웹만으로도 잘 돌아가던 서비스에 굳이 앱을 만들기로 결심한 이유는 단 하나였습니다. “알림을 보지 못해서 계약이 불발되었다”는 고객의 VOC 때문이었죠.
그런데 1주일 만에 찍어낸 POC 앱에서 ‘알림’을 점검해 보니 갈 길이 멀었습니다. 우선 UI부터가 어색했습니다. 모바일 웹에서는 상단 헤더의 종 모양 아이콘을 누르면 그 자리에서 바텀시트(Bottom Sheet)로 알림 인박스가 열렸는데, 초벌 앱에서는 종 아이콘을 누르면 아예 별도의 알림 페이지로 화면 전체가 이동해 버렸습니다.
디테일 체크 단계에서 이를 모바일 웹과 동일하게 바텀시트 형태의 알림 인박스로 다시 맞추고, 미읽음 배지와 탭 필터(전체/견적/공지), 그리고 외부 알림 링크를 타고 들어왔을 때 최대 3초간 ‘이동 중...’ 스피너를 보여준 뒤 목표 화면으로 넘겨주는 브릿지 화면까지 웹과 똑같이 정렬했습니다.


하지만 진짜 문제는 그다음이었습니다. 앱 안에 알림함 화면이 예쁘게 들어앉은 것과, 유저의 휴대폰이 꺼져 있을 때 잠금화면에 푸시 알림이 울리는 것은 완전히 별개의 기능이었기 때문입니다.
웹을 그대로 옮기는 스킬만으로는 네이티브 푸시가 저절로 생겨나지 않습니다. 실제 유저가 계약을 놓치지 않게 하려면 다음의 네이티브 파이프라인을 처음부터 끝까지 별도로 챙겨야 했습니다.
여기까지 진행하니 이젠 앱다운 모습이 어느 정도 완성된 것을 확인할 수 있었습니다. 이젠 진짜 마지막 단계로 넘어가 보도록 하겠습니다.
마지막으로 가장 많은 시간을 쏟은 곳은 픽셀과 토큰 단위의 디자인 디테일 체크였습니다.
1편에서 소개한 mobile-design-sync의 초벌 방식은 심플했습니다. 피그마 전체 화면 스크린샷, 모바일 웹 스크린샷, 모바일 앱 스크린샷을 찍어 AI에게 보여주고 “다른 부분을 찾아 고쳐라”고 시키는 것이었죠. 레이아웃이 크게 무너진 초벌 단계에서는 이 방법이 마법처럼 통했습니다.
그런데 완성도를 80%에서 99%로 끌어올리려 하자, 전체 화면 스크린샷 비교가 오히려 디자인을 망치는 역효과를 내기 시작했습니다. 크게 두 가지 이유 때문이었습니다.
가장 짜증 났던 사례가 견적 요청(RFQ) 1단계 화면의 카테고리 선택 버튼들이었습니다.
피그마 시안과 맞춘다며 에이전트가 수정을 끝냈는데, 앱 화면을 보니 2열 그리드로 놓인 버튼들의 테두리가 유독 시커멓고 답답해 보였습니다. 확인해 보니 에이전트가 버튼의 기본(rest) 테두리 색상을 #aaaaaa로 넣어둔 상태였습니다.
왜 #aaaaaa를 넣었는지 추적해 보니 두 가지 문제가 겹쳐 있었습니다.
첫째로, 전체 화면을 한 장의 PNG로 캡처해 축소하면 1px짜리 연한 회색 테두리(#dbdbdb)가 주변 픽셀과 섞이면서 눈대중으로는 진한 회색(#aaaaaa)과 구분이 되지 않았습니다.
둘째로, 피그마 전체 화면 프레임 안에 예시로 배치된 버튼 인스턴스 하나가 디자이너가 작업하던 중 우연히 마우스 오버(hover) 상태로 놓여 있었는데, 에이전트가 스크린샷만 보고 그 호버 상태의 진한 테두리를 ‘기본 테두리’로 착각해 모든 버튼에 덮어씌운 것이었습니다.
결국 Figma MCP로 화면 전체 캡처가 아니라 개별 버튼 컴포넌트의 리프 노드 변수를 직접 조회하고 그 값을 사용하도록 스킬을 변경하고 나서야 이슈가 해결되었습니다.
스크린샷을 눈으로만 비교했다면 영영 못 잡았을 1px 단위의 미묘한 톤 차이였습니다.

또 하나 예상치 못한 복병은 기존 Next.js 웹 코드 안에 데스크탑 스타일과 모바일 스타일이 함께 섞여 있었다는 점이었습니다.
저희 웹은 Tailwind의 반응형 프리픽스(lg:gap-20, lg:p-10 등)로 데스크탑과 모바일을 한 컴포넌트에서 처리하거나, 화면에 따라 데스크탑에서는 컴팩트한 카드로 보여주고 모바일에서는 풀사이즈 카드나 바텀시트로 보여주는 곳들이 있었습니다.

처음에는 피그마 시안과 웹 코드, 앱 코드를 한꺼번에 열어두고 싱크를 맞췄더니, 에이전트가 웹 코드에 적힌 데스크탑용 패딩(lg:p-10)이나 폰트 크기를 모바일 앱에 그대로 가져다 쓰는 실수가 반복됐습니다.
이 시행착오 끝에 디자인 싱크 2라운드에서는 규칙을 완전히 바꿨습니다.
1편과 하편에 걸친 실험을 마무리하며, 처음 던졌던 질문으로 돌아가 보려 합니다.
“정말 AI와 모노레포가 있으면 모바일 웹을 앱으로 만들어 출시하는 데 2주면 충분할까?”
직접 부딪혀 본 제 대답은 “절반은 맞고, 절반은 틀렸다”입니다.
맞았던 절반은 ‘속도’입니다. 예전 같았으면 React Native 프로젝트를 세팅하고 수십 개의 화면과 컴포넌트를 일일이 손으로 옮기는 데만 한 달이 훌쩍 넘게 걸렸을 겁니다. 이미 돌아가는 Next.js 모바일 웹을 SSOT로 삼고, 모노레포와 자동화 스킬을 엮어 단 일주일 만에 모든 화면이 연결된 앱 POC를 눈앞에 띄운 것은 분명 AI 시대에만 가능한 속도였습니다.
틀렸던 절반은 ‘디테일’입니다. 화면을 빠르게 찍어내는 것과, 그 앱을 스토어에 출시해 실제 고객의 계약을 책임지게 만드는 것은 전혀 다른 영역이었습니다.
물론 토큰을 더 태운다거나 디테일보다는 속도에 집중하면 큰 문제가 안 될 수도 있었습니다. 하지만 사이드 프로젝트인 만큼 토큰을 무작정 태울 수도 없었고, 디테일을 타협하는 것은 제 생각일 뿐 함께 일하는 디자이너분께는 포기할 수 없는 문제였죠.
그럼에도 한 가지 확실한 건, 1주일 만에 초벌 앱을 뽑아내는 ‘속도’가 있었기에 남은 시간과 에너지를 온전히 이 ‘디테일한 체크’에 쏟아부을 수 있었다는 점입니다. 코딩 자체에 치여 정작 중요한 앱 UX와 푸시 경험, 디자인 완성도를 타협하는 대신, 진짜 제품다운 디테일을 집요하게 파고들 수 있었습니다.
혹시 지금 돌아가는 웹 서비스를 바탕으로 빠르게 앱 출시를 고민하고 계신다면, 초반에는 과감하게 자동화 워크플로로 속도를 내어 POC를 올리시되, 그다음에는 반드시 속도를 내려놓고 UX 경계·로직 SSOT·네이티브 기본기·토큰 단위 디자인을 하나씩 체크하는 시간을 일정에 꼭 넣어두시길 추천합니다.
긴 호흡의 상·하편 여정을 끝까지 읽어주셔서 감사합니다.
ⓒ요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.