Тикетинг-платформа E7 вирішила веб-чекаут. Але у мобільного вебу є стеля. Pinch-to-zoom на SVG-карті залу в мобільному браузері — терпимо, не добре. А після купівлі квитка потік email → PDF → скріншот → показати на вході — це біль, що накопичується від заходу до заходу.
E7 знадобився нативний застосунок. Я зібрав його за 10 тижнів на React Native та Expo.
Чому натив, а не PWA
Це було реальне рішення, не дефолт. Повний фреймворк порівняння нативу з PWA — в окремому пості — ось як фактори зіграли для E7:
Жести карти місць вбили шлях PWA. Вибір місць вимагає pinch-to-zoom, подвійний тап для центрування та інерційного панорамування — все на 60fps для SVG із 3,000 вузлами. Mobile Safari та Chrome обробляють це терпимо для фото, але не для інтерактивних SVG, де кожен вузол — це тап-ціль. Ми прототипували в браузері — 24fps на Pixel 4a. Натив із Reanimated 3 — 60fps на тому ж пристрої.
Офлайн-зберігання квитків вимагає реальних API пристрою. PWA може використовувати Cache API, але не може надійно зберігати зашифровані дані квитків між перезапусками на iOS Safari. React Native з AsyncStorage + SecureStore дає зашифроване офлайн-зберігання, яке зберігається.
Пуш-сповіщення для нагадувань про заходи. Технічно можливо з PWA, але підключення ~15% (користувачі не дозволяють). Нативний пуш через Expo Notifications — 72% підписки. Для івент-застосунку нагадування — друга за використанням функція після квитків.
Обмеження
10 тижнів. Веб-платформа вже працювала; застосунок мав вийти до наступного концертного сезону.
Одна кодова база, дві платформи. Бюджету на окремі iOS та Android команди немає. React Native + Expo — єдиний шлях, що дає обидві платформи з однієї кодової бази у задані терміни.
Спільний бекенд. Застосунок звертається до того ж API, що й веб-платформа. Без окремого бекенду, без дублювання даних, без проблем синхронізації. Одна Prisma-схема, одна база Postgres, один акаунт Stripe.
Що відвантажили
Вибір місць — 60fps на бюджетному телефоні
Це інженерний центр проєкту. SVG на 3,000 місць, який можна зумити, панорамувати та тапати на нативній швидкості.
Архітектура:
-
Reanimated 3 для жестів. Pinch-to-zoom та пан працюють на нативному UI-потоці, не на JS-потоці. Це означає, що обробка жестів не блокує рендеринг React. На Pixel 4a різниця — 24fps (жести на JS-потоці) vs 60fps (нативний потік).
-
Віртуалізований рендеринг місць. Тільки місця, видимі в поточній області перегляду, рендеряться як інтерактивні
Pressable. Місця поза межами — один статичний<Path>. При панорамуванні місця входять і виходять з інтерактивної зони. Це тримає кількість вузлів нижче 200 незалежно від розміру залу. -
Оптимістичне блокування місць. При тапі на місце UI показує його як «ваше» миттєво (оптимістичне оновлення), поки запит блокування летить у фоні. Якщо блокування не вдалося (хтось інший встиг першим) — місце повертається в доступні з хаптик-вібрацією. Сприймана затримка: нуль. Реальний раунд-тріп: ~150ms.
-
Кольорове кодування станів. Доступні — білі. Ваші — зелені. Заблоковані іншими — сірі. Продані — темні. Легенда не потрібна — стани очевидні за кольором.
Пост про офлайн-архітектуру занурюється глибше в протокол синхронізації. Коротка версія: ми зібрали 4-станову чергу (pending → syncing → confirmed → failed), що обробляє обриви мережі посеред чекауту без втрати вибору користувача.
QR-гаманець
Після купівлі квитки з'являються у вбудованому гаманці. Кожен квиток показує:
- Назву заходу, дату, площадку
- Інформацію про місце/секцію
- QR-код, що оновлюється кожні 30 секунд (для запобігання шарингу скріншотів)
- Маршрут до площадки (діплінк у Карти)
Гаманець працює повністю офлайн. Дані квитка + ключі для підпису QR зашифровані та зберігаються локально при купівлі. Навіть якщо користувач у підземному клубі без сигналу — він може показати квиток, і сканер персоналу його верифікує.
Ротація QR — пізнє додавання. Первісний QR був статичним — та сама картинка щоразу. Через два тижні після запуску ми побачили скріншоти QR-кодів у соцмережах. Додавання ротації на основі часу (QR кодує ID квитка + поточне 30-секундне вікно, підписане HMAC) вбило шаринг скріншотів без впливу на легітимних користувачів.
Пуш-сповіщення
Три типи сповіщень, налаштованих за даними залученості:
- Нагадування про захід: за 24 години. 68% переходів.
- Відкриття входу: коли починається чекін на площадці. Відправляється тільки власникам квитків.
- Нові заходи: щотижневий дайджест для підписаних. 22% відкриттів — пристойно для неспамового застосунку.
Expo Notifications обробляє кросплатформну доставку. Firebase Cloud Messaging на Android, APNs на iOS, один API для обох. Налаштування сповіщень за категоріями — можна вимкнути нові заходи, не втрачаючи нагадування.
Діплінки
Лист підтвердження купівлі включає діплінк: e7shop://ticket/abc123. Якщо застосунок встановлений — відкривається прямо на квитку. Якщо ні — фолбек на веб-сторінку квитка з банером встановлення.
Одна ця фіча збільшила встановлення на 40% у перший місяць. Люди купують у вебі → отримують лист → тапають посилання → встановлюють застосунок → бачать свій квиток. Безтертьовий онбординг.
Цифри
Шість місяців продакшн-даних:
| Метрика | Значення |
|---|---|
| Всього встановлень | 8,200+ |
| Рейтинг у сторі | 4.8★ (iOS) / 4.7★ (Android) |
| Безаварійних сесій | 99.7% |
| Завершення чекауту (мобілка) | 82% (vs 78% веб) |
| Середній час вибору місць | 12s |
| Офлайн-квитків обслужено | ~1,400 |
| Підписка на пуш | 72% |
82% завершень чекауту в застосунку vs 78% у мобільному вебі — скромно, але розрив у задоволеності ширший, ніж цифри. Користувачі застосунку оцінюють вибір місць на 4.9/5 у пост-покупкових опитуваннях; користувачі мобільного вебу — на 3.4/5. Досвід фундаментально інший, навіть якщо дельта конверсії мала.
1,400 обслужених офлайн-квитків — це 1,400 людей, які застрягли б на вході з «немає сигналу», якби ми зібрали PWA. На підземній площадці в Братиславі це приблизно половина аудиторії.
Таймлайн рев'ю в App Store
Реальність для всіх, хто планує мобільний запуск:
| Етап | День |
|---|---|
| Перша збірка в TestFlight | Тиждень 6 |
| Внутрішнє тестування завершено | Тиждень 8 |
| Відправка в App Store | Тиждень 9, понеділок |
| Рев'ю Apple — перша відмова | Тиждень 9, четвер |
| Виправлення: додано кнопку «Restore Purchases» | Тиждень 9, п'ятниця |
| Повторна відправка | Тиждень 9, п'ятниця |
| Схвалено | Тиждень 10, вівторок |
| Відправка в Google Play | Тиждень 9, понеділок |
| Схвалено | Тиждень 9, середа |
Apple відхилив нас за відсутність кнопки «Restore Purchases», хоча ми не продаємо покупки в застосунку — квитки купуються через Stripe у вебі, а застосунок тільки показує їх. Рев'юер Apple інтерпретував показ квитків як потік покупки. Ми додали no-op кнопку «Відновити покупки», яка показує «Усі квитки синхронізовані з вашого акаунту», і пройшли при повторній відправці.
Урок: Закладайте повний тиждень на рев'ю App Store, навіть для v1. Google Play схвалив за 2 дні. Apple — 8 днів з урахуванням циклу відмови.
Що здивувало
Обробка кнопки «Назад» на Android зламала вибір місць. Дефолтна поведінка React Navigation закривала весь екран при натисканні апаратної кнопки «Назад» під час вибору місць. Користувачі очікували «зняти останнє місце», а не «піти зі сторінки». Кастомна логіка BackHandler виправила, але ми зловили це тільки тому, що бета-тестер повідомив про втрату вибору.
Витрата батареї від оновлення QR. 30-секундна ротація QR спочатку тримала екран активним і перемальовувала компонент по таймеру. На деяких Android-пристроях це з'їдало 8% батареї на годину на екрані квитка. Переключились на оновлення через requestAnimationFrame, що працюють тільки коли екран квитка у фокусі — витрата впала до менш ніж 1%.
Користувачі не читали онбординг. Ми зібрали 3-екранний онбординг із поясненням функцій. Аналітика показала, що 78% користувачів свайпали всі три екрани менш ніж за 2 секунди — вони не читали. Замінили на контекстні тултіпи, що з'являються при першому використанні кожної функції. Взаємодія з довідковим контентом зросла з 4% до 31%.
Що б зробив інакше
Підключив би Expo Updates з першого дня. Ми використовували EAS Build для бінарних релізів, але не увімкнули OTA-оновлення до 4-го тижня після запуску. Це означає, що два критичних фікси вимагали повної повторної відправки в App Store (3-денний цикл кожен). Expo Updates запушив би ці фікси за хвилини.
Використав би Expo Router замість React Navigation. На момент розробки Expo Router був пре-v1, і ми обрали стабільний варіант. Зараз він зрілий, файл-базований і автоматично обробляє діплінки. Заощадив би ~2 дні ручного налаштування навігації та конфігурації діплінків.
A/B-тестував би початковий зум карти місць. Ми за замовчуванням показуємо весь зал. Більшість користувачів одразу зумять у вподобану секцію. Старт із зумом у найпопулярнішу секцію (за історичними даними продажів) заощадив би жест і, ймовірно, покращив би час вибору.
Бізнес-результат
У E7 тепер є нативний застосунок, який встановили 8,200+ людей, із рейтингом 4.8★ та 72% підпискою на пуш-сповіщення. Це прямий маркетинговий канал до аудиторії — без рекламних витрат, без реселера, без посередника.
Завершення чекауту на мобілці вище в застосунку, ніж у вебі. Офлайн-квитки працюють на підземних площадках. Ротація QR вбила шаринг скріншотів. А потік діплінків з email у застосунок стимулює встановлення без жодного попапу «скачайте наш застосунок».
Застосунок коштував E7 приблизно 30% від того, що нативне агентство процитувало за iOS-only. Вони отримали обидві платформи, спільний бекенд і команду з однієї людини, що відвантажила за 10 тижнів.
Якщо ви збираєте мобільний продукт і сумніваєтесь, чи дасть React Native нативне відчуття продуктивності — дасть, для правильних юзкейсів. Вибір місць, карти, UI з важкими жестами — все реально на 60fps із правильною архітектурою. Давайте обговоримо ваш випадок.