Привет, Хабр! Меня зовут Константин Шкурко, я ведущий разработчик мобильных приложений в РСХБ. Сегодня хочу рассказать историю о том, как всем известные обстоятельства заставили нас в сжатые сроки искать альтернат­ивные пути доставки нашего инвестиционного приложения «Свои инвестиции» пользователям iOS - и как это изменило наш технологический стек.

Когда Apple заблокировала российские банковские приложения в App Store, перед нами встала непростая задача. У нас были десятки тысяч активных пользователей на iOS, которые пользовались брокерским приложением для управления своими инвестициями. Торговля акциями, облигациями, аналитика портфеля, выставление заявок — всё это внезапно стало недоступно для значительной части клиентской базы.

Время было критическим фактором. Люди не могли управлять своими активами, а финансовые рынки не ждут. Нужно было действовать быстро и при этом выбрать решение, которое будет не временной заглушкой, а полноценной платформой для развития.

Альтернативы были?

Первым делом мы проанализировали все возможные варианты. Каждый имел свои ограничения.

Технически можно было бы использовать TestFlight, постоянно обновляя тестовые сборки. Или же Apple Enterprise Developer Program формально позволяет распространять приложения внутри организации без App Store. Но для всех этих манипуляций нужен аккаунт разработчика Apple, и это создавало трудности, так как компания Apple заблокировала аккаунт банка. Поэтому эти варианты отмели сразу.

Progressive Web App оказался единственной технологией, которая позволяла:

  • Обойти блокировку в App Store легальным способом,

  • Установить приложение на домашний экран через Safari,

  • Работать в полноэкранном режиме, как и нативное приложение,

  • Обновляться автоматически без участия пользователя,

  • Не требовать сложных манипуляций при установке.

Да, у PWA на iOS были свои ограничения, о которых я рассказывал в статье Почему PWA на iOS всё ещё не торт. Но это был единственный вариант, который давал нам долгосрочную перспективу развития продукта.

Выбор фреймворка: почему React, а не Vue, Angular или Svelte

С форматом определились — PWA. Теперь нужно было выбрать технологический стек. Наша команда до этого занималась нативной iOS-разработкой на Swift, поэтому переквалификация на веб-технологии была серьёзным вызовом. Выбор фреймворка должен был учитывать не только технические возможности, но и скорость вхождения команды, доступность специалистов на рынке и долгосрочную поддержку.

Angular: мощно, но избыточно

Angular мы отмели довольно быстро. Да, это мощный фреймворк с полным набором инструментов из коробки, сильной типизацией через TypeScript и чёткой архитектурой. Но для нашей ситуации он оказался слишком тяжеловесным. Крутая кривая обучения, большой размер бандла, избыточность концепций для команды, которая только переходит с нативной разработки - всё это делало Angular неоптимальным выбором. Нам нужно было что-то более гибкое и с меньшим порогом входа.

Vue.js: просто, но есть вопросы к масштабируемости

Vue привлекал своей простотой и понятностью. Низкий порог входа, хорошая документация, интуитивная структура компонентов — казалось бы, идеальный вариант для команды, которая переучивается. Но мы видели риски в экосистеме. На тот момент Vue 3 только набирал обороты, многие библиотеки ещё не обновились, а опыта миграции с версии на версию у комьюнити было меньше. Главное же - мы смотрели на рынок специалистов. React-разработчиков было значительно больше, что критично для масштабирования команды.

Svelte: инновационно, но рискованно

Svelte нас заинтересовал своим принципиально иным подходом — компиляция вместо runtime, меньший размер бандла, отсутствие виртуального DOM. Производительность впечатляла. Но экосистема была слишком молодой. Меньше готовых решений, меньше проверенных библиотек, меньше специалистов на рынке. Для банковского приложения, где критична стабильность и надёжность, это был неприемлемый риск. Svelte — отличный выбор для экспериментов или стартапов, но не для финансового продукта с десятками тысяч пользователей.

React: баланс возможностей и стабильности

