7월 7일 월요일 퇴근하고 보고 싶었던 F1 영화를 보고 들어와서 샤워하고, 부랴부랴 내일 개모임 준비로 새벽 1시쯤부터 거의 피아니스트 마냥 타자를 두들기면서이 글을 쓰고 있다. 글 퀄리티는 모르겠다 ㅋㅋ
여튼 F1 영화 정말 감명 깊었고... 미쳤다! 🏎️ 추천드린다.

뭐 여튼 지난번 개모임을 빠진 이유는 바로 서울시와 신세계 그리고 우리 회사가 살짝 낀... 신세계 강남점에서 열린 넥스트로컬 팝업 때문이었다. 개발자가 갑자기 오프라인 중노동을 하게 된 사연을 들어보자.
사실 처음엔 "팝업? 그냥 브랜드 전시하고 끝 아닌가?"라고 생각했다. 하지만 막상 뚜껑을 열어보니... 이게 진짜 스타트업이구나 싶었다.

우리가 관리하는 파트너사 중 한 곳이 예상보다 준비가 부족했다. 사실 상품에 대한 준비보다는 그 파트너사 브랜드에 일을 할 사람이 없다는 게 문제였다. 그래서 우리가 직접 뛰어들어서 도왔는데, 이게 생각보다 물리적 노동이었다. 박스 상하차 정리부터 시작해서 시식용 샘플 자르기, 진열, 고객 응대까지...
"아, 이게 바로 스타트업의 맨땅에 헤딩이구나"라는 걸 몸소 체험했다. 🤸♂️
하지만 그래도 모든 일에는 배우는 게 있는 것 같다. 다행히 우리 프론트엔드 개발자분이 팝업 오픈 새벽에 실제 팝업 현장을 가서 동향을 파악하고, 파이어베이스랑 프론트엔드를 통해 약 2시간 안으로 POS 시스템을 뚝딱 만들어주셔서 매출 관리가 정말 편했다.

실시간으로 얼마나 팔렸는지 보는 게 이렇게 짜릿할 줄이야! 또한 이 실시간 매출 시스템 덕분에 여의도 사무실에서 일을 할 때 혹은 내가 팝업 스토어에서 일을 할 때 서로 확인할 수 있어서 매우 용이했던 것 같다.
보통 팝업은 마이너스를 생각하고 홍보 목적으로만 한다고들 하는데, 결과적으로 우리가 직접 관리한 브랜드들은 꽤 괜찮은 매출을 기록했다. 그 중에도 특히 우리가 직접 관리했던 부스의 매출도 괜찮은 편이었고, 신세계 측에서도 만족해해서 상시 판매 계약(곤돌라/엔캡 = 쉽게 말하면 매대?)까지 따내게 됐다.
이걸로 개발 외적인, 약간 서비스의 외적인 부분으로도 회사에 고정 매출이 생기게 되었고, 파트너사들과의 파이프라인도 따기가 조금 쉬워진 부분이 되었다.
이제 서울시와 신세계 사이에서 줄다리기 하는 포지션에서 이제야 좀 우리가 주체가 된 것 같은 느낌이 든다. 뭔가 회사가 한 단계 성장한 느낌도 들고, 이 팝업으로 인한 유입이 유의미하게 서비스로 보여지기도 해서 어떻게 보면 개발자가 아닌 사람도 할 수 있는 막노동을 한 게 꼭 나쁘지만은 않았다라는 생각이 든다.
아래는 팝업하면서 진짜 개힘들었지만 개맛있게 먹은 생선까스와 그외의 밥들…



한편으로는 외주 프로젝트도 열심히 진행 중이다. 1차 데드라인이 19일이라 지금 시점에서 약 10일 정도 남았는데, 솔직히 조금 밀렸다. 팝업이랑 뭐랑 좀 정리하고 쉬고 놀고 하다보니 밀렸다... ㅠㅠ



