← 목록으로

모두의 폰이 노래방 리모컨? 10년 차 개발자의 VKara 앱 재탄생 스토리

2026. 6. 2.

모두의 폰이 노래방 리모컨? 10년 차 개발자의 VKara 앱 재탄생 스토리

오랜만에 개발 블로그 글을 쓰려니 설레네요. 최근 GitHub Finish-Up-A-Thon 챌린지에 참여하면서 잠들어 있던 사이드 프로젝트, VKara를 멋지게 부활시켰습니다. 그 과정을 여러분과 공유하고 싶어 이렇게 키보드를 두드립니다.

무엇을 만들었나

VKara는 집에서 친구나 가족들과 함께 노래 부를 수 있도록 만든 브라우저 기반의 노래방 앱입니다.

오해는 마세요. 유튜브를 대체하려는 시도는 아닙니다.

유튜브는 이미 영상을 재생하는 데 탁월합니다. 우리가 필요로 하는 거의 모든 노래방 영상이 그곳에 있죠. 하지만 유튜브는 여러 사람이 함께 곡을 선택하고 관리하는 '노래방' 경험을 위해 설계된 서비스는 아닙니다.

VKara는 바로 이 지점을 파고들었습니다.

메인 재생 화면으로는 TV나 노트북에서 VKara를 엽니다. 나머지 사람들은 각자의 휴대폰으로 4자리 룸 코드나 QR 코드를 이용해 같은 룸에 참여하죠. 그러면 누구나 곡을 검색하고, 대기열에 추가하고, 재생/일시정지/건너뛰기 등의 제어를 할 수 있습니다.

TV는 그저 영상을 재생하는 역할만 합니다.

모두의 휴대폰이 각자의 리모컨이 되는 거죠.

이것이 VKara의 핵심 아이디어입니다.

한 문장으로 설명하기에는 충분히 간단하죠. 하지만 주말 이틀 만에 만들 수 있을 만큼 간단하진 않았습니다. 그 부분은 제가 직접 부딪혀가며 깨달은 교훈입니다. 제가 실무에서 수많은 프로젝트를 경험했지만, 아이디어의 단순함과 구현의 복잡성 사이의 간극은 늘 예상 밖의 난관을 안겨주곤 하더라고요. 이 VKara 프로젝트도 예외는 아니었습니다.

데모

링크는 다음과 같습니다:

작은 경고 하나 드리자면, 데모는 제한된 리소스로 운영되고 있습니다. 혹시 느리게 느껴지더라도 잠시만 기다려 주세요. 아직 학생 지갑이라 주머니 사정이 녹록치 않네요. 😂

사용 흐름은 이렇습니다:

  1. TV나 노트북에서 VKara를 엽니다.
  2. 휴대폰으로 코드 또는 QR을 통해 룸에 참여합니다.
  3. 노래방 영상을 검색합니다.
  4. 공유 대기열에 추가합니다.
  5. 함께 재생을 제어합니다.

Old VKara version: desktop, mobile search, and mobile controls

이전 버전: 아이디어는 통했지만, 제품은 여전히 노래방 용도에 억지로 끼워 맞춘 비디오 앱처럼 느껴졌습니다.

Current VKara mobile flow

현재 버전: 모바일 흐름은 이제 룸 참여, 검색, 액션 선택, 재생 제어에 집중되어 있습니다.

무엇이 바뀌었나

이전:

  • 프런트엔드와 백엔드가 분리된 리포지토리에 있었습니다.
  • 모바일 UI는 기본적으로 데스크톱 UI를 압축해 놓은 형태였습니다.
  • WebSocket 계약(contracts)이 여러 프로젝트에 흩어져 있었습니다.
  • TV 화면에 너무 많은 제어 기능이 있었습니다.

이후:

  • 모든 것을 Bun 워크스페이스 모노레포(monorepo)로 통합했습니다.
  • 공유 API/WebSocket 계약을 packages/shared-types로 옮겼습니다.
  • TV는 화면, 휴대폰은 리모컨이라는 두 가지 역할에 맞춰 제품을 재구축했습니다.
  • 룸 참여, 검색, 대기열, 제어 중심으로 모바일 흐름을 간소화했습니다.
  • 룸 상태, 재접속 동작, 재생 동기화, 배포 과정을 개선했습니다.

부활 스토리

VKara는 2025년 초쯤 시작했습니다.

당시 제 목표는 굉장히 개인적인 것이었습니다. 친구들과 집에서 노래방을 더 즐겁게 즐길 수 있는 방법을 찾고 싶었죠. 평소에는 TV로 유튜브를 켜고 노래방 영상을 검색한 다음, 휴대폰을 서로 넘겨가며 제어하는 식이었습니다.

이것도 나름대로 작동은 했지만, 영 불편하고 어색했어요.