React победил по совокупности факторов:

  • Зрелая экосистема. Огромное количество готовых библиотек, проверенных временем решений, стабильная обратная совместимость. Практически любую задачу можно было решить, используя существующие инструменты.

  • Большое комьюнити. На любой вопрос можно было найти ответ на Stack Overflow или в GitHub issues. Для команды, которая переучивается, это был критичный фактор - мы не могли позволить себе застревать на проблемах по несколько дней.

  • Доступность специалистов. React — самый популярный фреймворк, что означало возможность быстро расширить команду при необходимости или получить помощь от коллег из других подразделений банка.

  • Гибкая архитектура. В отличие от Angular React не навязывает жёсткую структуру проекта. Это давало нам свободу адаптировать архитектуру под наши задачи, используя опыт нативной разработки.

  • Производительность. Виртуальный DOM, оптимизированный рендеринг, возможность использовать мемоизацию и другие техники оптимизации позволяли создавать быстрые интерфейсы даже для сложных экранов с большим количеством данных.

Навигация в React: выбор между React Router и альтернативами

С основным фреймворком определились, но оставался вопрос навигации. В нативном iOS-приложении мы использовали паттерн MVP + Router, где роутинг был чётко выделенной частью архитектуры. Хотелось сохранить похожую концепцию.

На момент выбора основными вариантами были:

  • Reach Router — элегантное решение с фокусом на доступность, но уже тогда шла речь о его слиянии с React Router v6. Выбирать библиотеку, которая в перспективе будет упразднена, не имело смысла.

  • React Navigation — мощная библиотека, изначально созданная для React Native. Она предлагала привычные для мобильной разработки концепции стеков, табов и drawer-навигации. Для нас, как для «бывших» iOS-разработчиков, это было знакомо и понятно. Однако использование React Navigation в веб-приложении создавало дополнительный слой абстракции над нативным браузерным роутингом, что могло привести к проблемам с history API и SEO.

  • React Router — де-факто стандарт для веб-приложений на React. Зрелая библиотека с огромным комьюнити, полной интеграцией с браузерным API, поддержкой всех современных паттернов роутинга.

На тот момент мы были командой iOS-разработчиков, которая в сжатые сроки переучивалась на веб-технологии. опыта в React у нас практически не было. Мы изучали документацию, читали статьи, смотрели на best practices в комьюнити. React Router был везде — в туториалах, в примерах проектов, в рекомендациях. Это сыграло решающую роль.

Кроме того, React Router предлагал:

  • Декларативный подход к описанию роутов,

  • Встроенную поддержку вложенных маршрутов,

  • Лаконичный API для работы с навигацией,

  • Отличную документацию с множеством примеров.

Мы внедрили React Router, и он справился со своей задачей. Но если бы сейчас, с нашим текущим уровнем экспертизы в веб-разработке мы делали выбор заново, то более внимательно посмотрели бы в сторону решений с более нативной поддержкой deep linking.

Уроки навигации: deep linking в PWA

Одной из ключевых функций мобильного приложения является поддержка диплинков - возможность открывать конкретные экраны приложения по ссылкам из SMS, email, push-уведомлений или внешних источников. В нативном iOS это работало через Universal Links и URL schemes.

С React Router мы столкнулись с некоторыми особенностями при реализации диплинков в PWA:

  • Обработка внешних ссылок. Когда PWA установлено на домашний экран, iOS обрабатывает ссылки специфическим образом. Нужно было настроить файл apple-app-site-association и правильно конфигурировать manifest.json, чтобы ссылки открывались именно в PWA, а не в Safari.

  • State management при переходах по диплинкам. React Router отлично работает для внутренней навигации, но при переходе по внешней ссылке приложение фактически перезапускается. Нам пришлось реализовать механизм восстановления контекста - проверять параметры URL при инициализации и подготавливать нужное состояние приложения.

  • Навигационный стек. В нативных приложениях есть чёткое понятие стека экранов с возможностью вернуться назад. React Router использует браузерную историю, что при диплинках иногда приводило к неожиданному поведению кнопки «Назад». Мы написали обёртку над историей навигации, которая отслеживала способ открытия экрана и корректировала поведение back-кнопки.

