3일 만에 LoL 올타임 드래프트를 런칭하며 — 기획부터 배포까지

Hobin·2026년 6월 15일

역대 LoL 프로 선수를 카드로 드래프트해 가상의 시즌을 시뮬레이션하는 웹 게임을 혼자서 3일 만에 기획·개발·런칭했다. DB 없이 정적 JSON과 클라이언트 사이드 연산만으로 구현해 비용 0과 트래픽 안정성을 동시에 확보했고, 런칭 후 Vercel Analytics 기준 누적 방문자 약 600명, 누적 페이지뷰 약 13,000을 기록했다. 이 글은 결과 자랑이 아니라, 제약(3일·비용 0·1인) 속에서 내린 기술적 의사결정과 그 근거를 정리한 기록이다.

🔗 grandslamlol.vercel.app


1. 왜 만들었나

역대 선수를 비교하려는 수요는 예전부터 있었다. "롤판 풋볼매니저가 나왔으면 좋겠다", "시즌별로 붙이면 누가 제일 강할까" 같은 이야기는 커뮤니티에서 반복됐지만, 그걸 제대로 구현한 형태의 서비스는 없었다.

결정적 계기는 다른 종목이었다. 월드컵 시즌에 랜덤 올타임 스쿼드로 전승 우승을 노리는 드래프트 게임이 유행했고, NBA·NFL·EPL 등 여러 종목 버전이 나왔다. 이 포맷이 흥미로웠고, 자연스럽게 "LoL 버전은 왜 없지?"라는 생각으로 이어졌다.

기획을 시작한 게 6월 10일이었는데, 찾아보니 불과 두 시간 전에 누군가 비슷한 걸 만들어 배포한 상태였다. 다만 선수 사진도 없고, 데이터 조사가 부실한 양산형에 가까웠다. 그래서 "정확한 지표 + 선수 사진 + 개선된 UI"로 완성도 차이를 만들어 팬층을 잡겠다는 방향을 잡았다.

타이밍도 유리했다. MSI 2026 개막 직전이라 e스포츠 관심이 오르는 시점이었고, 약한 선점자만 있는 상황이었다. 약한 선점자만 있을 때 완성도로 카테고리를 가져온다는 판단이었다.

제약은 처음부터 명확하게 잡았다.

  • 기간: 3일 — LoL은 인기 종목이라 같은 생각을 하는 사람이 또 나올 수 있다고 봤다
  • 비용: 0원 — 비상업 프로젝트
  • 인력: 1인 — 기획·개발·디자인·배포 전부

"이 세 제약이 이후 모든 기술 의사결정의 기준이 됐다.
그리고 고민을 오래 끌지 않았다 — 경쟁자가 이미 나온 상황이라
속도가 중요했고, 기획을 정리한 지 한 시간 만에 개발에 들어갔다."


2. 무엇을 만들었나

GRANDSLAM은 랜덤으로 주어지는 (팀 × 시즌) 조합에서 역대 프로 선수 카드를 드래프트해, 5인 올타임 팀을 만들고 2026 시즌 캠페인을 시뮬레이션하는 웹 게임이다.

핵심 루프는 단순하다.

스핀(랜덤 팀/시즌) → 5개 포지션 드래프트 → 시뮬레이션 → 결과 등급 → 공유
  • 데이터 범위: 2013~2025, LCK·LPL·LEC·LCS + 국제 대회
  • 선수 평가: 각 선수-시즌을 OVR(종합 능력치)로 수치화
  • 두 가지 난이도: 기본(4스테이지)과 하드 모드(7스테이지 — LCK Cup·First Stand·MSI·EWC·Worlds 등 실제 2026 대회 구조 반영)
  • 결과 공유: 만든 팀과 시뮬 결과를 URL로 공유


3. 기술적 의사결정

이 프로젝트에서 가장 고민한 부분이자, 이 글의 핵심이다. "무엇을 만들었나"보다 "왜 그렇게 만들었나"가 더 중요하다고 생각한다.

기술 스택: Next.js + TypeScript + Tailwind CSS, Vercel 배포, Cloudflare R2(이미지), 데이터베이스 없음.

3-1. 데이터베이스를 쓰지 않은 이유

가장 큰 결정은 DB를 아예 두지 않은 것이다. 보통 웹 서비스라면 당연히 백엔드와 DB를 두지만, 이 프로젝트의 제약과 성격을 보면 DB는 오히려 불필요했다.