그리고 잠깐 들어가기 전에, 이 개발이 끝날 때 즈음에는 다른 외주도 한 개 더 진행하게 생겼다. 대학 후배가 도와줄 수 있냐는 부분에서 시작하게 되었는데, 어쩌다 보니 보수를 받고 전문적으로 B2B 베어링과 같은 사업을 하는 사이트의 외주를, 이번에는 백엔드 부분만 만들게 되었다. 물론 총괄을 도와주게 될 것 같다.
보수 자체도 생각보다 제대로 받게 되어서 아마 지금 하는 중장비 외주 작업을 끝내게 되면 바로 들어가게 될 것 같은 기분이 든다.
그래서 다시 돌아와서 원래 하던 외주는 어떻게 되었냐면, 일단 처음엔 정말 바이브로만 코딩했다. "돌아가게 만들고 보자!" 정신으로 신나게 바이브 코딩을 달렸는데, 이제 보니 어느 정도 하다 보니 코드가 스파게티가 되었고, 기획도 없고, 디자인도 없고, 그냥 클라이언트의 요구사항에만 맞춰 만들다 보니 좀 코드적인 순회를 많이 돈 것 같다.
이거 필요하다 해서 백엔드 만들고, 이거 필요하다고 해서 프론트엔드 만들고... 아무래도 회사에서는 적어도 최소한의 기획이나 디자인적 요소는 보고 만들다 보니 큰 어려움은 없었던 것 같은데, 기획이 없는 상태에서 혼자하는 것은 생각보다는 속도가 나는 느낌을 받지는 못했다.
커밋 메시지를 보면 그 절망이 고스란히 담겨있다.
fix: 바이브코딩으로 만든 구조 대수정feat: 대대적인 파일 위치 수정 및 리팩토링 준비asdf ← 이러면 안 되지만 하다가 귀찮은데 백업하고 싶어서 이렇게 개판으로 커밋한 것도 있다.그래도 이제는 좀 다시 구체적으로 하고 있다.
일전보다 외주 작업 부분에 뇌를 더 많이 쓰고 있고, 이제는 괜찮은 UI와 기능이 머릿속에 만들어지다 보니 좀 명확해져서 바이브 코딩을 해야 되는 부분도 mdc라는 파일을 만들어서 프롬프트로 마치 헌법 1조처럼 코드 스타일 컨벤션, 에러 처리까지 구체적으로 정의해서 하고 있다. 어쩌다 블로그 글을 읽다가 발견했는데, Cusor를 효율적으로 쓰는 방법론 중에 이런 게 있었더라…
또한 백엔드의 경우 람다로 구현을 했기 때문에 최대한 한 페이지에 1개에서 2개의 API로 보통 뷰단을 커버리지 칠 수 있도록 만들고 있다. 예를 들면 아래 JSON 형식처럼 프론트에서는 map만 돌리면 그 페이지의 데이터를 전부 가져올 수 있는 형태로 말이다.
또한 모든 뷰단의 데이터를 어드민 페이지에서 글씨 및 링크까지 변환할 수 있도록 대부분에 적용하였다.
JSON{ "home": { "_id": "68669c64698dcb6c6959043b", "key": "main", "title": "안전하고 신뢰할 수 있는 중장비 렌탈 서비스", "heroSection": { "title": "하늘 위 모든 솔루션,<br/>어울림 스카이와 함께합니다.", "subtitle": "안전하고 신뢰할 수 있는 중장비 렌탈 서비스", "ctaButtons": [ { "text": "🏗️ 무료 견적 받기", "link": "/contact" }, { "text": "📋 서비스 안내", "link": "/service-guide" } ], "backgroundImageUrls": [ { "url": "https://d1h7waosxik1t4.cloudfront.net/home/hero-images/1a5674de-77a2-46c2-bfaf-546097d7e539_1751743884833.webp", "order": 0, "isActive": true } ] } }, "notices": [...], "workShowcases": [...], "customerReviews": [...], "contactInfo": { "contactPhoneNumber": "010-1234-5678", "kakaoOpenChatUrl": "https://open.kakao.com/o/example" } }
이렇게 하면 프론트엔드에서는 정말 간단하게 map만 돌리면 끝이고, 백엔드에서는 한 번의 API 호출로 페이지 전체를 구성할 수 있다. 람다 특성상 호출 수를 최소화하는 게 비용 면에서도 좋고!
프론트엔드도 대수술을 했다.
이 부분도 mdc 부분의 프롬프트로 엄격하게 정의하여서 기존의 useState + useEffect 지옥에서 벗어나서 React Query(Tanstack Query)로 갈아탔다. 캐싱, 리페칭, 에러 핸들링이 이렇게 깔끔해질 줄이야!
그래도 얼추 이제 마무리 단계다. 남은 것들은 아래 정도일 것 같다:
디자인 시스템 정리 - 아직 정리하지 못한 컴포넌트들이 중구난방
SEO 최적화 - 메타태그, 사이트맵, robots.txt
Google Analytics - 데이터 수집 체계 구축
성능 최적화 - 이미지 lazy loading, 번들 사이즈 최적화
바이브 코딩의 결과물을 이제야 제대로 된 제품으로 만들고 있다.
팝업을 병행하고 최근까지까지 사이사이에 회사 백오피스도 열심히 개발했다.
사실 앞으로는 백오피스라고 부르기도 모호한게 이제 또 이 백오피스의 기능이 커져서 진짜 직원들을 관리하는 백오피스 내부툴과 서비스 어드민으로 분리하기로 했다.
가장 큰 변화는 Role-Based Access Control(RBAC) 시스템 도입이었다. 기존에는 사용자별로 간단하게 role을 나눈 구조였는데, 이제 B2B를 염두해서 타이트한 권한 시스템으로 완전히 바꿨다.
우리가 정한 RBAC의 핵심 철학: 정의는 코드로, 구성은 데이터로
TypeScript// 모든 권한을 Enum으로 정의 export enum Permission { USER_CREATE = 'user:create', USER_READ = 'user:read', PARTNER_CREATE = 'partner:create', EMAIL_TEMPLATE_READ = 'email_template:read', // ... 더 많은 권한들 }
파트너 초대, 크루 초대 등 다양한 이메일을 관리하는 시스템도 만들었다. 매번 이 이메일 폼을 하드코딩할 수는 없으니까 그냥 앞으로의 발전 가능성을 생각하여 이메일 등의 시스템을
TypeScript// 나름 템플릿 변수 치환 방법 const htmlContent = ` <h1>안녕하세요 {{userName}}님!</h1> <p>{{companyName}}에서 초대장을 보냈습니다.</p> <a href="{{inviteLink}}">가입하기</a> `;
백엔드 개발자가 이메일 form 디자인까지 해야 하는 현실... 하지만 이제는 AI가 많이 도와주니까! 🤖
특히 중요했던 건 기존 사용자 스키마를 건드리지 않는 것이었다:
user.schema.ts: 순수한 사용자 정보만
rbac_user.schema.ts: 백오피스 접근 권한과 역할 매핑
role.schema.ts: 동적 역할과 권한 집합
서비스 사용자 데이터와 백오피스 권한을 완전히 분리했다. 나중에 권한 시스템에 문제가 생겨도 서비스는 안전하다!