Сейчас эти проблемы решены, и диплинки работают стабильно. Но если бы мы начинали сейчас, возможно, обратили бы внимание на более современные решения вроде TanStack Router, который изначально спроектирован с учётом типобезопасности роутинга и более гибкой работы с параметрами URL. Впрочем, миграция с одного роутера на другой в живом проекте — это всегда риск и затраты, поэтому мы продолжаем развивать текущее решение.

UI-библиотека: опыт с Material-UI

Для ускорения разработки мы выбрали Material-UI (MUI) как основную UI-библиотеку. На старте это казалось правильным решением: готовые компоненты, консистентный дизайн, хорошая документация, поддержка темизации.

Почему начали с MUI

  • Скорость разработки. Готовые кнопки, инпуты, модальные окна, таблицы — всё это позволяло быстро собирать экраны, не тратя время на создание базовых компонентов с нуля.

  • Адаптивность из коробки. MUI компоненты хорошо работали на разных размерах экранов, что критично для PWA, которое должно корректно отображаться и на iPhone SE, и на iPad.

  • Accessibility. Компоненты MUI разработаны с учётом требований доступности, что важно для банковского приложения.

Почему постепенно отказываемся

С развитием продукта мы столкнулись с ограничениями MUI:

  • Кастомизация — это боль. Наш дизайн постепенно отходил от Material Design, появлялись специфические требования к компонентам. Переопределение стилей MUI часто превращалось в борьбу со специфичностью CSS-селекторов и внутренней структурой компонентов. Иногда проще было написать компонент с нуля, чем заставить MUI-компонент выглядеть так, как нужно дизайнерам.

  • Размер бандла. MUI — довольно тяжёлая библиотека. Даже с tree-shaking финальный размер приложения получался больше, чем хотелось бы. Для PWA каждый килобайт критичен, особенно при первой загрузке по мобильному интернету.

  • Ограниченность кастомных компонентов. Финансовое приложение требует специфических UI-элементов: графики котировок, таблицы ордеров, сложные формы для выставления заявок. MUI не покрывал эти потребности, и мы всё равно писали свои компоненты.

Сейчас мы идём по пути создания собственной дизайн-системы на базе более низкоуровневых инструментов. Это даёт полный контроль над внешним видом и поведением компонентов, позволяет оптимизировать производительность под наши задачи и создавать действительно уникальный пользовательский опыт.

Переосмысление архитектуры: от Swift MVP к React

Наше нативное приложение было построено на архитектурном паттерне MVP (Model-View-Presenter) с отдельным слоем роутинга. Это проверенная временем структура, которая хорошо работала для iOS-приложения средней сложности.

Что мы перенесли из нативного приложения

  • Сетевой слой. Полностью переписали на JavaScript, но сохранили логику работы с API. Все эндпоинты, форматы запросов и ответов, механизмы авторизации - всё это мигрировало практически один-в-один. Это сильно упростило интеграцию и тестирование, так как бэкенд-команда уже знала, как работает клиент.

  • Обработка ошибок. Финансовое приложение должно корректно обрабатывать множество видов ошибок: проблемы сети, таймауты, ошибки бизнес-логики, проблемы авторизации. Мы создали единую систему обработки ошибок, которая классифицирует их по типам и показывает пользователю понятные сообщения. Логика классификации была портирована из Swift-приложения.

  • Кэширование и оффлайн-режим. Используя Service Workers, мы реализовали стратегии кэширования данных, похожие на те, что были в нативном приложении. Пользователь может видеть последние данные о портфеле даже без интернета.

Как адаптировали архитектуру под React