판단 근거

  1. 저장할 사용자 데이터가 없다. 로그인도, 회원가입도, "내 기록 저장"도 없는 일회성 게임이다. 저장할 상태가 없으면 DB도 필요 없다.
  2. 트래픽 안정성. 커뮤니티에서 한 번 터지면 순간 트래픽이 몰린다. 정적 파일은 DB 부하 없이 CDN에서 그대로 서빙되므로 트래픽이 몰려도 죽지 않는다.
  3. 비용 0. DB 서버를 띄우면 비용이 발생한다. 비상업 프로젝트에서 정적 호스팅은 사실상 무료다.
  4. 속도. 매 요청마다 DB를 왕복하는 대신, 빌드 시점에 생성한 정적 JSON을 클라이언트가 바로 읽는다. 시뮬레이션 연산도 서버 왕복 없이 브라우저에서 1초 미만으로 처리된다.

구현 방식: 선수·팀·상대 데이터는 빌드 시점에 외부 소스에서 수집해 정적 JSON으로 번들링한다. 런타임에는 API 호출이 전혀 없다.

players.json        — 선수 데이터 (이름, OVR, 사진 URL, 팀, 시즌)
teams.json          — 팀 데이터
opponents-2026.json — 시뮬레이션 상대 데이터

트레이드오프: 데이터를 갱신하려면 재빌드가 필요하다. 하지만 선수 데이터는 시즌 단위로만 바뀌므로, 실시간성이 필요 없는 이 프로젝트에는 맞는 선택이었다.

3-2. DB 없이 결과 공유 구현하기

DB가 없으면 "결과 공유 링크"를 어떻게 만들까? 결과를 어딘가 저장해야 공유가 되는 것 아닌가? 이 문제를 URL 자체에 상태를 담고, 받는 쪽에서 재계산하는 방식으로 풀었다.

/r?p={선수 5명}&s={시드}

결과를 저장하는 대신, "누구를 뽑았는지(picks) + 시드 값"만 URL에 담는다. 링크를 열면 그 picks로 시뮬레이션을 다시 실행한다. 시드가 고정되어 있으므로 난수가 재현되고, 몇 번을 돌려도 같은 결과가 나온다.

이 방식의 장점

  • 저장소 불필요. 결과를 DB에 쌓지 않아도 공유가 된다.
  • 위변조 방지. 결과를 URL에 직접 넣으면 조작할 수 있지만, "재료(picks)"만 넣고 결과는 재계산하므로 조작이 무의미하다.
  • URL 길이 관리. 전체 결과를 직렬화하는 대신 입력값만 담으므로 URL이 짧다.

"저장 대신 재계산"이라는 발상 하나로 위변조 방지와 URL 길이 문제를 동시에 해결했다.

3-3. 외부 API Rate Limit 병목과 병렬 수집 파이프라인

데이터 수집에서 가장 큰 병목은 외부 위키 소스(Leaguepedia)의 API Rate Limit이었다. 2013~2025년, 4개 리그와 국제 대회의 선수-시즌 데이터를 순차로 긁자, 수집이 진행될수록 응답이 느려져 나중에는 선수 한 명을 받는 데 15분 가까이 걸리는 지경이 됐다. 3일이라는 데드라인 안에서는 그대로 둘 수 없는 병목이었다.

그래서 순차 수집을 버리고, 수집 작업을 여러 개로 쪼개 동시에 돌리는 병렬 파이프라인으로 바꿨다. 핵심은 세 가지였다.

  • 구간 분할: 여러 작업을 동시에 돌리면 같은 파일에 동시에 쓰면서 데이터가 꼬일 수 있다. 이를 막기 위해 작업별로 담당 연도와 출력 파일 범위를 명세(마크다운 가이드)로 엄격히 분리해, 각 작업이 서로 다른 파일만 건드리도록 격리했다.
  • 통합 검증: 모든 병렬 수집이 끝난 뒤, 전체 데이터를 한 번에 교차 검증하는 단계를 따로 뒀다. 연도별로 따로 모은 데이터가 누락이나 중복 없이 합쳐졌는지 점검하는 역할이다.
  • 타겟 재수집: 검증에서 누락이 발견되면, 전체를 처음부터 다시 돌리는 대신 문제가 된 구간만 특정해 다시 수집하도록 했다. 전체 재실행 비용을 피하면서 데이터 정합성을 맞추기 위한 처리였다.

이렇게 수집 → 가공 → JSON 생성까지를 빌드 파이프라인으로 자동화해, 사람이 손으로 데이터를 넣지 않아도 되도록 했다.

참고로 이 병렬 작업과 격리·검증 구조는, 코딩 에이전트에게 작업 범위를 나눠 맡기고 마지막에 검수 단계를 두는 방식으로 구성했다. "작업을 어떻게 쪼개고, 충돌을 어떻게 막고, 어떻게 검증할지"를 설계하는 일이 핵심이었다.

3-4. 이미지 셀프 호스팅 (Cloudflare R2)

선수 사진은 외부 이미지를 그대로 핫링크하지 않고, 직접 가공해 Cloudflare R2에 호스팅했다.