뭐 여튼 사실 더더 많은 내용들이 있지만 체력 이슈로 이번 개모임 정리는 이 정도에서 마무리하고 실제 UI와 개발한 기능을 보여드리면서 마무리를 하려고 한다...
그래서 결과적으로 느낀 점은 오늘 감명 깊게 본 F1 영화에 비유하자면 열심히 달리고 적당히 힘들면 피트스탑을 해야 될 것 같다.
따라서 사실상 외주로 번 돈으로 몽골여행도 예약했다 !무려 5박 6일로 8월에 여행할 예정이다
그쯤에서 한번 쉬면서 리프레쉬 할려고한다.
그리고 또 다시 나는 무언가를 하겠지... 그리고 기획 중인 동아리 “콩방”도...
뭔가 오늘 본 영화에 따르면 F1 레이서들처럼 계속 달리다 보면 연료도 떨어지고 타이어도 닳는다. 적절한 타이밍에 피트스탑을 해야 더 오래, 더 빠르게 달릴 수 있는 법이다. 🏁
지금 내 삶도 마찬가지인 것 같다. 신세계 팝업부터 외주, 백오피스 개발까지 정말 많은 일들이 있었지만, 7월까지 달리고(예비군은 아직 모름 ㅎㅎ…), 8월 친구들과 몽골 여행으로 한번 제대로 쉬고 오면 또 새로운 에너지로 달릴 수 있을 것 같다.
P.S. 19일 마감 맞출 수 있을까... 그리고 새로운 사이트 외주도... 일단 달려보자! 💨