React предлагает совершенно другую парадигму по сравнению с императивным UIKit в Swift. Вместо презентеров и явного управления UI мы использовали:

  • Компонентный подход. Каждый экран — это композиция переиспользуемых компонентов. Это ближе к SwiftUI, чем к классическому UIKit, и потребовало переосмысления структуры приложения.

  • Hooks для управления состоянием. useState, useEffect, useContext заменили презентеры. Логика обработки данных теперь живёт внутри кастомных хуков, что делает её переиспользуемой между компонентами.

  • Контекст и Redux для глобального состояния. Данные пользователя, настройки приложения, состояние авторизации - всё это хранится в глобальном state management. Мы использовали комбинацию Context API для простых случаев и Redux Toolkit для более сложной логики с асинхронными экшенами.

  • Разделение бизнес-логики и UI. Несмотря на отличия от MVP, мы сохранили принцип отделения бизнес-логики от представления. Кастомные хуки содержат логику работы с данными, а компоненты отвечают только за отображение.

Ограничения PWA на iOS: с чем пришлось бороться

PWA на iOS - это компромисс. Apple исторически не спешила с поддержкой прогрессивных веб-приложений, и даже сейчас существуют ограничения, которые влияют на пользовательский опыт.

Хранилище данных: непредсказуемая очистка

Одно из самых болезненных ограничений — Safari может удалить все данные PWA, если приложение не запускалось несколько недель. Это касается IndexedDB, localStorage, кэша Service Worker - всего.

Представьте: пользователь не заходил в приложение месяц, открывает его — а там пустой экран, нужно заново авторизовываться, все настройки сброшены. Для брокерского приложения это неприемлемо.

Наше решение: мы реализовали агрессивное кэширование критичных данных на сервере, привязанное к device ID. При запуске приложения проверяем, есть ли локальные данные. Если нет - подгружаем настройки и последнее состояние с бэкенда. Это не решает проблему полностью, но делает её менее болезненной.

Ограничения Service Worker: неполная поддержка

Service Workers в iOS Safari работают, но с оговорками. Например, долгое время не поддерживались background sync и периодическая синхронизация. Это означало, что обновление данных возможно только когда приложение открыто.

Что сделали: реализовали умное обновление данных при открытии приложения. Вместо background sync мы обновляем все критичные данные сразу при запуске PWA в фоне, а затем показываем уже актуальную информацию. Плюс используем WebSocket для real-time обновлений котировок, когда приложение активно.

Биометрия: Web Authentication API

Safari поддерживает Web Authentication API, но с нюансами. Face ID и Touch ID доступны, но интеграция требует правильной настройки сервера и сертификатов. Плюс UX отличается от нативного - появляются дополнительные промпты от браузера.

Как реализовали: По сути пока еще никак. Пользователи используют классический PIN-код. Просто руки еще не дошли. Но в ближайшее время фича в стэке задач. Приоритеты все же определяет бизнес.

Размер и вес: оптимизация до последнего байта

В отличие от нативного приложения, где ресурсы упакованы в IPA и грузятся один раз при установке, PWA загружает ресурсы по сети при каждом обновлении. Это накладывает жёсткие требования на размер бандла.

Code splitting. Разбили приложение на чанки по роутам. Главный экран грузится быстро, а экраны настроек, отчётов, аналитики подгружаются по требованию.

Ленивая загрузка изображений. Все иконки, логотипы компаний, графики грузятся лениво с использованием Intersection Observer. Плюс кэшироание не дает ломаться интерфейсу при загрузке новых изображений.

Результат: первая загрузка приложения занимает меньше 5 МБ, повторные визиты - практически мгновенные благодаря Service Worker кэшу.

Ориентация экрана: блокировка landscape

Для некоторых экранов критично, чтобы они открывались только в портретной ориентации - например, формы авторизации, ввод PIN-кода. В нативном iOS это контролируется через настройки контроллера.

В PWA нет прямого способа заблокировать поворот экрана для отдельных маршрутов. Screen Orientation API поддерживается, но с ограничениями.

Наше решение: мы реализовали собственную систему блокировки ориентации. Отслеживаем события orientationchange и resize, для определённых путей показываем экран-заглушку с просьбой вернуть телефон в вертикальное положение. Это не блокирует физический поворот, но делает приложение неиспользуемым в ландшафтной ориентации, где это критично. За такие пляски с бубном спасибо корпорации Apple. Политика Apple все больше движется в сторону обязанности разработчиков проектировать интерфейсы для всех типов ориентаций.