판단 근거

  • 핫링크 리스크. 외부 위키 이미지 주소를 직접 쓰면, 그쪽 정책(Referer 검증, CDN 변경)에 따라 사진이 한꺼번에 깨질 수 있다.
  • 용량 최적화. 원본을 256px webp로 리사이즈해 용량을 줄였다.
  • 비용. R2는 이그레스(전송) 비용이 무료라, 사진을 아무리 많이 노출해도 추가 비용이 없다.

파이프라인: 다운로드 → 256px webp 변환 → R2 업로드 → JSON에는 R2 URL만 기록.


4. 런칭과 지표

런칭 후 커뮤니티를 통해 5일간 초기 유입을 확보했다.

  • 누적 방문자: Vercel Analytics 기준 약 600명 (Visitors)
  • 누적 페이지뷰: - 약 13,000 (방문자 대비 인당 약 21회) — 한 명이 여러 번 드래프트를 돌렸다는 뜻으로, 인게이지먼트 밀도는 높았다
  • 이탈률: 약 37%

지표는 서비스 성격과 함께 봐야 의미가 있다고 생각한다. 이 게임은 "한 번 해보고 결과를 공유하는" 비타민형 서비스라, 구조적으로 재방문이 약하고 이탈률이 높은 게 정상이다. 실제로 초기 스파이크 이후 일별 유입은 빠르게 꺾였다. "바이럴 유입으로 약한 리텐션을 덮는다"는 초기 가설은 그대로는 성립하지 않았고, 잔존 동기가 없으면 유입을 늘려도 트래픽이 새는 양동이라는 걸 데이터로 확인했다. 그래서 리텐션에 매달리는 대신, 이미 일어나고 있던 결과 공유 행동을 유입 경로로 전환하는 데 집중했다. (구체적인 과정은 5장에서 다룬다.)


5. 유저 피드백 → 개선

런칭 직후 커뮤니티에서 구체적인 피드백을 받았고, 빠르게 반영했다. 이 과정이 개인적으로 가장 의미 있었다.

한 유저는 PC 화면의 문제를 조목조목 짚어줬다. 하단 여백이 과하고, 선수 정보 텍스트가 너무 작고, 일부 선수(13시즌) 카드는 배경과 OVR 숫자 색이 겹쳐 아예 안 보인다는 지적이었다.

이런 피드백을 받아 다음을 즉시 수정했다.

  • 가시성 버그: 배경과 OVR 색이 겹쳐 안 보이던 카드 → 색상 대비 조정
  • PC 레이아웃: 하단 여백 과다, 선수 정보 텍스트 과소 → 레이아웃 재조정
  • 리롤 버그: 특정 상황에서 리롤이 동작하지 않던 문제 → 수정

유저의 대체 행동을 포착해 유입 경로로 — 워터마크

지표를 보면서 한 가지를 발견했다. 사이트에 결과를 공유하는 버튼이 있었지만, 실제 유저는 그 버튼을 누르는 대신 결과 화면을 직접 캡처해 커뮤니티에 올리는 행동을 더 많이 했다. 외부 커뮤니티에 올라온 캡처본을 보면서 이 패턴을 확인했다.

그렇다면 공유 버튼을 다듬기보다, 이미 일어나고 있는 캡처 행동을 유입으로 연결하는 게 맞다고 판단했다. 문제는 두 가지였다. 캡처에 로고(GRANDSLAM)만 찍히고 사이트 주소가 남지 않았고, "GRANDSLAM"으로 검색하면 동명의 일반 결과(테니스·골프 그랜드슬램 등)에 묻혀 사이트를 찾기 어려웠다.

그래서 로고 바로 아래에 사이트 주소를 작게 노출하도록 헤더 레이아웃을 수정·배포했다. 캡처 화면의 어느 부분을 잘라도 헤더만 들어가면 주소가 따라가므로, 유저들이 자발적으로 올리는 스크린샷이 그대로 유입 경로가 되도록 한 것이다. 내장 공유 기능을 늘리는 대신, 유저가 이미 하고 있는 행동을 관찰해 거기에 맞춰 제품을 고친 사례였다.

피드백을 "재해석"한 사례 — 하드 모드 분리

가장 기억에 남는 건 한 유저의 제안이었다. "3스플릿 + 퍼스트 스탠드 + EWC를 전부 반영해달라"는 요청이었다. 실제 2026 시즌 구조를 더 충실히 반영하자는 좋은 제안이었지만, 대회를 7개로 늘리고 전부 우승해야 하면 난이도가 비현실적으로 높아진다는 문제가 있었다.