한 사람이 곡을 검색하는 동안 다른 사람이 실수로 바로 영상을 재생해버리기도 하고요. 다음 곡이 뭔지 알고 싶어 하는 사람도 있었지만, 대기열은 불분명했습니다. 게다가 캐스팅(casting) 기능은 보통 모두가 같은 Wi-Fi 네트워크에 연결되어 있어야만 했죠.

그래서 저는 프로토타입을 만들었습니다.

첫 버전은 기술적으로는 작동했습니다. 여기서 "작동했다"는 말은 위험한 종류의 작동을 의미합니다. 아이디어를 증명하기에는 충분했지만, 실제 노래방 밤에 자신 있게 내놓을 정도는 아니었죠.

제품에는 여러 문제가 있었습니다:

  • 데스크톱 화면에 영상 주변으로 너무 많은 제어 기능이 붙어 있었습니다.
  • 모바일 UI는 마치 압축된 데스크톱 앱처럼 느껴졌습니다.
  • 검색, 대기열, 제어 기능들이 너무 가깝게 붙어 있었죠.
  • 실시간 상태 관리가 불안정했습니다.
  • 프런트엔드와 백엔드가 별도의 리포지토리에 있었습니다.
  • 공유 타입(shared types)과 WebSocket 계약이 한 곳에서 깔끔하게 관리되지 않았습니다.

특히 마지막 부분이 예상보다 훨씬 더 중요했습니다.

VKara는 실시간 앱입니다. 프런트엔드와 백엔드는 룸 상태, 대기열 상태, 영상 메타데이터, 플레이어 명령어, WebSocket 메시지에 대해 서로 합의해야 합니다. 이런 계약(contract)들이 서로 다른 곳에 흩어져 있으면, 작은 변경 하나도 큰 위험으로 느껴집니다. 실제로 제가 참여했던 실시간 서비스 개발에서도 이 계약 관리가 얼마나 중요한지 뼈저리게 느꼈습니다. 프런트엔드와 백엔드가 서로 다른 언어를 쓰면, 작은 기능 변경 하나도 엄청난 리스크가 되죠.

결국 저는 작업 중단을 결정했습니다.

아이디어가 나빠서가 아니었습니다.

프로젝트를 변경하기가 너무 힘들었기 때문입니다.

올해 다시 이 프로젝트로 돌아왔을 때, 저는 새로운 기능을 추가하는 것부터 시작하지 않았습니다. 대신 프로젝트를 '안전하게 변경할 수 있는' 상태로 만드는 것부터 착수했습니다.

첫 번째 주요 단계는 이전 프런트엔드 리포와 이전 백엔드 리포를 하나의 Bun 워크스페이스 모노레포로 합치는 것이었습니다:

apps/web                # Next.js 프런트엔드
apps/api                # Elysia + WebSocket 백엔드
packages/shared-types   # 공유 API/WebSocket 계약
packages/shared-utils   # 공유 헬퍼 함수

이것은 단순히 폴더를 정리하는 것을 넘어섰습니다. 프로젝트를 작업하는 방식 자체를 바꿔 놓았죠.

모노레포 마이그레이션 이후, 웹과 API 변경 사항을 한 번에 확인할 수 있게 되었습니다. 공유 계약은 packages/shared-types에 모아두면 됩니다. 룸 이벤트나 비디오 형태를 변경할 때 더 이상 두 개의 리포를 뒤져가며 모든 것을 기억하고 있는지 걱정할 필요가 없어졌습니다.

두 번째 주요 변경 사항은 UX였습니다.

처음에는 VKara를 그저 반응형 웹사이트처럼 취급했습니다:

데스크톱 레이아웃. 모바일 레이아웃. 끝.

하지만 이것은 잘못된 사고방식이었습니다.

TV는 단순히 큰 휴대폰이 아닙니다. 휴대폰도 단순히 작은 TV가 아니죠.

각각 다른 역할을 합니다.

그래서 VKara는 이제 두 가지 역할로 생각합니다:

  • TV / 노트북: 재생 화면
  • 휴대폰: 리모컨

이 하나의 결정이 제품을 훨씬 명확하게 만들었습니다.

TV는 깔끔하게 유지되어야 합니다. 영상, 룸 코드 또는 QR, 그리고 기본적인 룸 제어 기능만 보여주면 됩니다.

휴대폰은 룸 참여, 곡 검색, 대기열 추가, 그리고 재생 관련 동작을 처리해야 합니다.

실제 흐름은 이렇게 바뀌었습니다:

룸에 참여 → 곡 검색 → 대기열에 추가 → 계속 노래 부르기.

이번 부활 작업의 상당 부분은 그리 화려하지 않았습니다. 사용자들이 오직 실패했을 때만 알아차리는 그런 종류의 작업이었죠. WebSocket 룸 상태, 재접속 동작, 재생 동기화, 대기열 업데이트, 룸 정리, 유튜브 메타데이터 형태, Docker 빌드, GitHub Actions, 그리고 모노레포 마이그레이션 이후의 Vercel 배포 등이 여기에 해당합니다.

