Команда JavaScript for Devs подготовила перевод кейса Shopify о миграции их крупнейших приложений на новую архитектуру React Native. Результат впечатляет: еженедельные релизы не остановились, стабильность сохранилась, а производительность выросла.
Введение
Мы успешно перевели два наших крупнейших приложения — Shopify Mobile и Shopify Point of Sale (POS) — на новую архитектуру React Native, при этом сохранив еженедельный график релизов и обеспечив работу миллионов продавцов. Миграция затронула сложный код с сотнями экранов и нативных модулей, большим количеством кастомных компонентов и глубокой интеграцией с собственными библиотеками, такими как FlashList.
Ключевые результаты:
Сохранили темп разработки на протяжении всей миграции
Не допустили остановки разработки новых возможностей
Выявили и решили типовые проблемы миграции на большом масштабе
В этой статье мы делимся нашим подходом к миграции, ключевыми решениями и уроками, которые мы извлекли, чтобы другие команды могли воспользоваться нашим опытом. Мы расскажем о практических стратегиях, которые помогли нам успешно перевести мобильное приложение Shopify на новую архитектуру, сохранив скорость разработки и стабильность приложения.
Стратегия миграции: движемся, не сбавляя ход
Основные принципы
Наша стратегия миграции строилась на трёх ключевых принципах:
Минимизировать изменения кода на первом этапе, рефакторить позже: вносить только минимально необходимые изменения, чтобы включить новую архитектуру. Оптимизация и рефакторинг могут быть выполнены после. Было критически важно мигрировать как можно быстрее, чтобы прекратить добавление нового кода, который ломал бы сборку на новой архитектуре.
Сохранять совместимость двух архитектур в процессе разработки: поддерживать и старую, и новую архитектуру на протяжении всей миграции, чтобы можно было постоянно тестировать и избегать регрессий. Удалять поддержку старой архитектуры уже после релиза.
Сохранить или улучшить показатели производительности и стабильности: убедиться, что новая архитектура соответствует или превосходит старую по производительности и стабильности — особенно по метрикам TTI (время до интерактивности) и по количеству сессий без сбоев — перед выпуском в продакшен.
Сохранение темпа разработки
В Shopify мы выпускаем обновления приложений каждую неделю, поэтому непрерывная разработка новых функций была критически важна во время миграции. Для крупных приложений остановка разработки — большой риск: технические сложности могут задержать выпуск важных функций или исправлений багов.
Наш подход позволил сбалансировать прогресс миграции с потребностями продавцов:
-
Тестирование двух архитектур: с помощью TopHat мы генерировали сборки для обеих архитектур для каждого PR. Это позволило легко тестировать изменения без локальной пересборки и предотвращало попадание в main ветку изменений, ломавших новую архитектуру.
Условная функциональность: для сторонних зависимостей, которые не поддерживали обе архитектуры в одной версии, мы использовали feature flags, чтобы условно отключать функциональность в режиме разработки на новой архитектуре.
Совет для авторов библиотек: выпускайте версии, поддерживающие обе архитектуры. Управлять условными версиями непросто — двойная поддержка значительно облегчает миграцию. Именно поэтому мы поддерживали обе архитектуры в альфа‑версиях FlashList v2.
Технические детали
Процесс миграции
Стратегия работы с нативными модулями: во время миграции мы не переводили модули на TurboModules. Мы изменяли только те модули, которые не работали на новой архитектуре (в основном те, что взаимодействовали с UIManager). С помощью feature flags мы форкали реализацию, обеспечивая совместимость обеих архитектур. Хотя TurboModules — это шаг вперёд, они пока не обязательны, и при наличии 40+ модулей в Shopify App мы решили вернуться к этому вопросу уже после миграции. Это позволит нам определить, какие модули действительно нужны и как улучшить их API, используя возможности TurboModules.
Управление зависимостями: мы обновили несовместимые зависимости до версий с поддержкой двух архитектур. Также пропатчили или удалили неподдерживаемые библиотеки. Это стало отличной возможностью сократить количество зависимостей.
Обновление до последней версии React Native: новая архитектура активно развивается, выходят регулярные исправления багов и улучшения производительности. Мы ставили приоритет на обновление до самой свежей версии React Native, проверяли её стабильность, а уже потом занимались оптимизацией кода приложения. Такой подход позволил получать улучшения из апстрима и не тратить время на исправление проблем, которые уже могли быть решены. Большинство багов, с которыми мы сталкивались, устранялись обновлением версии. Поэтому мы рекомендуем обновляться до максимально свежей версии перед началом миграции.
Изменения в коде приложения: мы внесли минимальные изменения, чтобы код функций работал на обеих архитектурах. Также добавили предупреждения об устаревании во временные ветки кода, чтобы после миграции их легко удалить.
Типовые проблемы миграции
Миграция потребовала адаптации к синхронной модели рендеринга новой архитектуры и обновлённым интерфейсам нативных модулей, что повлияло на работу существующих компонентов и интеграций.
Вот наиболее частые проблемы, с которыми мы столкнулись, и способы их решения:
Пакетная обработка состояния ломает компоненты
Новая архитектура объединяет обновления состояния в пакеты вместо их поштучной обработки. Некоторые компоненты, которые раньше работали, полагались на промежуточные состояния, которые больше не существуют из‑за пакетирования, и из‑за этого переставали работать.
Решение: рефакторить компоненты так, чтобы они не зависели от конкретного времени обновления состояния. Такой технический долг неизбежно накапливается в больших кодовых базах, а миграция даёт отличную возможность его устранить.
Пугающий белый экран
На нашем опыте белый экран почти всегда означал, что какой‑то TurboModule работает неправильно — использует хаки или старые API, специфичные для прежней архитектуры. Чаще всего это происходило с модулями, которые напрямую работали с UI. В результате JS‑приложение переставало рендериться, что сильно усложняло отладку.
Решение: проверить модули, которые взаимодействуют с UIManager. В качестве стратегии отладки можно поочерёдно комментировать компоненты и провайдеры в корневом компоненте App, пока не удастся изолировать проблемный модуль. Особенно полезной оказалась дискуссия по миграции UIManager.
Изменения в работе с Shadow Tree
Манипуляции с React Native‑вью из нативных UIManager могут вызывать серьёзные UI‑проблемы, например, нерабочие жесты касания из‑за рассинхронизации между представлением и тем, что React Native считает текущей иерархией. Мы столкнулись с этим в Mobile Bridge с компонентом TransportableView.
В нашем случае мы с помощью нативных модулей подменяли WebView, объявленные в React Native‑коде, как на iOS, так и на Android. В лучшем случае это приводило к тому, что WebView не загружались, в худшем — к падениям приложения. Мы решили проблему, полностью удалив WebView из React Native и управляя их жизненным циклом целиком на нативной стороне. Это был самый простой подход для нас, так как мы хотели сохранить совместимость обеих архитектур и переиспользовать максимум существующей реализации.
Решение: переносить гибридные компоненты в чистый React Native, полностью переводить их на натив или переписывать с использованием собственных shadow-нод.
Побочные эффекты View Flattening
View flattening удаляет из финального layout компоненты, которые признаны ненужными. Однако мы обнаружили случаи, когда компоненты с ref
удалялись, из-за чего ref
всегда был null
. Это вызывало проблемы, когда другие компоненты пытались использовать этот ref, особенно при работе с API ActionSheetIOS.
View flattening также ломал наш end-to-end тестовый набор: Appium переставал находить некоторые вью. Чтобы решить эту проблему, мы создали Babel-плагин, который автоматически добавляет collapsable={false}
для компонентов с явным testID
.
Решение: добавить
collapsable={false}
для вью сref
, чтобы гарантировать, что они не будут оптимизированы. Заметьте, что на Android view flattening существовал и раньше, так что такие баги чаще проявляются на iOS.
Устаревшие нативные модули в главном потоке
Мы столкнулись с проблемами “App Hang” на iOS во время запуска приложения, когда устаревшие нативные модули загружались в главном потоке параллельно с анимациями. Это вызывало дедлок и приводило к падению. Проблему было сложно воспроизвести в dev-режиме, зачастую приходилось закрывать и запускать приложение снова в течение нескольких минут. Однако в продакшене мы видели множество падений.
Решение: если у вас ещё есть устаревшие нативные модули, установите
requiresMainQueueSetup
вfalse
, если это не строго необходимо. В большинстве случаев — не необходимо.
Проблемы с производительностью анимаций
Приложение Shopify активно использует Reanimated для анимации навигации. Мы столкнулись с серьёзным падением частоты кадров на обеих платформах — проблемой, которая проявляется только при нашем масштабе и сложности анимаций.
Эти трудности показали реальную цену раннего внедрения технологии в крупном проекте. Тем не менее, реакция Software Mansion (поддерживающих Reanimated) и Meta была впечатляющей. Обе команды плотно сотрудничали с нами, чтобы выявить и устранить проблемы производительности, которые, скорее всего, остались бы незамеченными в меньших или менее сложных приложениях.
Software Mansion предоставила нам ранний доступ к патчам, устранившим большинство проблем с производительностью. Сейчас мы совместно с Meta и Software Mansion работаем на�� тем, чтобы интегрировать эти исправления в Reanimated и сам React Native, чтобы всё сообщество смогло воспользоваться результатами наших масштабных тестов. Улучшения производительности и исправления ошибок, полученные благодаря этому сотрудничеству, принесут пользу всем командам, которые используют Reanimated в новой архитектуре.
Рекомендация: для большинства приложений мы по-прежнему рекомендуем использовать Reanimated. Проблемы с производительностью в основном заметны только при сложных анимациях в крупных проектах, и в зависимости от их применения они могут почти не влиять на пользовательский опыт.
Подход к деплою
Поскольку деплой нельзя было скрыть за feature flag, мы выбрали осторожную стратегию постепенного деплоя, чтобы контролировать стабильность и производительность.
Важно было учесть различия в управлении раскаткой между App Store и Play Store.
Google Play Store позволяет гибко управлять деплоем: задавать точные проценты пользователей и в любой момент полностью останавливать новые установки. App Store, напротив, использует заранее заданный график постепенного деплоя. Даже если его приостановить, пользователи всё равно могут вручную обновиться до новой версии. Дополнительно стоит учитывать, что процесс одобрения новых сборок в App Store может занимать до 24 часов, что делает хотфиксы более медленными и рискованными.
По этой причине мы подготовили расписание, начиная с Android и постепенно увеличивая процент пользователей. iOS мы начали на день позже — чтобы убедиться в стабильности релиза.
График релиза:
День 1 — Android 8%, iOS 0%: цель — собрать первые сигналы с Android, так как там можно полностью остановить деплой в любой момент.
День 2 — Android 30%, iOS 1%: значительное увеличение процента на Android, при этом iOS остаётся на низком уровне, чтобы оставить время на реакцию.
День 3 — обе платформы 100%: к этому моменту мы уверены, что критических проблем нет. Поскольку в iOS нельзя увеличить процент до конкретного значения, мы решили полностью открыть релиз на двух платформах, чтобы собрать данные в масштабах всего трафика.
План реагирования на инциденты:
Наша целевая стабильность (сессий без сбоев) для приложения Shopify — 99,95%. Для этого релиза мы установили три уровня реакции в зависимости от показателей стабильности:
Стабильность выше 99,80%: исправление в следующем еженедельном релизе
Стабильность между 99,00% и 99,80% или критический сценарий с известным фиксoм: приостановка релиза и хотфикс
Стабильность ниже 99,00% или критический сценарий без быстрого решения: откат
Откат рассматривался как крайняя мера, так как он серьёзно нарушал бы наш график миграции. Нам были нужны данные, которые можно получить только при масштабном релизе. Откат был не только операционно затратным, но и откатывал нас далеко назад в прогрессе миграции. Поэтому, если была возможность рано приостановить деплой и устранить проблемы, мы всегда выбирали этот вариант.
Что прошло хорошо
Миграция в целом прошла успешно и почти не повлияла на наш процесс разработки. Мы сохранили еженедельный ритм релизов на протяжении всей миграции, продолжая поставлять новые возможности продавцам без перебоев.
Хотя основной целью был сам переход, мы сразу получили несколько ощутимых преимуществ только от миграции на Fabric:
Время запуска приложения улучшилось примерно на 10% на Android и на 3% на iOS.
Мы смогли упростить процесс рендеринга экранов, используя «measure before paint», что дало более плавную и быструю загрузку экранов. Подробнее об этом можно почитать в нашем блоге о FlashList v2.
Новое пакетное обновление состояния сократило количество лишних перерисовок, из-за чего переключение вкладок стало заметно быстрее.
В целом, во время миграции наша команда значительно повысила экспертизу в новой архитектуре. То, что поначалу было неизведанной территорией, стало областью глубокого понимания и заложило хорошую основу для будущих обновлений и оптимизаций React Native.
Широкое сообщество React Native тоже получило пользу от наших наработок. Мы полностью переписали FlashList, предоставив практические примеры кода, показывающие, как оптимизировать рендеринг в новой архитектуре. Мы также тесно сотрудничали с Meta и Software Mansion, чтобы находить и исправлять баги и проблемы с производительностью, которые теперь устраняются в апстриме.
Мы надеемся, что эта статья поможет и другим командам пройти через схожие вызовы и принесёт пользу, выходящую за рамки нашего продукта.
Что пошло не так
Несмотря на то что мы тщательно измеряли производительность и проводили оптимизации до деплоя, некоторые проблемы стали очевидны только в продакшене. Даже при всех наших усилиях по внутреннему тестированию реальное влияние оказалось значительнее, чем ожидалось.
Снижение производительности: некоторые экраны потребовали дополнительной оптимизации уже после релиза, так как изменения в рендеринговом пайплайне сломали ряд предположений, на которых изначально строились эти компоненты. На некоторых сложных экранах мы наблюдали рост времени загрузки до 20%. В этом нельзя винить саму новую архитектуру — она лишь выявила слабые места в дизайне компонентов, которые раньше были незаметны.
Проблемы со стабильностью: стабильность сессий (доля сессий без сбоев) сначала снизилась ниже нашей цели в 99,95%, но восстановилась после пары недель исправлений багов. Редкие падения мы всё ещё наблюдаем в глубине кода RN или сторонних библиотек, но они не критичны. Мы продолжаем работать с Meta и мейнтейнерами библиотек, чтобы найти решения.
Рост ANR: количество ошибок “Application Not Responding” выросло на обеих платформах — частично из-за наших собственных патчей Reanimated/React Native, которые исправляли другие проблемы с производительностью. Также появились некоторые дедлоки, вызванные инициализацией нативных модулей в главном потоке. В старой архитектуре это не приводило к проблемам, но на новой в редких случаях вызывало краши.
Учитывая, что это было фундаментальное изменение в нашем самом важном фреймворке, всё могло быть гораздо хуже. Мы остаёмся оптимистичными насчёт будущего React Native и верим, что новая архитектура даёт более надёжную основу для решения проблем с производительностью.
Рекомендации для других команд
Миграция на новую архитектуру React Native — сложная задача, но она вполне выполнима при правильном планировании.
Эти рекомендации основаны на нашем опыте миграции крупного продакшен-приложения, которое обслуживает миллионы пользователей. В зависимости от масштаба и сложности вашего проекта подходы могут отличаться, но этот список даёт прочную основу для планирования миграции.
Проведите аудит зависимостей заранее — выявите проблемы совместимости до начала миграции.
Обновитесь до последней версии React Native перед любыми изменениями в коде — так вы не будете тратить время на баги, уже исправленные в апстриме. Выпустите обновление до начала миграции, чтобы уменьшить зону риска.
Запускайте процесс как можно раньше — включите Fabric в режиме разработки, чтобы быстро получить сигналы о проблемных зонах в приложении. Сохраняйте совместимость со старой архитектурой.
Используйте ресурсы сообщества — многие из проблем, с которыми вы столкнётесь, уже обсуждались в ишью на GitHub или других площадках сообщества. Ищите существующие обсуждения перед тем, как разрабатывать собственные решения.
Минимизируйте изменения на первом этапе — приоритет на исправление багов и точечную оптимизацию перед релизом.
Стратегически подходите к нативным модулям — мигрируйте сначала только те, которые дают очевидную выгоду.
Планируйте временное падение стабильности и производительности — используйте поэтапную раскатку и возможности постепенного релиза в сторах для снижения рисков.
Старайтесь исправлять проблемы “вперёд” — избегайте откатов, чтобы не терять темп.
Русскоязычное JavaScript сообщество