Push-уведомления: долгожданная поддержка

До iOS 16.4 push-уведомления в PWA вообще не работали. Это было серьёзным ударом по функциональности - пользователи не получали уведомления о выполнении заявок, изменении цен на отслеживаемые активы, важных новостях рынка.

Что делали до iOS 16.4: использовали email и SMS для критичных уведомлений. Это дорого и не так оперативно, но альтернатив не было.

Позже Apple наконец добавила поддержку Web Push API. Мы быстро интегрировали push-уведомления через Service Worker, настроили VAPID ключи, реализовали подписку на уведомления.

Но и здесь не обошлось без нюансов. Чтобы PWA могло получать пуши, оно должно быть добавлено на домашний экран. Просто открытый сайт в Safari пуши не получает. Плюс процесс запроса разрешений отличается от нативных приложений — требуется явное действие пользователя, нельзя показать промпт сразу при запуске.

Как решили: сделали ненавязчивый онбординг при первом запуске, где объясняем преимущества установки PWA на домашний экран, включая возможность получать уведомления. Запрос на разрешение пушей показываем после того, как пользователь выполнил несколько действий в приложении и уже видит его ценность.

Результаты: как PWA спасло ситуацию

Несмотря на все технические сложности, PWA оправдало себя как бизнес-решение.

Возврат пользователей

Удовлетворённость пользователей, конечно, пострадала. PWA — это не полноценная замена нативного приложения, написанного на Swift. Есть ограничения, есть компромиссы, есть места, где UX не такой плавный. Но грамотное принятие решений продуктовыми менеджерами и руководством минимизировало эти нюансы. Результат — практически все пользователи вернулись и продолжают активно использовать приложение.

Кроссплатформенность как бонус

Неожиданным преимуществом стало то, что PWA работает не только на iPhone, но и на iPad, и даже на десктопах. Некоторые пользователи предпочитают торговать через веб-версию на компьютерах, что раньше не было возможным, у нас было только мобильное приложение. В ближайшей перспективе планируется порадовать пользователей и такой возможностью.

Скорость доставки фич

Ещё один плюс — скорость доставки новых функций. В нативном приложении каждое обновление проходило через App Store Review, что занимало от нескольких дней до недели. С PWA мы можем деплоить обновления несколько раз в день. Критичные багфиксы доходят до пользователей в течение нескольких минут, а не дней.

Одна команда, разные платформы

Важно отметить, что разработчики Android находятся в более выгодном положении. У них есть альтернативные сторы — RuStore, NashStore, возможность установки APK напрямую. Больше каналов распространения, меньше ограничений платформы.

Но мы - одна команда. Разработчики iOS и Android постоянно обмениваются опытом, помогают друг другу находить решения технических проблем, синхронизируют функциональность. Когда у нас возникает сложная задача — например, реализация сложного графика котировок или оптимизация производительности таблиц с большим количеством данных — мы садимся вместе и ищем best practices, которые можно применить на обеих платформах.

Эта кооперация особенно важна, потому что мы стремимся к паритету функциональности. Пользователь iPhone и пользователь Android должны получать одинаковый набор возможностей, одинаковый UX. PWA для iOS активно развивается, появляется много новых фич, и мы идём в ногу с функциональностью приложения на Android.

Что дальше: развитие PWA

PWA — это не временная заглушка, а полноценная платформа, которую мы продолжаем развивать.

Оптимизация производительности

Постоянно работаем над скоростью работы приложения. Профилируем рендеринг компонентов, оптимизируем тяжёлые вычисления, используем Web Workers для выноса обработки данных из основного потока. Цель — чтобы PWA работало так же быстро, как нативное приложение, или хотя бы максимально близко к этому.

Новые возможности платформы

Apple постепенно расширяет возможности PWA. Мы следим за обновлениями iOS и Safari, тестируем новые API, внедряем их, как только они становятся доступны. Например, недавно появилась лучшая поддержка Web Share API — теперь пользователи могут делиться аналитикой своего портфеля с друзьями нативным для iOS способом.

