Релізи в store займають багато часу. Apple review може тривати від кількох годин до кількох днів, а відхилений build обнуляє відлік заново. Google review в середньому швидший, але не гарантований — policy flag все одно може затримати реліз на тиждень. Це нічого не змінює в тому, що продуктовим командам потрібно постійно виправляти краші та доставляти невеликі покращення. Expo дає нам дисциплінований спосіб робити це — не перетворюючи кожен hotfix на екстрений native build і не обходячи тихцем store policy так, щоб додаток пізніше опинився під підозрою.
Найбільше болю створюють не самі повільні review, а команди, у яких немає плану на те, що відбувається між review — тоді кожен баг стає пожежею: хтось збирає build локально, TestFlight distribution плутають з production, і ніхто напевно не знає, який саме JS bundle зараз працює на телефоні конкретного користувача. Структурований Expo pipeline існує саме для того, щоб прибрати цю невизначеність, а не лише щоб пришвидшити релізи.
Розділяйте build profiles з самого початку
У EAS ми визначаємо профілі development, preview і production з окремими bundle ID або application ID, де це потрібно. Звучить як дрібна деталь налаштування, але саме це рішення запобігає майже всім іншим проблемам релізів далі по ланцюжку.
Failure mode, якого ми уникаємо: розробник вручну підміняє значення в .env перед build, забуває підмінити назад — і preview build випускається з production API keys, або, що гірше, production build звертається до staging backend, і ніхто цього не помічає, доки не почнуть надходити тікети в support. Env-файли в EAS прив'язані до профілю, а не копіюються вручну, тож те, який binary ви зібрали, детерміноване профілем, а не тим, у якому стані випадково опинилась локальна машина конкретної людини.
Це також важливо для чистоти analytics і crash reporting. Якщо preview і production builds ділять один project ID, ваші dashboards змішують шум внутрішнього тестування з реальними даними користувачів, і кожна метрика — crash-free sessions, конверсія воронки, error rate — стає трохи неточною так, що це важко помітити, доки хтось не запитає, чому цифри не сходяться з очікуваннями. Окремі профілі з окремими endpoints тримають ці дані чистими з першого дня, і це важливіше, ніж здається, щойно команда починає приймати рішення ship/no-ship на основі dashboard.
Розділення bundle або application ID додатково дозволяє встановлювати development, preview і production build поруч на одному тестовому пристрої. Це маленька зручність з непропорційно великим ефектом на швидкість QA — тестувальникам більше не потрібен окремий пристрій під кожне середовище, і регресії ловляться раніше, бо перевірка «а чи є цей баг також у production» займає секунди замість повного циклу видалення й перевстановлення.
OTA для JavaScript, store — для native
Expo Updates добре підходить для UI-логіки, змін текстів і виправлень багів, які не потребують нових native modules. Це помітно вужча категорія, ніж «будь-яка зміна в JS», і помилка з визначенням цієї межі в будь-який бік створює проблеми.
Якщо доставити over-the-air щось, що насправді потребувало native rebuild — новий запис permission, оновлення native dependency, зміну полів app.json, які впливають на скомпільований binary — OTA update або тихо не застосується, або, що гірше, застосується частково і залишить додаток у неузгодженому стані, який важко відтворити й важко пояснити в bug report. З іншого боку, ставлення до кожної зміни як до такої, що вимагає store review, зводить нанівець весь сенс використання Expo Updates і повертає команду туди ж — «ми можемо релізити лише раз на місяць» — навіть для змін, яким review взагалі не був потрібен.
Рішення тут — не хитра перевірка в runtime, а задокументована межа, узгоджена на етапі планування, а не о 23:00 перед демо. Для кожного проєкту ми фіксуємо, які категорії змін підходять для OTA (тексти, дрібні правки layout, бізнес-логіка, виправлення багів на існуючих екранах), а що завжди вимагає повного binary release (нові native modules, зміни permissions, оновлення SDK, будь-що, що торкається native-полів app.json). Цей список переглядається щоразу, коли оновлюється Expo SDK, бо межа зсувається в міру того, як дедалі більше можливостей стає керованими на рівні JS.
Варто також чітко пояснювати стейкхолдерам, що OTA — це не спосіб обійти review для змін, які мають через нього проходити. Політики App Store і Play Store обмежують те, що можуть змінювати OTA-оновлення, саме для того, щоб додаток не перетворювався на щось суттєво інше вже після затвердження. Дотримання цієї межі — не просто питання формальної відповідності: додаток, позначений за порушення policy, може отримати delisting або наслідки на рівні всього акаунту, що набагато руйнівніше за будь-яке повільне review.
Видимість крашів до того, як скаржаться користувачі
Кожен release profile вказує на один crash reporting проєкт, з тегами середовища і, що критично, з конкретним ID OTA update або native build number, які зараз активні. Звучить очевидно, але саме цей крок команди пропускають найчастіше — і саме він перетворює п'ятихвилинний фікс на розслідування на кілька днів.
Без тегування на рівні update сплеск крашів з'являється в dashboard без жодного способу зрозуміти, чи це від build, випущеного минулого тижня, чи від OTA-патчу, доставленого годину тому. Комусь доводиться вручну реконструювати таймлайн, звіряючи логи деплоїв з часовими мітками крашів, зазвичай під тиском, бо користувачі вже скаржаться. З тегуванням, щойно з'являється сплеск, ви точно знаєте, який update його спричинив, а отже можете зробити rollback або опублікувати виправний OTA channel за хвилини, а не гадати, яка з останніх трьох змін винна.
Друга половина цього — можливість rollback. OTA channel, який вміє тільки рухатися вперед, — лише половина запобіжника. Ми вважаємо вимогою, а не приємним бонусом, можливість «відкотити саме цей update, не торкаючись нічого іншого, що вийшло відтоді», бо цінність швидких OTA-релізів зникає, якщо єдиний спосіб скасувати невдалий update — випустити ще один фікс наперед і сподіватися, що він правильний.
Store submission як чекліст
Навіть коли OTA бере на себе більшість ітерацій, великі релізи все одно проходять через store, і цьому шляху краще бути нудним, ніж вигадливим. Перед кожним submission ми проходимо короткий чекліст: чи справді permissions збігаються з тим, що додаток запитує в runtime, чи актуальні privacy manifests (це стало значно частішою причиною відхилення, оскільки обидві платформи посилили вимоги щодо розкриття збору даних), чи відображають screenshots поточний UI, а не версію три релізи тому, і чи є план rollback, якщо новий binary поводитиметься некоректно вже після затвердження.
Кожен пункт окремо не складний. Проблеми виникають, коли їх пропускають під тиском часу — випускаючи build за ніч до дедлайну без повторної перевірки, чи новий SDK тихо не додав новий запит permission, або переюзаючи старі screenshots, бо оновлювати їх здається формальністю. Команди store review якраз і фіксують такі невідповідності, а відхилення за два дні до дати запуску коштує набагато дорожче за ті двадцять хвилин, які займає чекліст.
Типові помилки в існуючих pipeline
Кілька патернів повторюються, коли ми беремо у роботу React Native або Expo проєкт, який не будували з цією дисципліною із самого початку:
- Немає перевірки на паритет середовищ. Команда припускає, що preview і production налаштовані однаково, і дізнається протилежне лише тоді, коли баг, характерний виключно для production, ніде більше не відтворюється.
- OTA використовується як обхідний шлях під час затримок review — доставляючи зміни, які мали бути native build, і створюючи саме той policy-ризик, від якого мають захищати правила store.
- Crash reporting без тегування релізів, через що робота над інцидентом починається з «а з якої це взагалі версії», а не з «ось фікс».
- Знання про submission тримається в голові однієї людини. Акаунти в store, сертифікати підпису й provisioning profiles живуть у голові чи на машині однієї людини, і реліз повністю зупиняється, коли її немає на місці.
Жодна з цих проблем не є екзотичною. Це передбачуваний результат ставлення до release-інфраструктури як до чогось, що налаштовують один раз і забувають, замість того, щоб доглядати за нею так само уважно, як за кодом застосунку, який вона доставляє.
Якщо ваша mobile-команда застрягла між «ми можемо релізити лише раз на місяць» і «ми hotfix'имо з ноутбуків», розрив між цими двома крайнощами — не проблема інструментів, яку вирішує вибір кращого софту, а прогалина в процесі. Build- і update-тулінг Expo просто робить правильне впровадження цього процесу простим. Структурований pipeline з чітким розділенням профілів, задокументованою межею OTA, реальною видимістю крашів і нудним чеклістом submission — зазвичай найшвидший шлях від будь-якої з цих крайнощів до спокійного щотижневого release cadence.