노래방에서 사람들이 아키텍처가 얼마나 똑똑한지 신경 쓸 리 만무합니다.

그들은 버튼을 눌렀을 때 룸이 즉각 반응하는지에만 관심이 있죠.

GitHub Copilot과의 경험

첫 번째 버전과 이번 부활 버전의 가장 큰 차이는 비단 GitHub Copilot 때문만은 아니었습니다.

제가 Copilot을 어떻게 사용했느냐에 있었습니다.

첫 번째 버전에서는 제 AI 보조 워크플로우가 너무 순진했습니다. 코드를 요청하고, 버그를 손으로 고치며 빠르게 나아갔죠. 이는 프로토타입을 만드는 데는 도움이 되었지만, 늘 제가 신뢰할 만한 코드를 남긴 것은 아니었습니다.

이번에는 GitHub Copilot을 '마무리 파트너'처럼 활용했습니다.

코드를 변경하기 전에, Ask Mode를 사용해서 프로젝트를 다시 이해했습니다. VKara는 오랫동안 방치되어 있었고, 솔직히 제가 내린 모든 결정을 다 기억하지 못했죠.

제가 처음 던진 질문 중 하나는 대략 이런 식이었습니다:

"이 프런트엔드에서 SWR이나 React Query를 왜 사용하지 않는 거죠? 대신 뭘 쓰고 있는 거죠?"

답변은 앱이 클라이언트 상태를 위해 Zustand 스토어를 사용하고, 서버 데이터는 직접 fetch 호출을 쓰며, 룸/플레이어 상태는 WebSocket 기반의 실시간 동기화를 사용한다는 것을 다시 일깨워주었습니다.

그 질문 덕분에 엉뚱한 레이어를 건드리는 실수를 막을 수 있었습니다.

Using Ask Mode in VS Code to understand the old frontend data flow

Ask Mode는 코드를 수정하기 전에 이전 데이터 흐름을 이해하는 데 큰 도움이 되었습니다.

더 큰 작업을 할 때는 실행에 앞서 Plan Mode를 활용했습니다.

그냥 "이 두 프로젝트를 더 깔끔하게 만들어 줘"라고 말하는 대신,

저는 실제 마이그레이션 계획을 요청했습니다:

"vkaravkara-api를 하나의 모노레포로 합쳐줘. 히스토리는 유지하고, Bun 워크스페이스를 사용해. 공유 타입들을 찾아서 공유 패키지로 계약들을 옮겨줘."

Using Plan Mode in VS Code for the monorepo migration

Plan Mode는 막연한 리팩터링을 더 안전한 마이그레이션 경로로 바꿔주었습니다.

이것이 저의 반복적인 작업 루프가 되었습니다:

구상 → 질문 → 계획 → 실행 → 검증 → 반복

버그가 발생하면 정확한 TypeScript 에러나 Docker 로그를 붙여넣었습니다.

UI 작업의 경우, 스크린샷을 보여주고 무엇이 문제인지 설명했습니다.

아키텍처 질문에 대해서는 코드를 건드리기 전에 대안을 물어보았습니다.

이것이 진정한 업그레이드였습니다.

첫 번째 버전에서는 주로 AI에게 코드를 생성해달라고 요청했습니다.

이번 부활 과정에서는 GitHub Copilot에게 조사하고, 비교하고, 계획하고, 리팩토링하고, 검증하고, 트레이드오프(trade-offs)를 설명해달라고 요청했습니다.

최종 결과물이 여전히 완벽하지는 않습니다.

하지만 이제는 무엇이 정말 중요한지 알게 되었습니다.

'AI 가사'나 로드맵에서 멋져 보이는 거창한 기능들이 아닙니다.

진정한 문제들은 더 작고 인간적인 것들이죠. 누가 건너뛸 수 있는지, 누가 이 곡을 추가했는지, 누구 차례인지, 한 사람이 대기열을 모두 채우는 것을 어떻게 막을지, 누가 휴대폰을 닫았다가도 빠르게 다시 룸에 합류하게 할지, 그리고 다음 노래방 밤을 위해 같은 대기열을 어떻게 다시 불러올지 같은 것들입니다.

유튜브는 이미 우리에게 영상을 제공합니다.

VKara는 룸을 '평온하게' 느끼도록 만들어야 합니다.

이제 VKara는 더 이상 저만을 위한 프로토타입이 아닙니다.

TV를 켜고 친구에게 휴대폰을 건네며 이렇게 말할 수 있는 무언가입니다:

"다음 곡 골라 봐."

그리고 솔직히, 그것만으로도 이미 큰 승리처럼 느껴집니다.


원문: https://dev.to/lehuygiang28/i-rebuilt-my-karaoke-app-so-everyones-phone-could-be-a-remote-4k8b 수집일: 2026-06-02 02:14:10