Собственная дизайн-система

Уходим от MUI к собственным компонентам, оптимизированным под наши задачи. Это даёт полный контроль над внешним видом, производительностью и поведением интерфейса. Плюс позволяет создать уникальный визуальный стиль, который будет отличать наше приложение от конкурентов.

Улучшение онбординга

Работаем над тем, чтобы процесс установки PWA и первое использование были максимально понятными. Пользователи не привыкли к концепции «добавить на домашний экран», это нужно объяснять. Создаём интерактивные туториалы, пошаговые инструкции, видео-гайды.

Выводы: когда ограничения становятся возможностями

Блокировка в App Store казалась катастрофой. Мы потеряли канал распространения приложения для десятков тысяч пользователей. Но эта ситуация заставила нас искать альтернативные решения, осваивать новые технологии, выходить из зоны комфорта.

Переход с нативной iOS-разработки на React и PWA был непростым. Новый стек, новая парадигма, новые ограничения. Но мы справились:

  • Выбрали технологии, которые сочетают зрелость экосистемы, доступность специалистов и перспективы развития.

  • Адаптировали архитектуру, сохранив лучшее из нативного приложения и приняв best practices веб-разработки.

  • Решили технические проблемы PWA на iOS, находя обходные пути для ограничений платформы.

  • Вернули пользователей, предоставив им работающее решение в кратчайшие сроки.

Сегодня наше PWA для приложения Россельхозбанка "Свои инвестиции" — это полноценный продукт, который продолжает развиваться.  Если вы оказались в похожей ситуации или просто рассматриваете PWA как альтернативу нативным приложениям, не бойтесь. Да, будут сложности. Да, придётся искать нестандартные решения. Но это реально и работает.

Буду рад ответить на вопросы в комментариях и обсудить ваш опыт разработки PWA!

Комментарии (5)


  1. DevilDimon
    02.06.2026 19:08

    Но для всех этих манипуляций нужен аккаунт разработчика Apple, и это создавало трудности, так как компания Apple заблокировала аккаунт банка. Поэтому эти варианты отмели сразу. 

    Другие компании (в том числе банки) почему-то не отмели, и уже по шестому приложению релизят. Но ладно...


    1. SHK83 Автор
      02.06.2026 19:08

      Так и наш банк тоже выпускает натив и уже не шестой, а счет уже на десятки пошел. Ну вот пример свежий https://habr.com/ru/companies/rshb/articles/1039012/

      Я вот и сам не рад, что приложение в которое я получаю зп окирпичивают :) Поэтому пользуюсь уже года 4 «устаревшим» приложением одного однобуквенного банка. Ну если вам норм десятками ставить новые приложения от банка, то не все этому рады, а PWA позволяет не бесить пользователей бесконечными нативными приложениями. Ну такова уж реальность сейчас в стране. Я уж лучше PWA попользую или вэб версию. У каждого свой путь.


      1. DevilDimon
        02.06.2026 19:08

        1. Не надо получать зп на окирпиченное приложение, если не хочется. Понятно, что если в этом же банке и работать, то не получится, но никто не заставляет.

        2. Десятками не надо ничего ставить. Красный и желтый банк до сих пор работают у меня без единого обновления, те самые древние версии удаленные из стора. Пережили смену телефонов, симок, стран, номеров, почт, работ, карт и прочего. Принудительные выключения версий приложений - исключительно решение, а не техническая необходимость.

        3. У каждого свой путь, но вы выбрали PWA, а остальные выбирают натив, понял.


  1. savostin
    02.06.2026 19:08

    Так а что используете вместо MUI? Или прям все с нуля пишете?


  1. Diamon33
    02.06.2026 19:08

    как всем известные обстоятельства

    Это какие? Я просто не в курсе.

     а PWA позволяет не бесить пользователей бесконечными нативными приложениями. Ну такова уж реальность сейчас в стране.

    так не бесите