Друзья! Эту статью перевела команда «JavaScript for Devs» — сообщества, где мы делимся практическими кейсами, инструментами для разработчиков и свежими новостями из мира Frontend. Подписывайтесь, чтобы быть в курсе и ничего не упустить!
Что дальше
С новой архитектурой в основе наш фокус смещается от совместимости к оптимизации. Теперь мы можем использовать возможности, которые раньше были невозможны, решая текущие проблемы с производительностью и создавая ещё более качественный пользовательский опыт.
Наши следующие приоритеты:
Стратегическая миграция на TurboModules: конвертация наиболее часто вызываемых модулей (настройки пользователя, feature flags) и критических для производительности участков, чтобы убрать затраты на сериализацию.
Переход на синхронный layout: создание компонентов, использующих синхронный layout, чтобы устранить визуальные скачки и улучшить производительность на сложных экранах.
Оптимизация производительности: использование ленивой загрузки TurboModules и оптимизированного рендеринга для улучшения метрик TTI и ускорения запуска приложения.
И, наконец, мы хотим поблагодарить Meta и Software Mansion за партнёрство и поддержку на каждом этапе пути. Также благодарим сообщество React Native — эта работа была бы невозможна без опыта, которым вы делились в обсуждениях на GitHub, блогах и open source-проектах.
Новая архитектура — это будущее React Native. Да, миграция требует серьёзных усилий, но долгосрочные выгоды того стоят. Мы с энтузиазмом смотрим вперёд: синхронный layout избавляет от дерганий интерфейса, а TurboModules обеспечивают молниеносное взаимодействие с нативом — всё это позволяет нам и дальше создавать удобный и плавный опыт, которого ждут наши продавцы. Будущее React Native выглядит светлым, и Shopify глубоко заинтересована в его успехе.