pile·
프론트엔드·토스 SLASH토스 SLASH·

토스는 어떻게 광고 속에 게임을 넣었을까

토스가 광고 시스템을 자체 구축하면서 서드파티 SDK 대신 국제 광고 표준 MRAID를 직접 구현해 플레이어블 광고(앱 설치 전 게임 체험)를 지원한 과정이다. mraid.js 브릿지, 5중 렌더링 완료 신호, 보안 중심 설계, 앱 심사 없는 즉시 배포를 동시에 달성했다.

핵심 포인트
  • 플레이어블 광고 미지원이 게임 광고주 예산 배정을 막는 직접 원인이었다.
  • MRAID 표준의 '실제 호출되는 API만 구현' 원칙으로 복잡도를 최소화했다.
  • 5중 신호(폰트·이미지·페인트·DOM 안정성·프레임 합성)로 실제 렌더링 완료를 판정해 빈 화면 오판을 방지했다.
  • Object.freeze()와 핸들러 격리로 외부 소재가 SDK를 오염시키지 못하도록 보안 설계했다.
  • 서버가 URL을 제공하는 구조로 앱 스토어 심사 없이 즉시 배포 가능.
상세 정리
  • 문제 진단: 게임 광고주는 성과 기반으로 매체를 선택하는데, 플레이어블 광고 소재 형식을 수용하지 못하면 예산 배정이 이루어지지 않는 구조였다.
  • 자체 구현 선택: 토스는 광고 서버를 처음부터 직접 운영했기 때문에 외부 SDK 의존 없이 전체 파이프라인을 통제하는 방향을 선택했다.
  • MRAID 표준 채택: 게임 소재 제작사가 단일 API로 여러 매체에서 동작하도록 업계가 표준화한 MRAID 인터페이스를 채택했다.
  • 선택적 구현 원칙: 전체 MRAID 스펙이 아니라 실제 광고 소재가 호출하는 API만 골라 구현해 초기 개발 범위를 제한했다.
  • mraid.js 브릿지: 웹뷰 내 HTML5 게임 소재와 네이티브 앱 사이의 통신 채널로, 네이티브 기능(클릭 이벤트, 상태 변경)을 JS 이벤트로 노출한다.
  • 5중 렌더링 완료 신호: 빈 화면을 렌더링 완료로 오판하지 않기 위해 폰트 로드·이미지 디코드·페인트·DOM 안정성·프레임 합성 다섯 단계를 모두 통과해야 완료로 판정한다.
  • 보안 설계: Object.freeze()로 SDK 내부를 불변 처리하고, 각 이벤트 핸들러를 격리해 소재의 예외가 SDK 전체로 전파되지 않도록 방어했다.
  • 엔드카드 관리: 게임 종료 후 앱 설치 권유 화면을 MRAID 표준과 별도 레이어로 관리해 전환 추적을 분리했다.
  • 배포 전략: 서버가 mraid.js URL을 제공하는 구조라 앱 스토어 심사 없이 즉시 배포 가능하고, 불변 파일 원칙과 감사 로그로 버전을 관리한다.
  • mraid.open() 단일 경로: 모든 클릭을 mraid.open()으로만 집계해 광고 클릭 측정 일관성을 보장한다.
왜 읽나광고 SDK를 자체 구현해야 하는 프론트엔드 엔지니어, 또는 MRAID·플레이어블 광고 표준을 실제 코드로 옮기는 방법을 찾는 개발자에게 전체 설계 결정 과정이 담겨 있다.
토스 SLASH
토스 SLASH 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

    여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

    요약 이어보기
    #react#typescript#webview+2
  2. 프론트엔드·토스 SLASH토스 SLASH·

    모노리포 희망편, 절망의 리포가 희망의 리포로 부활하기까지 걸린 1년

    토스 프론트엔드 팀이 100명 이상의 개발자가 사용하는 모노리포에서 겪은 의존성 버전 불일치, 개발 환경 편차, 느린 설치 속도 문제를 pnpm Catalog 도입으로 해결한 1년간의 여정을 다룬다. 카탈로그로 라이브러리 버전을 중앙화한 결과 의존성 설치 시간을 52% 단축하고 .pnp.cjs 파일 크기를 84% 줄이는 데 성공했다.

    요약 이어보기
    #react#pnpm#monorepo+1