그대로 반영하는 대신, 기본 모드는 그대로 두고 '하드 모드'로 분리했다. 기존 유저의 경험은 해치지 않으면서, 하드코어 유저에게는 실제 시즌 구조(LCK Cup·First Stand·MSI·EWC·Worlds 등 7스테이지)를 제공하는 선택이었다.

유저 요구를 그대로 따르기보다, 제품 전체 관점에서 판단해 더 나은 형태로 풀어낸 경험이었다. 피드백을 받고 → 원인을 분석하고 → 빠르게 반영하는 사이클을 짧게 가져가려 했다.


6. 삽질과 배움

빠르게 만든 만큼 예상 못 한 문제도 많았다.

OVR 평가 체계의 재정의

선수에게 매기는 OVR을 처음부터 다시 설계했다. 단순히 유명세로 매기면 설득력이 없기 때문에, 대회 MVP·올프로 선정·인게임 스탯·대회 성적을 종합해야 했다. 실제로는 KDA·골드 점유율·CS·챔피언 딜량 같은 인게임 지표를 가중 합산하고, 대회 성적과 수상 이력을 반영하는 방식으로 수치화했다.

가장 어려운 점은 주관성이었다. 유저마다 특정 선수에 대한 기억과 평가가 다르기 때문에, OVR에 대한 피드백이 가장 많이 들어왔다. "이 선수가 왜 이 점수냐"는 논쟁은 어느 정도 불가피하다고 보고, 평가 근거(리그 성적, 인게임 지표)를 일관되게 적용하는 데 집중했다.

난이도 밸런싱 — 감이 아닌 측정으로

난이도 조정은 감에 의존하지 않고, 시뮬레이션을 다수 반복 실행해 결과 분포를 측정하고 목표 분포에 맞게 파라미터를 조정하는 방식으로 접근했다. 시뮬레이션 로직의 Elo 스케일 값과 상대 팀의 OVR을 조정하면서, 각 설정마다 10,000회씩 시뮬레이션을 돌려 등급별 도달 확률을 측정했다. "OVR 90 팀이 최고 등급에 도달할 확률"처럼 목표 수치를 정해두고, 그에 맞을 때까지 파라미터를 반복 조정했다.

흥미로웠던 건 직관과 반대되는 결과였다. 우리 팀이 상대보다 강한 구도에서는 변동성(Elo 스케일)을 낮추면 오히려 강팀이 쉽게 이겨 난이도가 내려갔다. "더 어렵게"를 위해 변동성을 키워야 하는, 처음 예상과 반대 방향의 조정이었다. 감으로 건드렸으면 틀렸을 부분을, 측정으로 잡아낸 사례였다.


7. 회고

잘한 것

  • 제약(3일·비용 0·1인)을 먼저 정의하고, 거기서 역산해 기술을 선택했다. "DB를 안 쓴다"는 결정이 전체 구조를 단순하게 만들었다.
  • 완벽보다 빠른 런칭 후 피드백 반영을 택했다. 일찍 내보낸 덕에 실제 유저 반응으로 개선할 수 있었다.

기술 부채와 다음 스텝

  • 3일 런칭을 우선하면서 런칭 전 QA를 최소화했다. 빠른 시장 검증이라는 이점은 얻었지만, 그 대가로 일부 버그를 유저 피드백으로 처음 발견했다. 빠른 실행과 검증 부채를 맞바꾼 셈이다. 다음 프로젝트에서는 핵심 플레이 루프만이라도 런칭 전에 직접 검증하는 절차를 두어, 속도와 안정성의 균형을 맞추려고 한다.

  • 비타민형 서비스의 한계(약한 리텐션)는 구조적이다. 다음에 유사한 걸 만든다면, 재방문을 유도하는 장치를 처음부터 설계에 넣을 것이다.

  • 가장 뼈아픈 건 유통 경로였다. 이 프로젝트의 핵심 가설 자체가 '공유 바이럴로 유입을 만든다'였는데, 정작 그 유입 채널(커뮤니티별 홍보 규정·게시 조건)을 배포 전에 검증하지 않고 작업이 끝난 뒤에야 탐색했다. 유입을 가설로 세운 순간, 채널 예열(GTM)까지가 배포 스코프였던 셈이다. 이후 프로젝트부터는 '유통 경로 확보'를 기획 단계 체크리스트에 못 박았다.

배운 것

  • "저장할 게 없으면 DB도 필요 없다" — 요구사항에서 역산하면 흔히 당연하게 여기는 컴포넌트도 덜어낼 수 있다.
  • 지표는 절댓값보다 서비스 성격과 함께 봐야 의미가 있다.
  • 유저가 "무엇을 요청하는가"만큼, "실제로 어떻게 행동하는가"를 보는 게 중요하다. 워터마크도, 하드 모드도 거기서 나왔다.

0개의 댓글