# Nerdy Production > Компания по разработке ПО: мобильные приложения на Flutter, веб-приложения и AI-сервисы. Базируется в Европе, работает с клиентами по всему миру. ## Админ-панели и дашборды https://ru.nerdy.pro/services/web-development/admin-dashboards Разработка админ-панелей — это инженерия интерфейсов, на которых бизнес работает, а не тех, которыми он себя рекламирует: дашборд, где операционная команда следит за цифрами, панель настроек, где менеджер меняет поведение продукта, бэк-офис, где поддержка находит аккаунт и чинит его. У этих инструментов нет ни лендинга, ни карточки в сторе. И они же определяют, сколько людей нужно, чтобы компания работала. Мы строим их на Vue и Nuxt с TypeScript насквозь, на том же стеке, что и наша публичная [веб-разработка](https://ru.nerdy.pro/services/web-development), но под другую цель. Админ-инструмент измеряется в сэкономленных минутах операторов и предотвращённых ошибках, а не в показателях отказов, и это меняет то, ради чего ведётся инженерия. #### Какие продукты мы делаем - **Операционные дашборды** — живые представления данных, по которым команда действует: заказы, сессии, выплаты, алерты — с фильтрами и детализацией, которые превращают стену строк в решение - **Панели конфигурации** — интерфейсы, где изменение настройки меняет продукт: правила ценообразования, фиче-флаги, параметры кампаний, которые бэкенд читает на лету - **Бэк-офисы мобильных продуктов** — веб-сторона приложения, как админка за [white-label парком](https://ru.nerdy.pro/services/whitelabel-app-development), который мы ведём; клиентская веб-часть — отдельная страница, [парные веб-приложения](https://ru.nerdy.pro/services/web-development/companion-web-app) - **Инструменты поддержки и работы с аккаунтами** — найти пользователя, увидеть то, что видел он, исправить то, что пошло не так, — с записью каждого действия в журнал - **Кабинеты мерчантов и партнёров** — обращённый наружу родственник админ-панели с ограниченными правами, где партнёр управляет своей частью вашей платформы ### Кто будет делать вашу админ-панель Наше самое ясное доказательство — бэк-офис, управляющий настоящим парком приложений. #### Админка, которая запускает брендированные приложения Для нашей [платформы кэшбэка и лояльности](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform) запуск нового white-label бренда раньше был инженерной повинностью: конфигурация, идентификаторы бандлов, ресурсы для подписи, метаданные сторов, новый таргет в CI. Мы свели всё это в одно приложение на Nuxt поверх PHP-сервисов и PostgreSQL. Менеджер заполняет форму (название бренда, тема, ресурсы, функции), а система генерирует конфигурационный бандл, регистрирует приложение в CI/CD конвейере и выпускает подписанные сборки для iOS и Android. Запуск бренда превратился из многодневной инженерной задачи в форму самообслуживания, которой управляют люди, не пишущие код. Это и есть стандарт, по которому мы оцениваем админ-инструменты: панель управляет машинерией, а не описывает её. #### Построено теми, кто строит и бэкенд Админ-панель хороша ровно настолько, насколько хорош её API, и мы редко делаем одно без другого. Тот же проект, что дал описанную выше админку, дал и PHP-сервисы за ней; фронтенд [Formtastic](https://ru.nerdy.pro/portfolio/formtastic) на Nuxt работает с бэкендом на Django и Go, который мы развивали вместе с ним. Когда обе стороны в руках одной команды, «фронтенду нужен эндпоинт под это» — вопрос одного утра, а не межкомандных переговоров, и правила валидации в панели совпадают с тем, что бэкенд действительно проверяет. ### Чего на самом деле требуют админ-инструменты Пять задач возникают в каждом бэк-офисе. #### Авторизация, совпадающая с оргструктурой Экран логина — это не контроль доступа. Настоящему админ-инструменту нужны роли (кто смотрит, кто редактирует, кто утверждает), проверяемые на сервере, а UI лишь отражает то, что API и так откажется выполнять. Ролевой контроль доступа мы закладываем сначала в слой API, чтобы вручную составленный запрос не мог сделать того, чего не позволила бы скрытая кнопка. #### Формы, которые управляют реальной машинерией Когда отправка формы генерирует конфигурацию, двигает деньги или запускает сборочный конвейер, форма наследует инженерные стандарты машинерии за ней: валидацию, совпадающую с серверной, идемпотентную отправку, чтобы двойной клик не мог запустить два бренда, и ясные, конкретные состояния ошибок. Счастливый путь — самая простая часть. #### Таблицы, которые переживают реальные данные Каждая админ-панель прекрасно выглядит на демо с двенадцатью строками и умирает на пятидесяти тысячах. Мы делаем пагинацию на сервере, уводим фильтрацию и поиск в базу данных, где есть индексы, и держим сортировку, фильтры и экспорт отзывчивыми на тех объёмах, до которых ваши операции действительно дорастут. #### Журнал изменений с первого дня Кто изменил ставку выплат, когда и с какого значения на какое? В админ-инструменте это функция, а не форензика. Действия логируются с автором, временем и значениями «до» и «после», потому что бэк-офис, где изменения анонимны, превращается в место, где менять что-либо не позволено никому. #### Скучно — намеренно Внутренним инструментам не нужно SEO и чаще всего не нужен серверный рендеринг: обычное single-page приложение на Vue за логином — часто правильное решение, и мы так и скажем, вместо того чтобы выставлять счёт за фреймворк, который вам не нужен. Там, где Nuxt отрабатывает своё — когда его серверные роуты заменяют отдельный бэкенд или публичная и внутренняя части живут в одной кодовой базе, — мы его используем, как сделали в админке парка. Этот выбор — инженерное решение, и в предложении он проговаривается явно. ### Как мы работаем **Проект с фиксированным объёмом.** Мы ведём инструмент от картирования процессов до деплоя, включая негламурные части вроде миграции данных из таблиц, которые он заменяет. **Staff augmentation.** Наши инженеры входят в вашу команду и строят бэк-офис рядом с вашим продуктом — см. [расширение команды](https://ru.nerdy.pro/services/team-augmentation). В обоих случаях смету под задачу мы присылаем в течение двух рабочих дней после того, как разберёмся в требованиях. ### Сколько стоит админ-панель Админ-панель — это веб-приложение с авторизацией, и оценивается она по тем же опубликованным тарифам, что и на странице [веб-разработки](https://ru.nerdy.pro/services/web-development): компактный бэк-офис с аутентификацией, ролевым доступом и основными таблицами и формами попадает в тариф веб-приложения, 1 350 000–3 600 000 ₽, а мультитенантный портал или панель, вплетённая в большую платформу, оценивается как работа уровня SaaS — от 4 500 000 ₽. Это те же ключи, из которых рисуется таблица цен пилларной страницы, поэтому разойтись они не могут. Что двигает цифру внутри диапазона: сколько ролей и процессов покрывает инструмент, какая часть бэкенда уже существует и панель лишь читает ваши системы — или управляет ими. ### Частые вопросы Что чаще всего спрашивают команды, заказывающие админ-панели и внутренние дашборды. #### Чем админ-панель отличается от дашборда? Дашборд показывает: живые метрики, таблицы, детализацию, на которых строится решение. Админ-панель действует: её формы и элементы управления меняют то, что делает продукт, — конфигурацию, контент, цены, учётные записи. Большинство настоящих бэк-офисов — и то и другое, и инженерная разница важна: трудные задачи дашборда — запросы и объёмы данных, трудные задачи админ-панели — авторизация, валидация и гарантия, что отправленная форма не может навредить. Мы строим их как один инструмент, применяя обе дисциплины. #### Сколько стоит разработка админ-панели? Компактный бэк-офис с аутентификацией, ролевым контролем доступа и основными таблицами и формами поверх существующего API обычно стоит от 1 200 000 до 3 200 000 рублей и занимает от одного до трёх месяцев: тот же опубликованный тариф веб-приложения, что и на нашей странице веб-разработки. Мультитенантный партнёрский портал или панель, которая оркеструет реальную машинерию, оценивается как работа уровня SaaS — свыше 4 000 000. Стоимость двигает в первую очередь количество отдельных ролей и процессов, а не число экранов. #### Вы делаете и бэкенд или только интерфейс? И то и другое, и обычно вместе. Админ-панель хороша ровно настолько, насколько хорош её API, поэтому проект, как правило, охватывает эндпоинты, слой авторизации и интерфейс как одну работу. Для white-label платформы, которую мы ведём, одна и та же команда построила админку на Nuxt и PHP-сервисы за ней, и именно поэтому отправка формы там может безопасно генерировать конфигурацию и запускать подписанные мобильные сборки. Если API у вас уже есть, мы строим поверх него и честно говорим, где его нужно менять. #### Vue или Nuxt для внутреннего инструмента? Для инструмента, целиком живущего за логином, честный ответ — часто обычное single-page приложение на Vue: индексировать нечего, поэтому серверный рендеринг добавляет движущиеся части, не добавляя ценности. Nuxt отрабатывает своё, когда его серверные роуты могут заменить отдельный бэкенд на небольшом продукте или когда внутренняя и публичная части делят одну кодовую базу. Мы работаем с обоими и проговариваем выбор в предложении явно, а не берём более тяжёлый инструмент по умолчанию. #### Смогут ли нетехнические сотрудники управлять инструментами, которые вы строите? Это проектная цель, по которой мы оцениваем собственную работу. Админка, которую мы построили для white-label платформы приложений, позволяет менеджерам без навыков программирования запускать полностью брендированное, готовое к публикации мобильное приложение, заполнив форму: система генерирует конфигурацию, регистрирует таргет в CI-конвейере и выпускает подписанные сборки. Мера качества бэк-офиса — убирает ли он инженера из цепочки, и каждый сценарий мы проектируем по этому стандарту. #### Как вы обеспечиваете безопасность админ-панели? Авторизация проверяется на сервере, а не просто прячется в интерфейсе; сессии — в httpOnly, secure, SameSite cookie; ролевой контроль доступа — на уровне API; и журнал изменений, фиксирующий, кто, что и когда поменял. Админ-инструменты концентрируют привилегии, что делает их мишенью. Если у вас есть готовая панель, в которой вы не уверены, наш аудит AI-кода отдельно проверяет аутентификацию, работу с сессиями и утечки данных. Посмотрите, [как устроена админка парка](https://ru.nerdy.pro/blog/white-label-app-platform-flutter), в разборе платформы, почитайте про [white-label услугу](https://ru.nerdy.pro/services/whitelabel-app-development), к которой она относится, или начните с пилларной страницы [веб-разработки](https://ru.nerdy.pro/services/web-development). ## Аудит AI-кода https://ru.nerdy.pro/services/ai-code-audit Аудит AI-кода — это независимая проверка кодовой базы, написанной AI или вручную, на уязвимости безопасности, архитектурные слабости и пробелы в готовности к продакшену — до того, как их найдут реальные пользователи. Итог — отчёт с находками, ранжированными по критичности, и понятный путь к их исправлению. Большая часть того, что мы аудируем сегодня, начиналась в Cursor, Bolt, Lovable, Claude или ChatGPT — прототип собирается быстро, но готовым к продакшену в первый же день он бывает редко. При этом сам аудит не ограничивается кодом, который написал AI: те же категории риска и тот же процесс применимы к кодовой базе, которую AI вообще не трогал. Будь вы командой, которая быстро выпускает продукт с AI-инструментами и хочет проверку перед следующим релизом, или основателем, который унаследовал AI-приложение и не знает, что в нём на самом деле, — аудит один и тот же. ### Что мы проверяем Семь направлений в каждом аудите — а не только сканирование безопасности, на котором останавливается большинство инструментов «AI code review». #### Что входит в каждый аудит - Безопасность: Уязвимости OWASP Top 10, инъекционные атаки, обход аутентификации, утечка секретов, небезопасное хранение данных - Архитектура: Границы сервисов, поток данных и связанность — выдержит ли дизайн реальных пользователей и реальную нагрузку - Производительность: Запросы к базе данных, проблемы N+1, утечки памяти и запас прочности под продакшен-трафиком - Корректность: Бизнес-логика, граничные случаи и баги, которые проявляются только на реальных данных - Зависимости: Устаревшие пакеты, известные CVE и риски лицензирования в дереве зависимостей - Покрытие тестами: Что протестировано, что нет и что молча ломается при следующем изменении - Запахи AI-кода: Дублированная логика, галлюцинированные API и несогласованные паттерны, которые оставляет агент без присмотра ### Что вы получаете Аудит — это не PDF, который ляжет в стол. На выходе три вещи. #### Результат аудита - Отчёт с находками: Каждая проблема ранжирована по критичности — критическая, высокая, средняя, низкая — с понятным объяснением риска и конкретным исправлением - Пример отчёта об аудите: Обезличенный пример реального аудита — вы увидите, за что платите, ещё до того, как пришлёте нам код - Созвон по устранению проблем: Разбор каждой находки с нашими инженерами и прямой ответ, что исправлять в первую очередь > **Посмотрите пример перед тем, как решиться** > > Хотите увидеть, как выглядит реальная находка — критичность, объяснение риска, исправление — прежде чем присылать нам код? [Запросите обезличенный пример отчёта об аудите](https://ru.nerdy.pro/contact?intent=ai-code-audit), и мы вам его вышлем. ### Два способа начать К нам приходят за аудитом по одной из двух причин. Отчёт с находками выглядит одинаково в обоих случаях — разница в том, с чего мы начинаем искать. #### Аудит AI-сгенерированного или vibe-coding приложения Вы (или нетехнический основатель, или подрядчик) собрали в Cursor, Bolt, Lovable, Claude или ChatGPT что-то рабочее — оно хорошо смотрится на демо, ранним пользователям нравится, и теперь ему нужно выдержать столкновение с реальным миром. Мы начинаем с того, в чём эти инструменты систематически проваливаются: захардкоженные секреты, аутентификация, которая формально есть, но не проверяет запрос, и использование API, которое никогда не проектировалось под бюджет. Наш разбор именно этого перехода — в статье [Как довести AI-прототип до продакшена](https://ru.nerdy.pro/blog/ai-prototype-to-production). #### AI-ускоренный аудит любой кодовой базы Чтобы это относилось к вам, кодовая база не обязана быть написана AI. Мы прогоняем один и тот же автоматизированный анализ — статический анализ, сканирование зависимостей, обнаружение секретов — на любом стеке, независимо от того, помогал ли AI его писать, и именно это позволяет нам укладывать аудит в недели, а не месяцы. Поверх этого идёт ручная проверка: наши инженеры сами читают вашу архитектуру и бизнес-логику; автоматизация лишь означает, что меньше бюджета уходит на поиск очевидного вручную. ### Что идёт не так в AI-сгенерированном коде Мы провели аудит десятков AI-сгенерированных кодовых баз. Одни и те же категории проблем появляются практически в каждой — это системные слепые пятна, а не исключения. - **45%** Проваливают тесты: AI-код проваливает проверки безопасности OWASP (Veracode, 2025) - **5-20x** Перерасход на API: Типичный перерасход из-за неоптимизированного использования платных API - **2x** Больше утечек секретов: Разработчики с AI-помощниками раскрывают учётные данные почти вдвое чаще (Apiiro, 2025) - **2-4 недели** Срок аудита: Время на выявление и исправление критических проблем Эти цифры взяты из независимых исследований: [Veracode протестировал 100+ LLM](https://www.veracode.com/blog/genai-code-security-report/) и обнаружил, что 45% сгенерированного кода проваливает тесты безопасности. [Анализ Apiiro за 2025 год](https://apiiro.com/blog/4x-velocity-10x-vulnerabilities-ai-coding-assistants-are-shipping-more-risks/) показал, что разработчики с AI-помощниками раскрывают учётные данные почти вдвое чаще. [Исследователи NYU](https://arxiv.org/pdf/2108.09293) нашли уязвимости в ~40% программ, сгенерированных Copilot, а [исследование Стэнфорда](https://ee.stanford.edu/dan-boneh-and-team-find-relying-ai-more-likely-make-your-code-buggier) подтвердило, что разработчики с AI-помощниками пишут менее безопасный код — и при этом больше уверены в его надёжности. Наш собственный разбор Flutter-кода, написанного агентами, — в статье [Почему AI-агенты буксуют на Flutter](https://ru.nerdy.pro/blog/ai-agents-struggle-with-flutter): дублированное состояние, отсутствие тестов и интерфейс, который никто не проверил второй раз. #### Уязвимости безопасности 1. Открытые API-ключи и секреты AI-инструменты часто хардкодят API-ключи, учётные данные баз данных и токены сторонних сервисов прямо в исходный код. Они попадают в публичные репозитории, клиентские бандлы или файлы окружения, которые уходят в продакшен. Один утёкший ключ OpenAI может привести к тысячам долларов несанкционированных расходов за несколько часов. 2. Отсутствие аутентификации и авторизации AI-приложения часто реализуют аутентификацию на поверхностном уровне — экран логина есть, но бэкенд на самом деле не проверяет права. API-эндпоинты принимают любой запрос. Административные маршруты доступны без проверки ролей. Пользователь A может видеть данные пользователя B, просто подменив ID в URL. 3. Инъекционные атаки SQL-инъекции, XSS и промпт-инъекции распространены в AI-коде. AI-модели генерируют код, который подставляет пользовательский ввод напрямую в запросы, HTML или промпты LLM без санитизации. Один уязвимый эндпоинт может скомпрометировать всю базу данных или позволить злоумышленникам манипулировать поведением вашего AI. 4. Небезопасное хранение данных Персональные данные в открытом виде, сессионные токены в localStorage, пароли, захешированные MD5, или вообще без хеширования. AI-инструменты выбирают простейшую реализацию, которая редко бывает безопасной. Соответствие GDPR и требованиям конфиденциальности, как правило, отсутствует. Наш разбор [шифрования в продакшен-приложениях](https://ru.nerdy.pro/blog/encryption-explained) показывает, как данные должны защищаться при хранении и передаче. #### Оптимизация расходов на API 1. Избыточные вызовы API Самая дорогая проблема, которую мы находим. AI-приложения делают лишние вызовы к платным сторонним API — геокодинг, финансовые данные, AI-модели, сервисы верификации. Они запрашивают одни и те же данные повторно, не кэшируют ответы и делают запросы, которых можно было бы избежать простой локальной логикой. Мы регулярно находим приложения, где 60-80% расходов на API — впустую. 2. Отсутствие лимитов и бюджетов Нет лимитов на пользователя. Нет дневных лимитов расходов. Нет аварийной отсечки при скачке затрат. Один пользователь — или бот — может выжечь весь ваш месячный бюджет за день. AI-инструменты никогда не реализуют контроль затрат, потому что не имеют представления о вашей бизнес-модели и тарифах интегрируемых сервисов. 3. Отсутствие кэширования и батчинга Запрос одного и того же курса валюты, геолокации по IP или профиля пользователя из платного API при каждом обращении вместо кэширования. Одиночные вызовы API в цикле вместо использования пакетных эндпоинтов. Каждый лишний запрос стоит денег, и при масштабировании это складывается в тысячи долларов в месяц. #### Утечки данных и конфиденциальность 1. Логирование конфиденциальной информации AI-код обожает избыточное логирование. Email-адреса, пароли, платёжные данные и персональная информация попадают в логи приложения, сервисы трекинга ошибок и сторонние аналитические системы. Эти логи часто доступны без аутентификации и хранятся бессрочно. 2. Избыточные ответы API API-ответы, возвращающие целые объекты пользователя — включая хешированные пароли, внутренние ID, email-адреса и метаданные — когда фронтенду нужно только отображаемое имя. GraphQL-эндпоинты без ограничений глубины, позволяющие злоумышленникам извлечь всю модель данных. 3. Передача данных третьим сторонам AI-инструменты интегрируют аналитику, трекинг ошибок и мониторинг без учёта того, какие данные в них уходят. Поведение пользователей, персональная информация и бизнес-данные оказываются в сторонних системах без согласия, нарушая GDPR и другие требования конфиденциальности. ![Деплой непроверенного AI-кода в продакшен](https://ru.nerdy.pro/services/ai-transition-example.webp) *Что происходит, когда AI-код уходит в продакшен без аудита* ### Кто проводит аудит Nerdy Production возглавляет [Илья Никсан](https://ru.nerdy.pro/team/nixan) — экс-CTO QIWI, одной из крупнейших платёжных платформ на своём рынке, где он руководил примерно 12 инженерными командами: от веб-продуктов до карточного процессинга и зоны PCI-DSS. Это и есть планка, по которой мы меряем свои аудиты: не «работает ли код», а «выдержал бы он проверку человека, который нёс ответственность за соответствие требованиям и отвечал за продакшен-инцидент». У нас самих нет сертификации PCI-DSS или SOC 2, и мы прямо скажем, если находка выходит за рамки того, что может подтвердить аудит, — но человек, задающий планку критичности находок, управлял регулируемой инфраструктурой, а не просто читал о ней. Наши инженеры ежедневно работают с AI-инструментами разработки, и именно поэтому мы знаем, где они дают сбой. Статья [Почему AI-агенты буксуют на Flutter](https://ru.nerdy.pro/blog/ai-agents-struggle-with-flutter) разбирает конкретные пробелы, которые агент без присмотра оставляет за собой: дублированное состояние, отсутствие тестов, интерфейс, который никто не проверил второй раз — те же паттерны, что мы ищем в каждом аудите, независимо от языка и фреймворка. ### Как проходит аудит Мы даём полный аудит с конкретными исправлениями — не просто список проблем. 1. Доступ и определение объёма Вы предоставляете нам доступ на чтение к репозиторию и развёрнутому окружению. Мы определяем объём аудита на основе ваших приоритетов: полный аудит или фокус на конкретных направлениях (безопасность, расходы или конфиденциальность). Никаких изменений в коде на этапе аудита. 2. Автоматический анализ Мы запускаем статический анализ, сканирование уязвимостей зависимостей, обнаружение секретов и профилирование расходов на API. Это ловит очевидные проблемы — известные уязвимости, открытые учётные данные, устаревшие пакеты с патчами безопасности и явные проблемы производительности. 3. Ручная экспертная проверка Наши инженеры вручную проверяют архитектуру, бизнес-логику, потоки аутентификации, API-интеграции и обработку данных. Именно здесь мы находим то, что пропускают автоматизированные инструменты: логические ошибки, пробелы в авторизации, возможности оптимизации расходов и ошибки проектирования, которые проявятся при масштабировании. 4. Отчёт с ранжированием по критичности Мы предоставляем детальный отчёт, где каждая находка размечена по критичности (критическая, высокая, средняя, низкая) и типу (безопасность, расходы, конфиденциальность, производительность). Каждая находка включает понятное описание риска, proof of concept там, где применимо, и конкретную рекомендацию по исправлению с примерами кода. 5. Исправление — выбор за вами Вы решаете, кто исправляет находки. Мы можем реализовать все исправления сами как проект с фиксированным объёмом — сначала критические патчи безопасности, вы проверяете каждое изменение перед мержем. Или, если вы хотите, чтобы исправления делала ваша команда, мы можем встроить в неё инженера, который уже знает ваш отчёт с находками, через [расширение команды](https://ru.nerdy.pro/services/team-augmentation) — вместо того чтобы просто вручить вам документ и исчезнуть. В любом случае результат — кодовая база, готовая к продакшену, которую можно деплоить с уверенностью. ### AI-код vs продакшен-код Что меняется после профессионального аудита | Аспект | После аудита (Production-Ready) | AI-код | Разработка с нуля | | --- | --- | --- | --- | | Безопасность | Усиленная | Поверхностная | Зависит от команды | | Расходы на API | Оптимизированы | Перерасход в 5-20x | Обычно оптимизированы | | Конфиденциальность | Соответствие GDPR | Утечки данных | Зависит от процесса | | Время до продакшена | 2-4 недели | Уже работает | 3-6 месяцев | | Стоимость запуска | Низкая (стоимость аудита) | Бесплатно (рискованно) | Высокая (полная разработка) | | Обработка ошибок | Корректное восстановление | Падения в граничных случаях | Обычно обработаны | | Масштабируемость | Протестирована под нагрузкой | Не протестирована | Архитектурно заложена | | Мониторинг | Полная наблюдаемость | Нет или избыточный | Стандартная настройка | > **Суть:** AI-код позволяет пройти 80% пути за 5% времени. Но оставшиеся 20% — безопасность, оптимизация расходов, конфиденциальность данных, обработка ошибок — отделяют демо от продакшен-приложения. Аудит закрывает этот разрыв, не выбрасывая то, что уже создано с помощью AI. ### Цены на аудит AI-кода Прозрачные тарифы в зависимости от размера кодовой базы и объёма аудита #### Безопасность Только критические уязвимости **180–450 тыс. ₽** 1-2 недели - Сканирование уязвимостей OWASP Top 10 - Обнаружение секретов и учётных данных - Проверка аутентификации и авторизации - Проверка уязвимостей зависимостей - Приоритизированный отчёт по находкам - Рекомендации с примерами кода [Начать](https://ru.nerdy.pro/contact?intent=ai-code-audit) #### Оптимизация расходов Сократите расходы на API **270–630 тыс. ₽** 1-2 недели - Профилирование платных API - Выявление избыточных вызовов - Стратегия кэширования и батчинга - План внедрения rate limiting - Оптимизация стоимости запросов - Отчёт с прогнозом экономии [Начать](https://ru.nerdy.pro/contact?intent=ai-code-audit) #### Полный аудит (Популярно) Полная готовность к продакшену **450 тыс. ₽ – 1,4 млн ₽** 2-4 недели - Всё из раздела «Безопасность» - Всё из раздела «Оптимизация расходов» - Проверка конфиденциальности и GDPR - Анализ производительности и масштабируемости - Аудит инфраструктуры - Детальный отчёт + внедрение исправлений [Начать](https://ru.nerdy.pro/contact?intent=ai-code-audit) #### Поддержка Постоянный аудит и мониторинг **от 180 тыс. ₽** в месяц - Ежемесячные проверки безопасности - Мониторинг расходов на API и алерты - Ревью обновлений зависимостей - Проверка безопасности новых функций - Приоритетное реагирование на инциденты - Прямой доступ через Slack/Telegram [Начать](https://ru.nerdy.pro/contact?intent=ai-code-audit) > **Возможен индивидуальный расчёт стоимости** > > Стоимость зависит от размера кодовой базы, количества сервисов и интеграций, а также объёма аудита. Небольшое односервисное приложение с одной AI-интеграцией будет стоить ближе к нижней границе. Мультисервисная платформа с несколькими AI-провайдерами, процессингом платежей и пользовательскими данными потребует более тщательной проверки. Свяжитесь с нами для бесплатной первичной оценки. ### Часто задаваемые вопросы Ответы на частые вопросы об аудите AI-сгенерированного и продакшен-кода #### Моё приложение создано в Cursor / Bolt / Lovable — вы работаете с ними? Да. Мы проводим аудит кода независимо от того, как он был сгенерирован. Cursor, Bolt, Lovable, Claude, ChatGPT, GitHub Copilot или любая комбинация AI-инструментов — процесс аудита одинаков. На выходе — код, и именно его мы проверяем. У нас есть опыт со всеми основными AI-инструментами, и мы знаем их типичные ошибки. #### Вы аудируете кодовые базы, которые не создавались с помощью AI? Да. Аудит выявляет одни и те же категории риска — безопасность, архитектуру, производительность, корректность, зависимости, покрытие тестами — независимо от того, писал ли код AI. То, что мы называем AI-ускоренным аудитом, использует автоматизированный анализ, чтобы быстрее пройти любую кодовую базу, поэтому написанное вручную приложение получает такую же тщательную проверку в те же сроки. #### Нужно ли останавливать приложение на время аудита? Нет. Аудит никак не нарушает работу приложения. Мы проверяем исходный код и можем запускать тесты только на чтение в staging-окружении. Ваше продакшен-приложение продолжает работать в штатном режиме. Если мы обнаружим критическую уязвимость с немедленным риском, мы уведомим вас сразу, чтобы вы могли принять решение. #### Сколько реально можно сэкономить на API? По нашему опыту, AI-приложения переплачивают за вызовы платных API в 5-20 раз. Основная экономия — за счёт устранения избыточных вызовов к сервисам геокодинга, финансовых данных, AI-провайдерам и другим платным API, внедрения кэширования ответов, батчинга запросов и добавления rate limiting для предотвращения злоупотреблений. Мы наблюдали снижение ежемесячных счетов за API с $5 000 до менее чем $500 после оптимизации, хотя результаты зависят от конкретных паттернов использования. #### Вы также исправляете найденные проблемы или только делаете отчёт? И то, и другое. Отчёт аудита содержит конкретные рекомендации по исправлению с примерами кода для каждой находки. При желании мы можем реализовать все исправления сами — это включено в тариф «Полный аудит» и доступно как дополнение к другим тарифам. Если вы хотите, чтобы исправления делала ваша команда, мы можем встроить в неё инженера через расширение команды. Вы проверяете и одобряете каждое изменение перед мержем. #### Можно посмотреть пример отчёта об аудите перед тем, как решиться? Да. Запросите обезличенный пример отчёта об аудите через форму контактов, и мы вышлем его — тот же формат с ранжированием по критичности, объяснениями рисков и рекомендациями по исправлению, что и в реальном отчёте, только без данных, идентифицирующих клиента. #### Я не технический специалист — я пойму отчёт аудита? Да. Каждая находка включает описание риска и его бизнес-последствий на понятном языке, а не только технический жаргон. Проблемы разнесены по критичности, чтобы вы понимали, что требует немедленного внимания, а что может подождать. В начале отчёта — executive summary с ключевыми выводами и рекомендованным планом действий. ## Веб-разработка https://ru.nerdy.pro/services/web-development ### Современная веб-разработка на Nuxt и SSR Мы создаём быстрые, понятные поисковикам веб-приложения на современном стеке с серверным рендерингом. Не конструктор сайтов и не раздутый single-page app, который отдаёт Google пустой экран, — настоящие приложения, которые рендерятся на сервере, быстро грузятся на телефоне и ранжируются. Насколько мы уверены в этом стеке? **Этот самый сайт собран на нём** — Nuxt 4, серверный рендеринг, Tailwind CSS и Nuxt Content. Мы выбрали эти инструменты не для лендинга — мы ведём на них собственный бизнес. И делали на них проекты для клиентов, включая [Formtastic](https://ru.nerdy.pro/portfolio/formtastic), платформу для создания форм на Nuxt, и [платформу лояльности с кешбэком](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform), чья админка на Nuxt запускает брендированные приложения поверх PHP-сервисов. - **SSR** Индексируется сразу: Серверный HTML, который краулеры читают без запуска JavaScript - **90+** Цель по Lighthouse: Мы строим под Core Web Vitals, а не против них - **Mobile-First** Адаптивно везде: Сначала под телефоны, затем — десктоп - **Type-Safe** TypeScript насквозь: От слоя API до пропсов компонентов ### Наш стек — и почему Nuxt + SSR Выбор фреймворка — это разница между сайтом, который Google читает мгновенно, и тем, который он с трудом рендерит. Вот на чём мы строим и почему. #### Серверный рендеринг для SEO Классический single-page app отдаёт пустую HTML-оболочку и просит браузер собрать страницу с помощью JavaScript. От этого страдают краулеры, превью ссылок и медленные устройства. Nuxt рендерит полный HTML на сервере, поэтому контент присутствует уже в первом ответе — мета-теги, заголовки, структурированные данные и текст, всё до того, как выполнится хоть строка клиентского JavaScript. Это самый мощный рычаг внутренней SEO-оптимизации, который может дать фреймворк, и здесь он по умолчанию, а не как надстройка. #### Tailwind для быстрого, единообразного UI Мы стилизуем на Tailwind CSS — utility-first, без хаотичных глобальных стилей, без мёртвого CSS, который грузится пользователю. Он держит дизайн-систему единой на всех страницах, делает адаптивные брейкпоинты явными и отгружает только реально используемые классы. Результат — UI, который быстро собирать, легко поддерживать и который мало весит. #### Nuxt Content для контентных сайтов Для маркетинговых сайтов, документации и блогов [Nuxt Content](https://content.nuxt.com) превращает Markdown в полностью отрендеренные на сервере страницы с типобезопасными схемами — ровно так работают блог и страницы услуг этого сайта. Редакторы пишут Markdown; сайт остаётся быстрым и индексируемым; разработчики держат всё под контролем версий. База данных не нужна. #### Strapi, когда нужна headless CMS Когда контентом должны владеть нетехнические редакторы — товары, объявления, кампании, — мы подключаем [Strapi](https://strapi.io), open-source headless CMS. Ваша команда получает удобную админку; фронтенд на Nuxt получает данные через чистый API и рендерит их на сервере. Вы получаете редакторскую свободу без потери производительности и SEO, а данные и хостинг остаются вашими. #### Что мы делаем правильно - Мобильная совместимость: Mobile-first, адаптивные макеты, которые работают на любом экране от телефона до широкоформатного — протестировано, а не на авось - Правильная аутентификация: Сессии, OAuth и JWT с реальными серверными проверками авторизации — а не экран входа с открытым бэкдором - Безопасные cookie и согласие: httpOnly, secure, SameSite cookie для сессий плюс обработка согласия по GDPR для аналитики и трекинга - SEO встроено: Серверные мета-теги, Open Graph, canonical и hreflang, структурированные данные JSON-LD на каждой странице - Производительность и Core Web Vitals: Оптимизация изображений, code splitting и кеширование, настроенные под прохождение Core Web Vitals на реальных устройствах - Интеграция headless CMS: Nuxt Content или Strapi, чтобы команда публиковала контент, не трогая код > **Безопасность — часть сборки** > > Аутентификация, cookie и работа с данными — именно здесь большинство веб-приложений незаметно даёт утечки. Мы делаем это правильно с самого начала — а если вам досталось приложение, в котором вы не уверены, наш [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit) проверит аутентификацию, безопасность сессий и утечки данных, пока они не стали инцидентом. ### Что мы строим Четыре вида веб-работы возвращаются в наших проектах снова и снова, и теперь у каждого есть собственная страница с доказательствами за ней: - **[Админ-панели и дашборды](https://ru.nerdy.pro/services/web-development/admin-dashboards)** — бэк-офисы, стоящие за продуктами: ролевой доступ, формы, которые управляют реальной машинерией, таблицы, остающиеся удобными на любом объёме. Опорный пример — админка, запускающая брендированные приложения нашей [платформы кэшбэка и лояльности](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform). - **[Разработка на Nuxt](https://ru.nerdy.pro/services/web-development/nuxt-development)** — маркетинговые сайты, контентные платформы и миграции на серверный рендеринг, как переезд [Formtastic](https://ru.nerdy.pro/portfolio/formtastic) с шаблонов Django на Nuxt. Этот сайт — постоянная демонстрация. - **[Парные веб-приложения](https://ru.nerdy.pro/services/web-development/companion-web-app)** — браузерная сторона продукта, который живёт прежде всего в мобильном приложении и делит с ним один API и один бэкенд. - **[Встраиваемые виджеты и скрипты](https://ru.nerdy.pro/services/web-development/embeddable-widgets)** — JavaScript, работающий внутри страниц, которые вы не контролируете, без зависимостей и защищённый по замыслу, как сборщик за нашей [платформой детекции ботов](https://ru.nerdy.pro/portfolio/realtime-bot-detection-platform). ### Flutter для веба — плюсы и минусы Flutter умеет компилироваться в веб, и клиенты с мобильным приложением на Flutter часто спрашивают, не переиспользовать ли его и для сайта. Честный ответ: иногда — но редко для публичного, контентного веба. Вот в чём компромисс. | Аспект | Nuxt + SSR (Публичный веб) | Flutter Web | Классический SPA | | --- | --- | --- | --- | | SEO / индексация | Отлично (SSR) | Плохо (canvas) | Слабо (пустая оболочка) | | Размер первой загрузки | Маленький | Тяжёлый бандл | Средний | | Общий код с мобильным | Нет | Почти 100% | Частично (React Native) | | UI как в приложении | Хорошо | Отлично | Хорошо | | Контентные / маркетинговые сайты | Идеально | Плохо подходит | Сносно | | Доступность | Нативный HTML | Улучшается, ограничена | Нативный HTML | | Лучше всего для | Сайты и веб-приложения | Внутренние инструменты в духе приложения | Дашборды | **Где Flutter web силён:** продукт, который ведёт себя как приложение, где у вас уже есть мобильная кодовая база на Flutter и вы хотите, чтобы одна команда выпускала продукт на всех платформах — внутренние дашборды, инструменты за логином, сильно интерактивные или canvas-ориентированные интерфейсы за стеной авторизации, где SEO не важно, а пользователи ждут приложение, а не страницу. **Где он подводит:** всё, что нужно находить в Google. Flutter web рендерит в canvas, который краулеры читают с трудом, отгружает тяжёлый стартовый бандл и конфликтует с браузером в части доступности и диплинков. Для маркетинговых сайтов, блогов, e-commerce и лендингов это дисквалифицирующий недостаток. > **Итог:** используйте **Nuxt + SSR** для всего публичного и SEO-зависимого — маркетинговых сайтов, контента, e-commerce, маркетинговых страниц SaaS. Используйте **Flutter web**, когда расширяете мобильное Flutter-приложение в веб-инструмент, который ведёт себя как приложение, а охват через поиск не цель. Мы делаем и то, и другое и честно подскажем, что нужно вашему проекту. Если на повестке и мобильное приложение, посмотрите услугу [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development) и разбор компромиссов в [Flutter против React Native в 2026](https://ru.nerdy.pro/blog/flutter-vs-react-native-2026) и [Flutter против нативной разработки](https://ru.nerdy.pro/blog/flutter-vs-native-2026). ### Стоимость веб-разработки Прозрачные диапазоны на основе объёма и сложности #### Маркетинговый сайт / лендинг Быстрый, готовый к SEO, контентный **450 тыс. ₽ – 1,1 млн ₽** 2-4 недели - Nuxt + SSR для полной индексации - Адаптивный, mobile-first дизайн - SEO-мета, Open Graph и структурированные данные - Nuxt Content или Markdown-CMS - Формы обратной связи и аналитика - Настройка под Core Web Vitals - Деплой и настройка домена [Начать](https://ru.nerdy.pro/contact?intent=web-development) #### Веб-приложение (Популярно) Интерактивный продукт с авторизацией **1,4–3,6 млн ₽** 1-3 месяца - Всё из маркетингового сайта - Аутентификация пользователей (OAuth / JWT) - Безопасные сессии и работа с cookie - Интеграция REST или GraphQL API - Ролевой контроль доступа - Проектирование и интеграция БД - Админ-панель [Начать](https://ru.nerdy.pro/contact?intent=web-development) #### Платформа на headless CMS Контент в руках редакторов, в любом объёме **1,8–4,5 млн ₽** 2-4 месяца - Бэкенд на Strapi или Nuxt Content - Кастомная редакторская админка - Поддержка многоязычного контента - Индексируемые страницы с серверным рендерингом - Управление медиа и ассетами - Процесс превью и публикации - Архитектура API-first [Начать](https://ru.nerdy.pro/contact?intent=web-development) #### SaaS / E-commerce Сложный, масштабируемый веб-продукт **от 4,5 млн ₽** 4+ месяца - Все платформы и интеграции - Платежи и подписки - Мультитенантная архитектура - Продвинутая безопасность и комплаенс - Масштабируемая инфраструктура бэкенда - Мониторинг и наблюдаемость - Выделенная поддержка [Начать](https://ru.nerdy.pro/contact?intent=web-development) > **Доступна индивидуальная оценка** > > Каждый проект уникален. Эти диапазоны приблизительны и основаны на типовом объёме. Мы подготовим детальную оценку, когда разберёмся в ваших требованиях, интеграциях и сроках. Свяжитесь с нами для бесплатной консультации и точной оценки. ### Частые вопросы Распространённые вопросы о наших услугах веб-разработки #### Почему вы строите на Nuxt, а не на WordPress или конструкторе сайтов? Nuxt даёт серверный рендеринг — это значит, что поисковики и превью ссылок видят полностью собранный HTML в первом ответе, а не пустую оболочку. Вы получаете скорость и индексируемость статического сайта с мощью настоящего приложения, полный контроль версий над кодом — и никакой раздутости от плагинов и поверхности атаки, свойственных типичной установке WordPress. Для контентных сайтов мы соединяем Nuxt с headless CMS, чтобы у редакторов всё равно была удобная админка. #### Действительно ли серверный рендеринг улучшает SEO? Да, существенно. При SSR контент страницы, мета-теги, данные Open Graph и структурированные данные присутствуют в HTML, который отдаёт сервер, поэтому краулеры читают их без выполнения JavaScript. Single-page приложения, которые рендерятся целиком в браузере, индексируются медленнее, а некоторые краулеры и превью ссылок в соцсетях могут вовсе их пропустить. SSR — самое значимое решение на уровне фреймворка, которое можно принять ради органического поиска. #### Можете ли вы сделать моё веб-приложение на Flutter для веба? Можем, и для подходящего проекта это отличный вариант. Flutter web хорош для продуктов, которые ведут себя как приложение, и внутренних инструментов за логином, особенно когда у вас уже есть мобильное приложение на Flutter и вы хотите общую кодовую базу. Он плохо подходит для всего, что должно ранжироваться в Google, потому что рендерит в canvas, который краулеры читают с трудом, и отгружает тяжёлый стартовый бандл. Для публичных, контентных или e-commerce сайтов мы рекомендуем Nuxt с серверным рендерингом. #### Как вы обеспечиваете безопасность аутентификации и данных? Мы реализуем реальную серверную авторизацию, а не просто экран логина. Сессии используют httpOnly, secure, SameSite cookie, чтобы токены нельзя было украсть клиентскими скриптами, и мы поддерживаем потоки OAuth и JWT с ролевым контролем доступа. Согласие на cookie обрабатывается по GDPR. Если у вас есть готовое приложение, наш аудит AI-кода отдельно проверяет аутентификацию, работу с сессиями и утечки данных. #### Что выбрать — Nuxt Content или Strapi? Зависит от того, кто редактирует контент. Nuxt Content идеален, когда контентом управляют разработчики или технические авторы в Markdown под контролем версий, например блог или документация, без базы данных. Strapi — правильный выбор, когда нетехническим редакторам нужна удобная админка для управления товарами, объявлениями или кампаниями. Оба рендерятся на сервере через фронтенд на Nuxt, так что в любом случае сайт остаётся быстрым и индексируемым. Мы поможем выбрать под вашу команду. #### Сколько стоит проект веб-разработки? Быстрый маркетинговый или контентный сайт обычно стоит от 400 000 до 960 000 рублей, веб-приложение с авторизацией от 1 200 000 до 3 200 000, платформа на headless CMS от 1 600 000 до 4 000 000, а сложный SaaS или e-commerce продукт свыше 4 000 000. Итоговая сумма зависит от числа функций, интеграций и объёма дизайна. Мы даём фиксированную оценку после бесплатной консультации. :: ## Встраиваемые виджеты и скрипты https://ru.nerdy.pro/services/web-development/embeddable-widgets Встраиваемый виджет — это код, который ваши клиенты вставляют на собственные сайты: сниппет аналитики, кнопка чата, форма бронирования, измерительный сборщик. Это самый трудный вид фронтенд-инженерии, потому что всё, что обычный веб-проект принимает как данность, здесь отсутствует: вы не выбираете ни фреймворк, ни базовый уровень браузеров, ни соседние скрипты на странице, ни момент, когда ваши пользователи обновятся. Ваш код работает гостем на страницах, которых вы никогда не увидите, и вести себя он обязан на всех сразу. Это нишевая дисциплина, и мы в ней работали: построенный нами сборщик сигналов встроен в тысячи сайтов, которые мы не контролируем, и кормит платформу, оценивающую десятки тысяч запросов в минуту. #### Какие продукты мы делаем - **Измерительные и аналитические сборщики** — скрипты, которые собирают сигналы со страницы и отправляют их на вашу платформу, как тот, что стоит за нашей работой по детекции ботов - **Функциональные виджеты** — чат, бронирование, формы, калькуляторы: кусочек вашего продукта, живущий внутри страницы вашего клиента - **Интеграции, устанавливаемые сниппетом** — онбординг «добавьте один тег script», который ставит ваш продукт перед посетителями вашего клиента - **Приём данных за скриптом** — бэкенд, принимающий то, что отправляет сниппет, на любом объёме, в который складывается трафик ваших клиентов - **Дашборд, из которого ваши клиенты за всем этим наблюдают** — интерфейсы отчётности и настройки разобраны на странице [админ-панелей и дашбордов](https://ru.nerdy.pro/services/web-development/admin-dashboards) ### Кто будет делать ваш встраиваемый скрипт Одну такую систему мы построили целиком — от сниппета до платформы за ним. #### Сборщик в тысячах враждебных страниц Для [платформы детекции ботов в реальном времени](https://ru.nerdy.pro/portfolio/realtime-bot-detection-platform) мы построили браузерный сборщик, который собирает доказательства, на основе которых выносится вердикт «человек или бот». Это чистый JavaScript без фреймворка, без рантайма, добавляемого на этапе сборки, и без зависимостей, потому что он работает в тысячах страниц рядом со всем, что эти страницы ещё загружают, не может рассчитывать на модульную систему или набор полифилов и не имеет права конфликтовать с сайтом вокруг себя. Среда активно враждебна: то, что он измеряет, пытается его обойти. И он маленький, потому что каждый килобайт списывается с перформанс-бюджета самого клиента, на каждом просмотре страницы, всегда. #### И платформа, принимающая то, что он отправляет Сниппет — это маленькая видимая часть системы. За этим сборщиком мы построили сервисы приёма и скоринга на Go и дата-платформу под ними, потому что успешный встраиваемый скрипт умножает собственный трафик на каждого клиента, который его установил, и бэкенд должен быть рассчитан на эту арифметику с первого дня. Полная архитектура — в [кейсе](https://ru.nerdy.pro/portfolio/realtime-bot-detection-platform). ### Чего на самом деле требует встраиваемый код Пять ограничений отделяют эту работу от обычной фронтенд-инженерии. #### Бюджет размера, измеряемый в килобайтах Ваш скрипт отгружается каждому посетителю каждого клиента. Рантайм фреймворка оказался бы больше целого хорошо сделанного сборщика, поэтому серьёзные встраиваемые скрипты обходятся без зависимостей по замыслу, а не из предпочтений. Размер вашего скрипта ваши клиенты видят в собственных Lighthouse-показателях. #### Ноль предположений о принимающей странице Ни модульной системы, ни базового уровня современных браузеров, ни гарантии, что страница уже не грузит конфликтующую библиотеку, чересчур усердный CSS-reset или вторую копию вашего же скрипта. Всё работает в собственной области видимости, глобальное пространство имён трогается один раз — если вообще трогается, — а любой отказ происходит тихо, вместо того чтобы уронить чужое оформление заказа. #### Защита по замыслу Страница, в которой работает ваш код, может быть инструментирована, пропатчена или эмулирована вокруг него. В нашей работе по детекции ботов обойти измерение и есть вся работа противника. Даже в более дружелюбных встраиваниях урок тот же: читать браузерные API напрямую, проверять среду, а не доверять ей, и держать каждое суждение на сервере, где логика не видна странице. #### Версионирование, которое нельзя навязать Клиенты вставляют ваш сниппет один раз и больше его не трогают. Эта одна строка обязана оставаться стабильным загрузчиком, пока всё за ней эволюционирует: поэтапные раскатки, нерушимый контракт между загрузчиком и полезной нагрузкой и совместимость, поддерживаемая годами, — потому что «пожалуйста, обновите код встраивания» — письмо, на которое никто не отвечает. #### Согласие и приватность как архитектура Сторонний скрипт — это третья сторона, поэтому GDPR применяется на каждом сайте, который его устанавливает. Что скрипт собирает, когда ему можно работать относительно состояния согласия на принимающей странице и что уходит по сети — это проектные решения, которые мы фиксируем явно и заранее, чтобы комплаенс-ревью ваших клиентов пропускало ваш встраиваемый код, а не помечало его. ### Как мы работаем **Проект с фиксированным объёмом.** Сниппет, загрузчик, приём данных и конвейер деплоя, который безопасно их версионирует: всё это оценивается и выпускается как одна система. **Staff augmentation.** Наши инженеры входят в команду, которая владеет вашей платформой, — см. [расширение команды](https://ru.nerdy.pro/services/team-augmentation). В обоих случаях смету под задачу мы присылаем в течение двух рабочих дней после того, как разберёмся в требованиях. ### Сколько стоит встраиваемый виджет Сниппет маленький, система — нет. Встраиваемый скрипт оценивается по тем же опубликованным тарифам, что и страница [веб-разработки](https://ru.nerdy.pro/services/web-development). Сборщик или виджет вместе с загрузчиком и компактным приёмом данных попадает в тариф веб-приложения, 1 350 000–3 600 000 ₽; когда платформа за ним несёт настоящий масштаб (скоринг, аналитику, клиентские дашборды), система оценивается как работа уровня SaaS, от 4 500 000 ₽. Это те же ключи, из которых рисуется таблица цен пилларной страницы, поэтому разойтись они не могут. Что двигает цифру: насколько враждебны среды, которые ваш скрипт должен пережить, в какой суммарный трафик складываются страницы ваших клиентов и какая часть принимающей платформы уже существует. ### Частые вопросы Что чаще всего спрашивают команды, отправляющие код в чужие страницы. #### Почему нельзя собрать наш виджет на React или Vue? Потому что рантайм фреймворка оказался бы больше всего виджета целиком, и потому что принимающая страница может уже выполнять другую версию того же фреймворка или глобальное окружение, которое воюет с вашим. Встраиваемый код, который отгружается каждому посетителю каждого клиента, измеряется относительно перформанс-бюджета каждого из этих клиентов, поэтому серьёзные встраиваемые скрипты — это чистый JavaScript без зависимостей по замыслу. Собственный сайт вашего продукта может быть сколь угодно тяжёлым на фреймворки; код, который путешествует, обязан быть самодостаточным. #### Как встраиваемый скрипт не ломает сайты, которые его устанавливают? Ничего не предполагая и ничего не трогая. Всё работает внутри собственной области видимости, глобальное пространство имён трогается один раз — если вообще трогается, — стили изолированы, чтобы CSS принимающей страницы не протекал ни внутрь, ни наружу, а каждый путь отказа деградирует тихо: сломанный виджет никогда не должен утащить за собой оформление заказа клиента. Мы строим в расчёте на враждебные страницы с самого начала, потому что сборщик, который мы эксплуатируем, работает рядом со всем, что загружают тысячи никак не связанных сайтов. #### Как вы обновляете скрипт, который клиенты вставили годы назад? Вставленная строка никогда не является продуктом. Это крошечный стабильный загрузчик, который подтягивает актуальную полезную нагрузку. Контракт между загрузчиком и нагрузкой — тот единственный интерфейс, который нельзя ломать, а всё за ним может эволюционировать: поэтапные раскатки сначала на долю трафика, версионированные нагрузки и мгновенный откат, когда релиз браузера преподносит сюрприз. Правильно спроектированный в первый день, этот шов и делает «пожалуйста, обновите код встраивания» письмом, которое вам никогда не придётся отправлять. #### Что с GDPR и согласием на cookie для встраиваемых скриптов? Сторонний скрипт — это третья сторона по GDPR на каждом сайте, который его устанавливает, поэтому под согласие мы проектируем с самого начала. Мы явно фиксируем, что скрипт собирает, идентифицирует ли что-то из этого человека и как он ведёт себя относительно состояния согласия принимающей страницы — включая режим работы без согласия там, где этого требует комплаенс. Цель — встраиваемый код, который приватность-ревью ваших клиентов одобряет без совещания. #### Можете ли вы построить и бэкенд, который принимает данные? Да, и мы охотнее строим обе стороны, чем любую из них по отдельности. Успешный встраиваемый скрипт умножает свой трафик на каждого клиента, который его установил, поэтому приём данных должен быть рассчитан на суммарный объём с самого начала: платформа за нашим сборщиком оценивает десятки тысяч запросов в минуту в реальном времени, на сервисах на Go, которые мы построили целиком. Сниппет и его бэкенд — одна система, и её отказы живут на их стыке. #### Сколько стоит разработка встраиваемого виджета? Виджет или сборщик с загрузчиком и компактным приёмом данных обычно стоит от 1 200 000 до 3 200 000 рублей и занимает один-три месяца: это опубликованный тариф веб-приложения на нашей странице веб-разработки. Когда настоящий продукт — принимающая платформа со скорингом в реальном времени, аналитикой и клиентскими дашбордами, система оценивается как работа уровня SaaS, свыше 4 000 000. Сам сниппет редко бывает дорогой частью; дорог трафик, который он порождает. Прочитайте, как сборщик встроен в систему целиком, в [кейсе детекции ботов](https://ru.nerdy.pro/portfolio/realtime-bot-detection-platform), посмотрите на [сервисы на Go](https://ru.nerdy.pro/technologies/go) за ним или начните с пилларной страницы [веб-разработки](https://ru.nerdy.pro/services/web-development). ## Из чат-бота в приложение https://ru.nerdy.pro/services/chatbot-to-app ### Зачем переходить от чат-бота к самостоятельному приложению Чат-боты — отличный способ быстро проверить идею. Telegram-боты, интеграции с WhatsApp и виджеты чатов на сайте позволяют протестировать спрос с минимальными вложениями. Но когда база пользователей растёт, ограничения становятся очевидными: ограниченный интерфейс, зависимость от платформы, отсутствие полноценных push-уведомлений и нулевое присутствие бренда на главном экране. Самостоятельное приложение снимает все эти ограничения, сохраняя привычный пользователям разговорный формат. - **3-5x** Вовлечённость: Приложения обеспечивают большее удержание пользователей, чем боты - **24/7** На главном экране: Ваш бренд всегда в одном касании - **100%** Контроль уведомлений: Полные возможности push-уведомлений - **Полный** Контроль данных: Ваши данные, ваша инфраструктура, ваши правила ### Когда пора выходить за рамки бота Не каждому чат-боту нужно становиться приложением. Но есть чёткие сигналы, что продукт перерос свою платформу: #### Признаки того, что бот достиг предела 1. Ограничения интерфейса Ваш бот делает больше, чем простой обмен текстовыми сообщениями. Пользователям нужны мультимедиа, интерактивные элементы, графики или сложные многоступенчатые сценарии — всё то, с чем мессенджеры справляются плохо. Каждый обходной путь внутри ограничений бота — это время, потраченное на борьбу с платформой, а не на развитие продукта. 2. Платформенные риски Весь ваш бизнес работает на чужой инфраструктуре. Изменение политики, прекращение поддержки API или блокировка могут остановить ваш бизнес в одночасье. Лимиты Telegram Bot API, ограничения WhatsApp Business и процессы модерации платформ создают непредсказуемые риски для вашего роста. 3. Брендинг и пользовательский опыт Боты живут внутри чужого интерфейса. Вы не можете контролировать шрифты, цвета, анимации или макет. Нет заставки, нет онбординга, нет способа выстроить целостный образ бренда. Ваш продукт выглядит как любой другой бот — неотличимый в переполненном канале. 4. Ограничения монетизации Встроенные покупки, подписки и платёжные интеграции либо недоступны, либо сильно ограничены в большинстве мессенджеров. Самостоятельное приложение открывает полный инструментарий монетизации: подписки App Store и Google Play, нативные платёжные сценарии и прямой биллинг. #### Что вы получаете с приложением - Собственный UI/UX: Полный контроль над каждым пикселем — анимации, макеты и взаимодействия, созданные под ваш продукт - Push-уведомления: Целевые, запланированные уведомления без ограничений платформы и лимитов на отправку - Офлайн-доступ: Пользователи могут работать с контентом и функциями без интернета - Присутствие в сторах: Вас находят через поиск в App Store и Google Play - Нативные возможности устройства: Камера, биометрия, GPS, датчики и платформенные интеграции - Аналитика и атрибуция: Полное отслеживание поведения, анализ воронок и маркетинговая атрибуция ### Как мы выполняем переход Мы уже реализовывали именно такой переход для реальных продуктов — как за шесть недель превратить Telegram-бота в приложение, мы разбираем по неделям в [отдельном материале](https://ru.nerdy.pro/blog/telegram-bot-to-app-6-weeks). Та же чат-ориентированная основа на Flutter лежит в наших приложениях [Arcana](https://ru.nerdy.pro/portfolio/arcana) и [YouMi](https://ru.nerdy.pro/portfolio/youmi). Наш подход сохраняет то, что в вашем боте работает, и убирает его ограничения. #### Наш процесс 1. Аудит бота и маппинг функций Мы анализируем ваш существующий бот: каждую команду, каждый сценарий, каждую интеграцию. Каждая функция бота сопоставляется с нативным эквивалентом в приложении, и мы выявляем возможности, которые может дать только самостоятельное приложение. Результат — детальная спецификация, которая становится основой разработки. 2. Проектирование архитектуры Мы проектируем архитектуру приложения для работы с вашим существующим бэкендом. Если бот общается с REST API — приложение общается с тем же API. Если используются WebSocket-соединения — мы реализуем управление жизненным циклом соединений с логикой переподключения, очередью сообщений и синхронизацией состояния. Переписывать бэкенд не нужно, если вы сами этого не хотите. 3. UI/UX-дизайн Мы переводим ваши разговорные сценарии в нативные экраны приложения. Чат-взаимодействия остаются чатом там, где это уместно. Но теперь вы можете добавить дашборды, экраны настроек, галереи и любые другие паттерны интерфейса, которые были невозможны внутри бота. Для текущих пользователей дизайн остаётся привычным, но сам опыт становится заметно богаче. 4. Разработка и тестирование Мы используем Flutter для кроссплатформенной разработки. Одна кодовая база, две платформы, нативная производительность. Наш процесс включает непрерывную интеграцию, автоматизированное тестирование и регулярные демо-сборки, чтобы вы видели прогресс каждую неделю. 5. Миграция и запуск Мы берём на себя публикацию в App Store и Google Play, настройку аналитики, конфигурацию push-уведомлений и планирование пути миграции существующих пользователей. Ваш бот может продолжать работать во время перехода — мы помогаем переводить пользователей постепенно с помощью диплинков и объявлений в боте. ### Чат-бот vs самостоятельное приложение Что меняется при переходе с мессенджера на собственное приложение | Характеристика | Приложение (Рекомендуем) | Telegram-бот | WhatsApp-бот | | --- | --- | --- | --- | | Интерфейс | Полная кастомизация | Только текст и кнопки | Только текст и списки | | Push-уведомления | Полный контроль | Ограничено | Только шаблоны | | Офлайн-доступ | Да | Нет | Нет | | Брендинг | Полный контроль | Только аватар бота | Бизнес-профиль | | Монетизация | Полная (покупки, подписки) | Telegram Stars | Нет встроенной | | Функции устройства | Все нативные API | Камера, геолокация | Очень ограничено | | Аналитика | Полное отслеживание | Базовая статистика | Базовая статистика | | Платформенный риск | Нулевой | Высокий (изменения API) | Высокий (изменения политики) | | Присутствие в сторах | Полный SEO и ASO | Отсутствует | Отсутствует | | Владение данными | Полное | Совместно с Telegram | Совместно с Meta* | > **Главное:** Чат-бот — отличная стартовая точка и инструмент проверки идеи. Но когда продукту нужен собственный интерфейс, надёжные уведомления, монетизация и независимость от сторонних платформ, самостоятельное приложение — естественный следующий шаг. Переход не означает отказ от бота — он даёт вашему продукту пространство для роста. ### Чек-лист готовности к переходу из бота в приложение Прежде чем писать первую строчку кода, прогоните бота по этому чек-листу. Чем больше пунктов вы можете отметить, тем быстрее и дешевле пройдёт переход. Если на каком-то пункте застряли — именно для этого и нужен бесплатный аудит миграции. ##### Сигналы продукта — пора ли переходить? - У вас есть активная растущая база пользователей (от нескольких тысяч в месяц) - Пользователи регулярно просят функции, которые бот не может дать - Вы упираетесь в лимиты платформы, модерацию или ограничения API - Нужны push-уведомления, которые вы полностью контролируете — время, таргетинг и содержание - Нужна монетизация через подписки или встроенные покупки - Ваш бренд заслуживает места на главном экране, а не внутри чужого приложения ##### Техническая готовность — насколько сложен переход? - Бот уже общается с серверным API (REST, GraphQL или WebSocket) - Бизнес-логика живёт на сервере, а не зашита в обработчики бота - Можно выгрузить или синхронизировать существующие аккаунты и историю переписки - У вас есть полный список команд, сценариев и интеграций бота - Аутентификацию можно переиспользовать или сопоставить с телефоном, email или соцсетями ##### Готовность к запуску — можете ли вы выпустить и мигрировать? - У вас есть (или можно создать) аккаунты Apple Developer и Google Play - Вы можете анонсировать приложение существующим пользователям через самого бота - Есть план параллельной работы бота и приложения во время миграции - Вы знаете, какие метрики определяют успех (удержание, конверсия, выручка) [Получить бесплатный аудит миграции](https://ru.nerdy.pro/contact?intent=chatbot-to-app) > **Не уверены, сколько пунктов отметили?** > > Чтобы начать, не нужно отмечать все пункты. Большинство команд приходят к нам с пробелами — нет отдельного бэкенда, нет плана миграции, нет понимания, какие функции важнее всего. Аудит превращает этот чек-лист в конкретную приоритизированную дорожную карту со сроками и фиксированной сметой. ### Стоимость перехода из чат-бота в приложение Прозрачные тарифы на переход от бота к приложению #### Простой бот Переход без сложностей **450–900 тыс. ₽** 4-6 недель - Приложения для iOS и Android - Ключевые функции бота сохранены - Базовый чат-интерфейс - Push-уведомления - Аутентификация пользователей - Публикация в сторах - Миграция пользователей из бота [Начать](https://ru.nerdy.pro/contact?intent=chatbot-to-app) #### Функциональный бот Сложные сценарии и интеграции **900 тыс. ₽ – 2 млн ₽** 2-3 месяца - Приложения для iOS и Android - Все функции бота + новые экраны - Индивидуальный UI/UX-дизайн - Обмен сообщениями в реальном времени - Интеграция платежей - Офлайн-режим - Аналитическая панель - Админ-панель [Начать](https://ru.nerdy.pro/contact?intent=chatbot-to-app) #### AI-бот (Популярно) Боты с AI/ML-бэкендом **1,6–3,2 млн ₽** 3-4 месяца - Приложения для iOS и Android - AI-чат со стримингом ответов - Рендеринг Markdown в реальном времени - Мультимедийный контент - Управление подписками - Оптимизация производительности - Офлайн-кэширование - Аналитика и инсайты [Начать](https://ru.nerdy.pro/contact?intent=chatbot-to-app) #### Корпоративный бот Мультиплатформенная экосистема **от 3,2 млн ₽** 4+ месяца - Все платформы + Web - Миграция сложных рабочих процессов - Мультипользовательский/ролевой доступ - Корпоративные интеграции - Разработка бэкенда - Расширенная безопасность - White-label варианты - Выделенная поддержка [Начать](https://ru.nerdy.pro/contact?intent=chatbot-to-app) > **Возможен индивидуальный расчёт стоимости** > > Двух одинаковых переходов от бота к приложению не бывает. Стоимость зависит от сложности бота, количества функций, требований к бэкенду и желаемых сроков. Мы подготовим детальную смету после анализа вашего существующего бота. Свяжитесь с нами для бесплатной консультации и точного расчёта. ### Часто задаваемые вопросы Ответы на частые вопросы о переходе от чат-бота к самостоятельному приложению *Meta Platforms Inc. признана экстремистской организацией, её деятельность запрещена на территории РФ. #### Можно ли оставить бота работать параллельно с приложением? Конечно. Мы рекомендуем стратегию постепенной миграции. Бот продолжает работать, пока запускается приложение, и мы помогаем переводить пользователей постепенно через объявления в боте и диплинки. Многие компании оставляют упрощённую версию бота работать постоянно как дополнительную точку входа. #### Нужно ли будет переписывать бэкенд? Обычно нет. Если у бота уже есть серверный API, приложение подключается к тем же эндпоинтам. Мы адаптируем клиентскую архитектуру для работы с вашей существующей инфраструктурой. Если бот работает полностью внутри платформы (например, через Telegram Bot API без отдельного бэкенда), мы помогаем выделить бизнес-логику в полноценный API. #### Сколько времени занимает переход? Простой бот с базовым функционалом можно перевести за 4-6 недель. Функциональные боты со сложными сценариями, AI-интеграциями и функциями реального времени обычно занимают 2-4 месяца. Детальные сроки мы предоставляем после аудита существующего бота. #### Почему Flutter для приложения? Flutter позволяет создать приложение для iOS и Android из единой кодовой базы, что сокращает время и стоимость разработки на 40-50% по сравнению с двумя нативными приложениями. Flutter компилируется в нативный ARM-код, поэтому нет компромиссов по производительности. Он идеален для чат-приложений, которым нужен плавный скроллинг, обновления в реальном времени и рендеринг мультимедийного контента. #### Что будет с существующими пользователями? Мы проектируем путь миграции, минимизирующий трение. Сообщения в боте анонсируют приложение, диплинки направляют пользователей в App Store или Google Play, и мы можем синхронизировать аккаунты между ботом и приложением. Существующие данные и историю переписки можно сохранить и импортировать в новое приложение. ## Миграция с React Native на Flutter https://ru.nerdy.pro/services/flutter-app-development/flutter-migration ### Что такое миграция на Flutter на самом деле Если вы всё ещё выбираете между Flutter и React Native, сначала прочитайте [Flutter vs React Native в 2026 году](https://ru.nerdy.pro/blog/flutter-vs-react-native-2026) — эта страница исходит из того, что выбор уже сделан, и отвечает на вопросы реализации: как, сколько времени, сколько стоит и насколько рискованно. Миграция с React Native на Flutter — это переписывание, а не портирование. У Flutter и React Native нет общих артефактов сборки, общей библиотеки компонентов и общего слоя состояния — каждый экран, навигация и нативная интеграция создаются заново на Dart. Что действительно переживает переход — всё, что не является кодом: ваши бизнес-правила, контракты API, модели данных и дизайн-решения, которые уже приняла ваша команда. Всё это становится спецификацией, по которой строится новое приложение, — поэтому миграция быстрее и менее рискованна, чем разработка с чистого листа, но это, честно говоря, всё равно полная пересборка клиента. - **0** Общих артефактов сборки: React Native и Flutter компилируются в разные рантаймы — каждый экран создаётся заново на Dart - **Экран за экраном** Поэтапная поставка: Flutter встраивается в ваше работающее приложение по одному экрану, а не одним одномоментным переходом - **Без простоя** Приложение продолжает работать: Текущее приложение продолжает выходить в релизы и обслуживать пользователей всё время миграции - **100%** Бизнес-логика переносится: В виде спецификации — ваши правила, контракты API и модели данных переносятся, даже если код — нет ### Кому стоит мигрировать — а кому нет Flutter обычно оправдан для команды, уже работающей на React Native или нативе в продакшене, — но не всегда, и мы лучше скажем это прямо, чем продадим переписывание, которое вам не нужно. #### Признаки того, что пора мигрировать - Бесконечные апгрейды React Native (New Architecture, ломающие релизы, разъезжающиеся зависимости) отнимают реальное инженерное время каждый цикл - Вы упёрлись в потолок производительности — подтормаживания на сложных экранах, медленные списки, рывки анимаций, — который архитектура моста не может исправить - Нативные модули превратились в обузу: форкнутые пакеты, зависимости, зафиксированные на версии, код, оставленный прежним автором - Нанимать тяжело, и результат получается неровным, а вам нужна одна команда на одной кодовой базе вместо отдельных специалистов по RN и нативу - Вы уже пишете новые фичи на Flutter и хотите перевести на него остальное приложение #### Когда мигрировать не стоит - Ваше приложение стабильно, хорошо работает и не тормозит роадмап - Ваша команда довольна и продуктивна, и у неё нет желания браться за многомесячную пересборку - Вы много вложили в кастомные нативные модули: они нормально работают, а достойного аналога на Flutter для них нет - Причина мигрировать — «Flutter выглядит красиво», а не реальная проблема со стоимостью, производительностью или поддержкой > **Честный вывод:** если ни один из «признаков» выше не описывает ваше приложение — не мигрируйте. Стабильное приложение с довольной командой лучше не трогать. Расскажите, где у вас реально болит, и мы прямо скажем, стоит ли миграция того — включая ответ «пока нет». ### Поэтапный подход: add-to-app Причина мигрировать с нами, а не заказывать переписывание с нуля, — в модели поставки, а не в выборе фреймворка. Мы встраиваем Flutter в ваше существующее приложение на React Native или нативе как модуль — интеграция add-to-app — и переносим экраны по одному через общий роутер. Для этого не нужен одномоментный переход, и приложение не уходит в офлайн ни на секунду. А если вам нужен Flutter *внутри* нативного приложения, которое остаётся нативным, — внедрение без полного переезда, — это отдельная услуга: смотрите [интеграцию add-to-app](https://ru.nerdy.pro/services/flutter-app-development/add-to-app). #### Как работает add-to-app 1. **Существующая оболочка остаётся** Текущее приложение на RN или нативе продолжает работать в продакшене без изменений как оболочка-хост 2. **Встраивается модуль Flutter** Движок Flutter добавляется в оболочку через add-to-app и использует общую навигацию и нативные сервисы 3. **Экраны переносятся по одному** Каждый экран выходит на Flutter за роутером, тестируется и релизится независимо 4. **Экраны на RN выводятся из эксплуатации** Как только Flutter-экран доказал себя в продакшене, его аналог на RN удаляется, а не архивируется 5. **Полный переход** Когда перенесены все экраны, сама оболочка-хост убирается, и приложение становится на 100% Flutter Даром это не даётся. Всё время миграции у вас работают два тулчейна и два слоя состояния параллельно — а пока дизайн-система не перенесена полностью, ещё и два визуальных языка в разных углах одного приложения. Временно сложности становится больше, а не меньше. Мы заранее составляем карту миграции именно для того, чтобы это окно было как можно короче, но не будем делать вид, что оно исчезает само. ### Что переносится, а что пересобирается Не всё выбрасывается. Вот что реально переживает переход на Flutter, а что пересобирается с нуля. | Слой | Переносится | Пересобирается на Flutter | | --- | --- | --- | | Бизнес-логика и правила | Да | — | | Контракты API | Да | — | | Модели данных | Да | — | | Дизайн-токены и брендбук | Да | — | | Тест-кейсы (как спецификации) | Да | — | | Экраны UI | — | Пересобирается | | Навигация | — | Пересобирается | | Управление состоянием | — | Пересобирается | | Нативные интеграции | — | Пересобирается | | CI/CD | — | Пересобирается | ### Как мы проводим миграцию #### 1. Аудит Мы изучаем вашу кодовую базу — экраны, навигацию, управление состоянием, нативные модули, поверхность API и покрытие тестами — и отмечаем, что перенести просто, а что будет по-настоящему сложно. Каждая мина — форкнутый нативный модуль, недокументированная машина состояний, контракт с бэкендом, который никто не записал, — находится здесь, а не через три спринта в разгар пересборки. #### 2. Карта миграции Аудит превращается в поэкранный план миграции: последовательность, зависимости между экранами, какие нативные модули требуют аналогов на Flutter, и реалистичный порядок, при котором приложение остаётся готовым к релизу на каждом шаге. Именно по этому документу оценивается весь остальной проект. #### 3. Поэтапная поставка Мы встраиваем Flutter в существующую оболочку и начинаем выпускать экраны в порядке приоритета — обычно начиная с чего-то низкорискового, чтобы обкатать процесс, а затем переходя к экранам, которые действительно важны. Приложение всё это время продолжает релизиться в обычном режиме; миграция идёт параллельно с обычным роадмапом, а не вместо него. #### 4. Переход Когда перенесены все экраны и старый код удалён, мы убираем оболочку-хост и выпускаем сборку полностью на Flutter. С этого момента приложение поддерживается как любая другая кодовая база на Flutter — одна команда, один тулчейн. Хотите оставить работу внутри команды? Через [расширение команды](https://ru.nerdy.pro/services/team-augmentation) наши инженеры могут влиться в вашу команду и провести миграцию под вашим управлением, а не нашим. ### Снизить риск до принятия решения Не обязательно сразу соглашаться на полную миграцию, чтобы понять, что она собой представляет. Наша услуга [аудита AI-кода](https://ru.nerdy.pro/services/ai-code-audit) проводит описанный выше этап аудита как отдельную работу с фиксированным объёмом: полный разбор текущей кодовой базы и письменный отчёт, который становится картой миграции — мины, риски нативных модулей и реалистичная последовательность, — независимо от того, продолжите вы работать с нами после этого или нет. > **Начните отсюда, если не уверены** > > Аудит — самый необязывающий способ узнать, имеет ли смысл миграция для вашего приложения, сколько она реально будет стоить и где настоящий риск, — прежде чем кто-либо напишет хоть строчку на Dart. ### Сроки и стоимость Миграция на Flutter масштабируется как пересборка, потому что это она и есть: кодовая база, которую вы получаете, — полноценное приложение на Flutter, построенное в том же объёме, что и при разработке с нуля. Что меняет поэтапный подход — это риск, а не объём: поставка по экранам растягивает календарь по сравнению с одномоментным переписыванием, но приложение при этом ни разу не уходит в офлайн, и вы можете остановиться, поставить на паузу или пересмотреть приоритеты на границе любого экрана. Мы не называем сроки и стоимость миграции без изучения приложения — диапазон слишком широк, чтобы быть полезным. Но поскольку миграция оценивается как пересборка, честным ориентиром служат опубликованные тарифы на странице [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development): приложение в объёме уровня Business — 2,7–5,4 млн ₽ при разработке с нуля — это то, к чему тяготеет миграция сопоставимого приложения, плюс этап аудита и карты миграции в начале. Как мы оцениваем саму лежащую в основе разработку — в статьях [сколько времени занимает разработка Flutter-приложения](https://ru.nerdy.pro/blog/how-long-to-build-a-flutter-app) и [что реально влияет на стоимость разработки на Flutter](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026). ### Часто задаваемые вопросы Всё, что нужно знать о миграции с React Native или натива на Flutter #### Можно ли перенести приложение на React Native на Flutter поэтапно? Да — именно так мы проводим каждую миграцию. Мы встраиваем Flutter в существующее приложение на React Native через интеграцию add-to-app и переносим экраны по одному через общий роутер. Приложение остаётся работающим и готовым к релизу всё это время; момента, когда оно уходит в офлайн ради переписывания, не бывает. #### Миграция с React Native на Flutter — это переписывание? Да, и мы говорим об этом сразу. У React Native и Flutter нет общих артефактов сборки, поэтому UI, навигация, управление состоянием и нативные интеграции пересобираются с нуля на Dart. Переносится бизнес-логика, контракты API и модели данных — в виде спецификаций, по которым строится новый код, а не в виде переиспользуемого кода. #### Сколько занимает миграция с React Native на Flutter? Зависит от размера приложения и количества нативных модулей и кастомных интеграций — единого числа, применимого ко всем приложениям, не существует. По срокам это близко к разработке эквивалентного приложения на Flutter с нуля, плюс этап аудита и карты миграции в начале. Поэтапный подход растягивает календарь по сравнению с одномоментным переписыванием — в обмен на то, что приложение никогда не уходит в офлайн. #### Сколько стоит миграция на Flutter? Миграция оценивается как пересборка клиентского приложения, потому что это она и есть. Точную цифру мы называем только после аудита конкретного приложения — сумма сильно зависит от количества нативных модулей, сложности интеграции с бэкендом и объёма кастомного UI. #### Можно ли перенести нативное iOS/Android-приложение на Flutter? Да. Тот же поэтапный подход add-to-app применим к нативным приложениям на Swift/Kotlin так же, как к React Native — инструментарий add-to-app у Flutter встраивается в существующий нативный хост тем же способом. Аудит и карта миграции будут немного другими (нет специфичных для RN рисков вроде бесконечных апгрейдов New Architecture), но модель поставки идентична. #### Что будет с нашими существующими нативными модулями? Каждый аудируется отдельно. У некоторых есть поддерживаемый аналог на Flutter или Dart FFI, и они переключаются напрямую. Другим нужна тонкая обвязка через platform channels вокруг существующего нативного кода — так он продолжает работать без переписывания. Карта миграции, полученная в результате аудита, показывает, что есть что, ещё до начала разработки. #### Стоит ли вообще переходить на Flutter? Не всегда. Если приложение на React Native стабильно, хорошо работает, а команда продуктивна, миграция — это затраты без реальной проблемы за ними, и мы скажем это прямо, а не продадим переписывание. Миграция оправдана, если вы боретесь с бесконечными апгрейдами, упёрлись в потолок производительности, который не преодолеть, несёте нагрузку по поддержке нативных модулей или не можете нанять и удержать стабильную команду. Если ничего из этого не про ваше приложение — оставайтесь как есть. ## Парные веб-приложения https://ru.nerdy.pro/services/web-development/companion-web-app Парное веб-приложение — это браузерная сторона продукта, который живёт прежде всего в мобильном приложении: место, где клиент регистрируется до установки, где расшаренная ссылка открывается у того, у кого приложения нет, где работа, начатая на телефоне, продолжается на десктопе. Это тот же продукт, на том же API, оформленный под то, что браузер действительно умеет хорошо, — а не порт приложения и не брошюра о нём. Большинство агентств делает одну из сторон. Мы выпускаем Flutter-приложения, веб-фронтенды и бэкенд-сервисы, которыми и те и другие пользуются. Это меняет инженерию, потому что на вопрос «на какой стороне должна жить эта функция» отвечает одна команда, глядящая на одну систему, а не переговоры двух подрядчиков. #### Какие продукты мы делаем - **Клиентские веб-приложения рядом с мобильными** — полный продукт в браузере там, где это оправдано, или осознанное подмножество там, где нет - **Публичные, расшариваемые страницы** — страницы, в которые разворачивается пересланная ссылка, отрендеренные и индексируемые, с передачей в приложение, когда оно установлено - **Веб-клиенты реального времени** — живые данные в браузере из той же ленты, которую потребляют приложения - **Бэк-офисы того же продукта** — операторская сторона — отдельная дисциплина, разобранная на странице [админ-панелей и дашбордов](https://ru.nerdy.pro/services/web-development/admin-dashboards) - **Веб-сторона на Nuxt** — когда парная веб-часть должна ранжироваться, машинерия для этого — наша практика [разработки на Nuxt](https://ru.nerdy.pro/services/web-development/nuxt-development) ### Кто будет делать ваше парное веб-приложение Паттерн видно на двух продуктах в продакшене: у каждого — приложение и веб на одном общем бэкенде. #### Formtastic — одна платформа, три клиента [Formtastic](https://ru.nerdy.pro/portfolio/formtastic) работает как веб-приложение на Nuxt плюс Flutter-приложения для iOS, iPad и Android — все против одного бэкенда на Django и Go. Одна и та же функция устроена по-разному на каждой стороне. Голосовой ввод в вебе записывает аудио в браузере и отправляет его на эндпоинт транскрипции, потому что бэкенд, который можно расширить, уже был; мобильные приложения несут Whisper прямо на устройстве, потому что на объектах нет связи. Одна функция, две реализации, и каждая честно считается со своей средой. Именно это суждение, больше чем владение фреймворком, и есть то, чего продукт с двумя клиентами требует на самом деле. #### ExtraETF — одна лента реального времени, все клиенты Для [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) мы построили на Go сервис на базе WebSocket, который транслирует живые рыночные данные и веб-, и мобильным клиентам: тысячи одновременных подключений, обновления сведены к той частоте, которая реально нужна человеку, следящему за ценой. Мы сделали мобильные приложения и слой реального времени; веб-клиент, который его потребляет, принадлежит команде самого клиента. Хорошо спроектированный сервис кормит и те интерфейсы, которые вы никогда не будете строить сами. ### Чего на самом деле требует продукт с двумя клиентами Четыре дисциплины удерживают приложение и веб от превращения в два разных продукта. #### Один API, честно обслуживающий обоих В момент, когда вебу нужен «всего один особый эндпоинт», у вас уже два бэкенда под одним именем. Мы проектируем API как единый контракт продукта: одна аутентификация, одни модели, одно версионирование для каждого клиента. Там, где сторонам действительно нужны разные формы данных, это различие прописывается в контракте, а не залатывается в клиенте. #### Решить, чем является веб-версия Дорогая ошибка — «приложение, только в браузере». Парная веб-часть окупает свой бюджет тем, чего приложение не умеет: быть достижимой из поисковой выдачи, мгновенно открываться по пересланной ссылке, давать клавиатуру и большой экран для серьёзного редактирования. Мы просеиваем через этот тест каждую функцию, и вычеркнутая из веб-версии функция для нас решение, которое мы отстаиваем вслух, а не дыра, обнаруженная позже. #### Передача между вебом и приложением Пересланная ссылка должна открывать приложение, когда оно установлено, веб-страницу — когда нет, и никогда — сломанную промежуточную страницу. Universal links на iOS и app links на Android, фолбэк-страницы за ними и сценарий регистрации, который начинается в браузере и продолжается в приложении. На этом шве продукты с двумя клиентами теряют пользователей заметнее всего, поэтому мы прорабатываем его намеренно, не рассчитывая, что он заработает сам. #### Рендеринг под задачу страницы Публичные, расшариваемые страницы рендерятся на сервере, чтобы краулеры и превью ссылок видели настоящий контент; рабочее пространство за логином рендерится как приложение, которым оно и является. Компромиссы разложены на пилларной странице [веб-разработки](https://ru.nerdy.pro/services/web-development), и выбор делается для каждого маршрута, а не для проекта целиком. ### Как мы работаем **Проект с фиксированным объёмом.** Мы ведём веб-сторону от определения набора функций до деплоя, против вашего существующего API или того, который построим рядом с ним. **Staff augmentation.** Наши инженеры входят в команду, которая владеет продуктом, — см. [расширение команды](https://ru.nerdy.pro/services/team-augmentation). Если мобильную сторону тоже нужно строить, это наша [практика Flutter](https://ru.nerdy.pro/services/flutter-app-development). В обоих случаях смету под задачу мы присылаем в течение двух рабочих дней после того, как разберёмся в требованиях. ### Сколько стоит парное веб-приложение Парная веб-часть оценивается по тем же опубликованным тарифам, что и страница [веб-разработки](https://ru.nerdy.pro/services/web-development): веб-приложение с авторизацией на вашем существующем API попадает в тариф веб-приложения, 1 350 000–3 600 000 ₽, а полноценная вторая сторона продукта с реальным временем, платежами или мультитенантной структурой оценивается как работа уровня SaaS — от 4 500 000 ₽. Это те же ключи, из которых рисуется таблица цен пилларной страницы, поэтому разойтись они не могут. Что двигает цифру: какая часть API уже существует и насколько чисто он спроектирован под больше чем одного клиента, какую часть продукта покрывает веб-версия и едут ли функции реального времени по существующей ленте — или её нужно строить. ### Частые вопросы Что чаще всего спрашивают команды, чей продукт живёт в приложении, а пользователи продолжают просить его в браузере. #### Наш продукт — мобильное приложение. Нужна ли нам веб-версия вообще? Не всегда. Честный ответ зависит от того, где утекает ваш рост. Веб-сторона окупает бюджет, когда регистрации начинаются с поиска или пересланных ссылок, когда пользователи просят работать на большом экране или когда каждый расшаренный кусок контента сейчас упирается в тупик для людей без приложения. Если ничего из этого не про вас, мы так и скажем: хорошо сделанной карточки в сторе и диплинков может быть достаточно. #### Должна ли веб-версия уметь всё, что умеет приложение? Обычно нет, и решить, что оставить за бортом, — большая часть работы по определению объёма. Браузер выигрывает в достижимости (поиск, пересланные ссылки, мгновенный доступ без установки) и в работе с клавиатурой на большом экране; приложение выигрывает камерой, офлайном, уведомлениями и тем, что оно в одном касании. Голосовой ввод Formtastic — образцовый пример: веб записывает в браузере и транскрибирует на сервере, приложения транскрибируют на устройстве, потому что на объектах нет связи. Одна функция, разные честные реализации, и часть функций справедливо остаётся только в приложении. #### Вы можете строить на нашем существующем бэкенде или вам нужно владеть им? Мы регулярно строим поверх существующих API: веб-фронтенд Formtastic работает на том бэкенде на Django, который у продукта уже был, расширенном там, где это понадобилось вебу. Первым делом мы оценим, действительно ли API готов к нескольким клиентам: аутентификация, которой браузер может пользоваться безопасно, модели, не предполагающие сценариев только для приложения, и версионирование, позволяющее двум клиентам развиваться. Где он не дотягивает, мы точно скажем, что менять, — и можем сделать обе стороны сами, если так быстрее. #### Может ли веб-приложение показывать те же живые данные, что и мобильное? Да, и это вопрос проектирования ленты, а не фреймворка. Для ExtraETF мы построили на Go сервис на базе WebSocket, который транслирует живые рыночные данные и веб-, и мобильным клиентам: тысячи одновременных подключений, обновления сведены примерно к одному на инструмент в секунду — частоте, которая реально нужна человеку. Спроектируйте слой реального времени один раз и правильно — и каждый нынешний и будущий клиент будет потреблять один и тот же поток. #### Как пересланная ссылка выбирает между приложением и веб-страницей? Через universal links на iOS и app links на Android: один и тот же URL открывает приложение, когда оно установлено, и веб-страницу, когда нет, — причём веб-страница несёт полный контент, а не уговоры установить приложение. Сделанная правильно, ссылка переживает и установку: человек, попавший на веб, установивший приложение и открывший его, оказывается у того контента, который ему обещали. На этой передаче продукты с двумя клиентами теряют больше всего пользователей, поэтому мы относимся к ней как к функции с собственным инженерным бюджетом. #### Сколько стоит парное веб-приложение? Веб-приложение с авторизацией на существующем API обычно стоит от 1 200 000 до 3 200 000 рублей и занимает один-три месяца: это опубликованный тариф веб-приложения на нашей странице веб-разработки. Полноценная вторая сторона продукта с данными в реальном времени, платежами или мультитенантной структурой оценивается как работа уровня SaaS — свыше 4 000 000. Главный рычаг — ваш API: спроектированный под несколько клиентов, он держит веб-разработку компактной, а рассчитанный только на приложение добавляет бэкенд-работу, которую мы явно включим в оценку. Посмотрите обе стороны в кейсе [Formtastic](https://ru.nerdy.pro/portfolio/formtastic), почитайте, как [лента реального времени ExtraETF](https://ru.nerdy.pro/blog/extraetf-realtime-fintech-flutter-go) раздаётся всем клиентам, или начните с пилларной страницы [веб-разработки](https://ru.nerdy.pro/services/web-development). ## Разработка медицинских приложений https://ru.nerdy.pro/services/flutter-app-development/healthcare Разработка медицинских приложений — это проектирование и разработка мобильных приложений, которые работают с клиническими и личными данными о здоровье: телемедицина и видеоконсультации, платформы ментального здоровья и терапии, личные кабинеты пациента, удалённый мониторинг и велнес-продукты. От обычной разработки она отличается тремя вещами: правила обращения с данными задаёт закон, а не ваша политика конфиденциальности; разрыв связи случается посреди чьей-то консультации, а не посреди ленты; заметная часть ваших пользователей — люди, которым нездоровится, пожилые или те, кто работает с приложением через вспомогательные технологии. Flutter подходит для медицины, потому что рисует интерфейс сам. Запись на приём, дневник симптомов или экран консультации ведут себя одинаково на iOS и Android из одной кодовой базы — примерно на 30–40% дешевле, чем делать тот же продукт дважды нативно. То, что напрямую касается платформы, — Apple HealthKit и Android Health Connect, Bluetooth-устройства, разрешения на камеру и микрофон, часть видео-SDK — идёт через platform channels: это рутина, но это реальная работа. #### Какие продукты мы делаем - **Телемедицина и видеоконсультации** — расписание, комната ожидания, живое видео и чат, заметки по сессии и последующее сопровождение - **Платформы ментального здоровья и терапии** — выбор специалиста, регулярные сессии и переписка между приёмами, как в [YouMi](https://ru.nerdy.pro/portfolio/youmi) - **Личные кабинеты пациента и приложения клиник** — записи, результаты, назначения, документы и напоминания поверх существующей медицинской информационной системы - **Удалённый мониторинг и ведение хронических заболеваний** — показатели с носимых и Bluetooth-устройств, динамика, пороговые значения и оповещение врача - **Велнес, фитнес и приверженность лечению** — трекинг привычек и приёма препаратов, где регуляторная нагрузка меньше, а задача удержания сложнее - **Инструменты для врачей** — графики дежурств и обходов, защищённая переписка и быстрая фиксация данных, рассчитанная на работу одной рукой и короткое окно внимания ### Кто будет делать ваше медицинское приложение Две вещи отличают команду, способную сделать медицинское приложение, от команды, которая делала просто приложения. #### Живой телемед-продукт, который нас позвали спасать [YouMi](https://ru.nerdy.pro/portfolio/youmi) — сервис онлайн-консультаций с психологом: пациент выбирает специалиста, записывается и переносит сессии, переписывается с психологом между приёмами. Когда в YouMi LLC обратились к нам, Flutter-приложение уже было в обоих сторах и не справлялось ровно с тем, ради чего существовало: сессии рвались, сообщения в чате не доходили, а утечки памяти в навигационном стеке были такими, что приложение падало при переходах между экранами. За две недели мы перестроили WebSocket-слой с полноценным жизненным циклом соединения — автоматическое переподключение, очередь сообщений при смене сети, корректная обработка ошибок, — добавили сквозные статусы доставки, чтобы и пациент, и психолог видели, что сообщение отправлено, доставлено и прочитано, и переписали навигацию на современных API роутинга Flutter. Именно из-за этого проекта надёжность сессии стоит на этой странице первым пунктом. В телемедицине соединение и есть продукт: консультация, оборвавшаяся на одиннадцатой минуте, — это не «ухудшенный опыт», а несостоявшийся приём, который стоит вам возврата денег, переноса и самого пациента. #### Реальное время и защищённая переписка, построенные не в первый раз Та же задача повторяется во всех наших проектах. [Jepta](https://ru.nerdy.pro/portfolio/jepta) — приложение для соседского общения в Германии, где за каждым чатом и каналом стоит WebSocket-слой; [Arcana](https://ru.nerdy.pro/portfolio/arcana) стримит ответы модели через server-sent events в чат, который остаётся плавным на тысячах сообщений. Свою позицию по криптографии мы записали, а не просто заявили, — см. [Шифрование простыми словами](https://ru.nerdy.pro/blog/encryption-explained), [Как на самом деле работает сквозное шифрование](https://ru.nerdy.pro/blog/how-end-to-end-encryption-works) и [Ваш SSL pinning, скорее всего, не работает](https://ru.nerdy.pro/blog/ssl-certificate-pinning-flutter). ### Что на самом деле требуют медицинские приложения В каждом медицинском проекте возникают шесть тем. Вот как мы закрываем каждую. #### Сессии, которые переживают реальную сеть Пациент выходит на консультацию с мобильного интернета, в коридоре, переключаясь с Wi-Fi на LTE посреди разговора. Видео идёт по WebRTC через SDK провайдера; чат, присутствие и состояние сессии — по WebSocket. И то и другое оборвётся, и вопрос проектирования не в том, как это предотвратить, а в том, что произойдёт дальше. Сценарии отказа конкретны, и это всегда одни и те же три: сокет переподключился, но не переподписался — приложение выглядит подключённым и ничего не получает; сообщения, отправленные офлайн, теряются вместо того, чтобы встать в очередь; клиент показывает «идёт сессия» уже после того, как сервер её закрыл. Мы считаем статус доставки полноценным элементом интерфейса — отправлено, доставлено, прочитано, ошибка, — потому что в клиническом разговоре «а он это получил?» не косметический вопрос, и тихий сбой хуже видимого. #### Данные пациентов и правила, которые ими управляют У нас нет ни аттестации HIPAA, ни сертификации SOC 2, и любой подрядчик, который продаёт сертификат вместо того, чтобы объяснить, как он строит систему, вам что-то продаёт. Обязательства HIPAA лежат на covered entity («подпадающей организации») и его business associates («партнёрах по обработке») — там, где HIPAA вообще применяется, это обычно вы, ваш хостинг-провайдер и ваш видеовендор, по подписанным соглашениям BAA (Business Associate Agreement). Наша работа — построить так, чтобы ваш комплаенс был достижим, а не превращался в переписывание: - Учётные данные и токены в iOS Keychain и Android Keystore, а не в shared preferences - TLS с пиннингом сертификатов и планом ротации, который не ломает старые клиенты - Биометрия или код-пароль на доступ к защищаемым медицинским данным (PHI), а не только на вход в приложение - Шифрованное локальное хранилище для всего, что кэшируется на устройстве, с явной политикой хранения и очисткой при выходе - Никаких медицинских данных в логах, крэш-репортах и аналитике — самая частая реальная утечка, которую мы находим - Минимум данных на устройстве, какой только позволяет продукт: данные, которых на нём не было, невозможно достать из украденного телефона GDPR добавляет свои архитектурные ограничения, а данные о здоровье относятся к особой категории по статье 9, поэтому для их обработки нужно отдельное основание — а если вы опираетесь на согласие, оно должно быть явным и отзываемым. Запрос на удаление должен быть запросом к базе, а не археологической экспедицией, и физическое расположение данных становится проектным решением, а не деталью хостинга. Такие решения принимают в начале — либо платят за них потом. #### Приём — это момент времени, а не строка Это медицинский аналог задачи о денежной арифметике из [финтеха](https://ru.nerdy.pro/services/flutter-app-development/fintech). Приём — это момент времени, о котором должны договориться два человека в двух часовых поясах. Сохраните его строкой локального времени — и рано или поздно вы сдвинете консультацию на час, когда между записью и сессией окажется переход на летнее время, или покажете врачу в Берлине слот, который пациент из Лиссабона бронировал совсем на другой час. Мы храним моменты в UTC, держим рядом исходный часовой пояс там, где этого требует правило отображения, показываем время в локальной зоне каждого участника и осознанно тестируем границы: запись, сделанная до перевода часов, на сессию после него; пациент, который переехал между этими двумя точками; напоминание, которое должно сработать в правильное локальное время на устройстве, сменившем зону после планирования уведомления. Переносы и отмены — та же задача плюс машина состояний, а обработка неявок — место, где собираются граничные случаи. #### Данные о здоровье на устройстве Показатели из Apple HealthKit, Android Health Connect и устройств с Bluetooth Low Energy — тонометров, весов, глюкометров, носимых гаджетов — доходят до Flutter-приложения через platform channels. Часть вендорских SDK поставляется с Flutter-пакетами; чисто нативные требуют обёртки — это понятный объём работы, а не риск, но он должен быть в смете. Недооценивают обычно разрешения и пропуски в данных. Медицинские разрешения гранулярные, отзываемые и на каждой платформе устроены по-разному; у фоновой доставки свои правила; а синхронизация, рассчитанная на непрерывный поток, покажет бессмыслицу в первый же раз, когда пользователь оставит часы на зарядке на двое суток. Что приложение делает с пропущенными данными — продуктовое решение, и принимать его лучше до того, как построен график. #### Доступность, которая здесь не «приятное дополнение» Медицинскими приложениями пользуются люди со слабым зрением, тремором, потерей слуха, с когнитивной нагрузкой из-за болезни, а также те, кто ухаживает за пациентом и действует от его имени. Это делает доступность функциональным требованием, а не галочкой комплаенса: динамический размер шрифта, при котором вёрстка не разваливается при 200 %, контраст, выживающий в ярко освещённой приёмной, размеры кнопок под неуверенную руку, метки для скринридера на каждом значимом элементе и субтитры к видео. Это всё чаще ещё и юридический вопрос. Европейский акт о доступности применяется к целому ряду потребительских цифровых услуг в ЕС с июня 2025 года, а на федеральные госзакупки в США распространяются требования Section 508 — попадает ли конкретный продукт под них — вопрос к вашим юристам, но инженерный ответ в обоих случаях один. Flutter кормит API доступности обеих платформ из дерева, которое строит виджет `Semantics`, так что это достижимо; просто эту работу нужно закладывать, а не обнаруживать в конце. #### Ревью в сторах, которое здесь строже, чем вы ожидаете Apple проверяет медицинские приложения по пункту 1.4.1 гайдлайнов App Review и может запросить методологию и регуляторные разрешения для любого клинического утверждения. У Google Play своя политика для health-приложений с отдельными декларациями, а у телемедицины и выписки рецептов — требования, различающиеся по странам. Это не повод не запускаться, но повод определить три вещи до того, как функцию начнут делать: что именно приложение утверждает, кто подтверждает это утверждение и в каких странах выходит приложение. Команды, которые узнают об этом на сабмите, теряют недели. ### Почему Flutter для медицины Честный аргумент за Flutter здесь: | Требование | Как это решает Flutter | | --- | --- | | iOS и Android одной командой | Одна кодовая база, примерно на 30–40% дешевле двух нативных разработок | | Одинаковый клинический интерфейс на всех устройствах | Flutter рисует свои пиксели, поэтому запись на приём или экран консультации выглядят одинаково на обеих платформах | | Надёжные видео- и чат-сессии | SDK для WebRTC и WebSocket доступны; надёжность обеспечивается продуманным переподключением, а не фреймворком | | Платформенные примитивы безопасности | Keychain, Keystore и биометрия доступны через platform channels | | HealthKit и Health Connect | Доступны через platform channels; для типовых сценариев есть проверенные пакеты сообщества | | Доступность | API доступности обеих платформ питаются от дерева `Semantics`, а динамический шрифт и контраст решаются в дизайн-системе | Экономия — это не скидка на инженерию, а снятие дублирующей работы: один сценарий записи, один сценарий согласия, один набор клинических экранов, которые тестируют дважды, а не строят дважды. Где нативная разработка всё ещё выигрывает: если ваш продукт по сути обёртка вокруг платформенной возможности — глубокий сценарий на Apple Watch или интеграция с устройством, существующая только как iOS-фреймворк, — преимущество кроссплатформенности схлопывается, и честным ответом может быть нативная разработка. Мы скажем, когда это так. Общая страница услуги — [разработка на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ### Как мы работаем **Проект с фиксированным объёмом.** Мы отвечаем за поставку целиком — дискавери, архитектура, разработка, подача в сторы. Подходит, когда вы хотите передать определённый объём и иметь команду, отвечающую за результат. Как это выглядит по неделям, описано в нашем [процессе разработки](https://ru.nerdy.pro/blog/flutter-app-development-process). **Staff augmentation.** Наши инженеры входят в вашу команду, в ваш репозиторий и ваши спринты, под вашим управлением. Подходит, когда у вас уже есть инженерное руководство и нужны дополнительные Flutter-разработчики — см. [расширение команды](https://ru.nerdy.pro/services/team-augmentation) и наш [гайд по найму Flutter-разработчиков](https://ru.nerdy.pro/blog/hire-flutter-developers-2026), где честно сказано, когда не стоит брать нас. **Спасение и стабилизация.** Иногда приложение уже существует и разваливается в проде — именно так начался проект YouMi. Такая работа оценивается по симптомам, а не по списку фич — теперь у неё есть [отдельная страница](https://ru.nerdy.pro/services/flutter-app-development/app-rescue). В любом из вариантов мы присылаем смету под задачу в течение двух рабочих дней после того, как разберёмся в требованиях. ### Сколько стоит медицинское приложение Сфокусированный первый релиз — аккаунты, запись на приём и один канал консультаций — это проект уровня Business: 2,7–5,4 млн ₽, за три-пять месяцев. Полноценная телемедицинская платформа с видеоконсультациями, отдельными приложениями для врача и пациента, интеграциями с существующей клинической системой и данными с медицинских устройств — это enterprise-работа, от от 8,1 млн ₽. Это те же опубликованные тарифы, что и на странице [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development) — вертикаль не получает второго прайс-листа. То, что двигает цифру медицинского проекта внутри этих диапазонов, вполне конкретно: поставщик видеосвязи, глубина интеграции с клиническими системами, которые вы не контролируете, и комплаенс-решения, определяющие, где могут храниться данные. Все три момента всплывают на этапе оценки — поэтому смета приходит после вопросов, а не до них. ### Частые вопросы Вопросы, которые чаще всего задают команды, делающие медицинские и телемедицинские продукты на Flutter. #### Подходит ли Flutter для медицинских приложений? Да. Flutter хорошо подходит для медицинских приложений, потому что рисует интерфейс сам: запись на приём, экран консультации и дневник симптомов ведут себя одинаково на iOS и Android из одной кодовой базы, обычно на 30–40 процентов дешевле двух нативных приложений. Он компилируется в нативный код, поэтому экран консультации остаётся плавным, пока поверх него идут видео и чат, а API доступности обеих платформ питаются от дерева, которое строит виджет Semantics, — в медицине это важнее, чем в большинстве категорий. Компромисс в том, что платформенные возможности вроде HealthKit, Health Connect, Bluetooth-устройств и части видео-SDK доступны через platform channels, а не напрямую: это рутинная, но реальная инженерная работа. #### Может ли приложение на Flutter соответствовать HIPAA? Да — ровно в той же мере, в какой это может любое приложение. Соответствие HIPAA — свойство вашей организации и вашего обращения с данными, а не UI-фреймворка, и обязательства лежат на covered entity и его business associates по подписанным соглашениям BAA: там, где HIPAA вообще применяется, это обычно вы, ваш хостинг и ваш видеовендор. Для технических мер Flutter использует те же платформенные примитивы, что и нативное приложение: iOS Keychain, Android Keystore, биометрические API и TLS с пиннингом сертификатов. Безопасность медицинского приложения определяется дисциплиной реализации. На практике мы находим одни и те же провалы: медицинские данные в логах и крэш-репортах, учётные данные в небезопасном хранилище и лишнее кэширование на устройстве — и все три встречаются в нативных кодовых базах не реже. #### Как во Flutter делают видеоконсультации? Через SDK провайдера WebRTC, а не через собственный стек WebRTC: медиатранспорт, TURN-инфраструктура и согласование кодеков — не то, куда медицинскому продукту стоит вкладывать инженерный бюджет. На качество на самом деле влияет то, что происходит вокруг звонка: комната ожидания, которая объясняет обеим сторонам, что происходит; обработка разрешений на камеру и микрофон, которая восстанавливается, если пользователь сначала отказал, а потом передумал; и поведение при смене сети посреди сессии. Консультация, оборвавшаяся на одиннадцатой минуте, — это несостоявшийся приём, поэтому переподключение проектируется с самого начала, а не добавляется после жалобы. #### Может ли приложение на Flutter читать данные Apple HealthKit и Android Health Connect? Да, через platform channels, и для типовых сценариев уже есть проверенные пакеты сообщества. Трудозатраты редко приходятся на само чтение. Они приходятся на разрешения — гранулярные, отзываемые и устроенные по-разному на каждой платформе, — на правила фоновой доставки и на то, что приложение делает с пропусками в данных: пользователю, оставившему часы на зарядке на двое суток, иначе покажут график, из которого следует клинически неверный вывод. Как отображать пропуски — продуктовое решение, которое стоит принять до того, как построен график. #### Сколько стоит разработка телемедицинского приложения? Сфокусированный первый релиз с аккаунтами, записью и одним каналом консультаций обычно занимает три-пять месяцев разработки. Полноценная телемед-платформа с видео, отдельными приложениями для врача и пациента, интеграциями в существующую медицинскую информационную систему и данными с медицинских устройств идёт дольше, потому что сроки определяются сторонними интеграциями, решениями по комплаенсу и ревью в сторах больше, чем работой над интерфейсом. Как объём, бэкенд, интеграции и комплаенс складываются в итоговую цифру, разобрано в нашем гайде по стоимости разработки на Flutter. #### Есть ли у вас сертификация HIPAA или SOC 2? Нет, и стоит с подозрением относиться к любому подрядчику, который продаёт сертификат вместо того, чтобы объяснить, как он строит систему. У HIPAA вообще нет схемы сертификации — есть аттестация и аудит на соответствие требованиям HIPAA, и относятся они к организации, обрабатывающей данные, а не к подрядчику, который пишет приложение. Мы даём архитектуру, которая держит медицинские данные вне логов, аналитики и лишнего локального хранилища, хранит учётные данные в аппаратно защищённых платформенных хранилищах и делает запрос на удаление по GDPR запросом к базе, а не раскопками, — чтобы ваш комплаенс был достижим, а не превращался в переписывание. #### Почему в медицинских приложениях доступность важнее? Из-за того, кто ими пользуется. Среди пользователей медицинского приложения — люди со слабым зрением, тремором, потерей слуха и когнитивной нагрузкой от болезни, а также те, кто ухаживает за пациентом и действует от его имени. Динамический шрифт, выдерживающий 200 процентов, контраст, работающий в ярко освещённой приёмной, размеры кнопок под неуверенную руку и метки для скринридера на каждом значимом элементе — это функциональные требования, а не галочка комплаенса. Есть и юридическое измерение: Европейский акт о доступности применяется к ряду потребительских цифровых услуг в ЕС с июня 2025 года, а на федеральные госзакупки в США распространяются требования Section 508, но инженерный ответ один и тот же — попадает ваш продукт под них или нет. Разбор стоимости — в статье [Стоимость разработки Flutter-приложения в 2026 году](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026), а как выглядит выпущенный телемед-продукт на Flutter — в [кейсе YouMi](https://ru.nerdy.pro/portfolio/youmi). ## Разработка на Nuxt https://ru.nerdy.pro/services/web-development/nuxt-development Разработка на Nuxt — это создание веб-приложений на Nuxt, full-stack фреймворке, который оборачивает Vue в то, что нужно продакшен-сайту: серверный рендеринг, роутинг по файловой структуре, серверные API-роуты, оптимизацию изображений и контентный слой для страниц на Markdown. Сайт на Nuxt отдаёт настоящий HTML уже в первом ответе, поэтому краулеры, превью ссылок и медленные телефоны получают саму страницу, а не спиннер, который её обещает. Мы не нейтральны к этому фреймворку и не притворяемся нейтральными. Этот сайт — четыре языковых домена, контентные коллекции на Markdown, генерируемые карты сайта, структурированные данные на каждой странице — это Nuxt, и последствия каждого архитектурного решения мы чувствуем на собственном поисковом трафике раньше, чем рекомендуем его кому-либо. #### Какие продукты мы делаем - **Маркетинговые и контентные сайты** — быстрые, индексируемые, редактируемые в Markdown или через CMS; это отдельный тариф на странице [веб-разработки](https://ru.nerdy.pro/services/web-development) - **Контентные платформы** — блоги, документация, многоязычные редакционные сайты, где структурированные данные и hreflang делают настоящую работу - **Продуктовые веб-приложения** — интерактивные приложения с авторизацией, чьи публичные страницы всё равно должны ранжироваться; вариант для бэк-офиса разобран на странице [админ-панелей и дашбордов](https://ru.nerdy.pro/services/web-development/admin-dashboards) - **Миграции на Nuxt** — перенос фронтенда с серверных шаблонов, WordPress или клиентского SPA без остановки продукта - **Веб-сторона мобильного продукта** — где продукт живёт в приложении, а веб — его публичное лицо; у этой пары своя страница, [парные веб-приложения](https://ru.nerdy.pro/services/web-development/companion-web-app) ### Кто будет делать ваше приложение на Nuxt Оба доказательства ниже живые, и любое из них вы можете проверить сами. #### Этот сайт, который вы прямо сейчас читаете через Nuxt nerdy.pro работает на Nuxt 4 с SSR: мультидоменная локализация на четыре языка, Nuxt Content за каждой страницей услуг и блога, генерируемые карты сайта, JSON-LD на каждой странице и Core Web Vitals, за которые мы отвечаем собственными лидами. Сказать «мы сами этим пользуемся» легко, поэтому мы делаем это проверяемым: откройте исходный код этой страницы — полный HTML уже там, без единой строки JavaScript. #### Formtastic — фронтенд, переведённый на Nuxt под нагрузкой Интерфейс [Formtastic](https://ru.nerdy.pro/portfolio/formtastic) начинался как шаблоны Django: быстрый старт, всё более дорогое развитие, каждое изменение UI завязано на бэкенд-разработчика. Мы разделили систему: Django остался API, а весь пользовательский интерфейс переехал на фронтенд на Nuxt с SSR и SSG. Разработка экранов ускорилась, нагрузка на сервер снизилась, а публичные страницы измеримо выиграли в SEO и скорости загрузки. Веб-приложение живёт по адресу app.formtastic.de. Бэк-офис нашей [платформы кэшбэка и лояльности](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform) — тоже Nuxt: серверные роуты и конвенции фреймворка окупаются даже там, где SEO вообще ни при чём. ### Чего на самом деле требует работа с Nuxt Рендеринг фреймворк даёт бесплатно. Продакшен на Nuxt — это всё вокруг него. #### Правильный способ рендеринга для каждой страницы SSR, SSG и рендеринг только на клиенте — решения на уровне маршрута, а не религия. Страница цен хочет быть статически сгенерированной и закэшированной на краю сети; дашборду за логином серверный рендеринг не нужен вовсе; контентной странице нужен SSR с заголовками кэширования, подстроенными под частоту её изменений. Мы задаём этот выбор явно для каждого маршрута, а несовпадения гидрации — классический продакшен-баг Nuxt — устраняем в корне. #### Моделирование контента до ввода контента Схема определяет, что контентный сайт умеет: какие поля есть у страницы, что валидируется на этапе сборки, что редактор может и не может сломать. Сначала мы моделируем контент: в Nuxt Content с типобезопасными схемами, когда текстами владеют разработчики, или в headless CMS, когда редакторы. Компромисс между ними разобран на странице [веб-разработки](https://ru.nerdy.pro/services/web-development). #### SEO-машинерия, которую большинство проектов пропускает Мета-теги и Open Graph, отрендеренные на сервере, канонические URL, hreflang между языками и доменами, структурированные данные JSON-LD, карты сайта, которые обновляют себя сами. Этот сайт выпускает всё это, страница за страницей, из того же кода, который мы деплоим клиентам. Ничто из этого не появляется от включённого плагина. #### Производительность как бюджет Core Web Vitals — цели, под которые мы строим: изображения, нарезанные и лениво загружаемые через собственный конвейер Nuxt, код, разбитый по маршрутам, кэширование, заданное по типу контента. Серверный рендеринг сам по себе не делает страницу быстрой; он лишь передаёт управление её скоростью вам. ### Миграция на Nuxt без остановки продукта Паттерн Formtastic обобщается. Существующий бэкенд продолжает служить API — Django, Rails, Laravel, PHP-сервисы, не важно, — а интерфейс переезжает на Nuxt маршрут за маршрутом, начиная с публичных страниц, где SSR окупается немедленно. Ни переписывания «большим взрывом», ни заморозки функций — и каждый перенесённый маршрут измеряется против своего предшественника по скорости и поиску, прежде чем поедет следующий. Если ваш фронтенд — клиентский SPA, который не ранжируется в принципе, путь тот же: меняется только фреймворк под ним. ### Как мы работаем **Проект с фиксированным объёмом.** Дискавери, модель контента, разработка, деплой, включая подключение аналитики и передачу поисковой консоли, чтобы SEO-результаты вы могли измерить сами, не полагаясь на наши слова. **Staff augmentation.** Наши инженеры входят в ваш репозиторий и спринты — см. [расширение команды](https://ru.nerdy.pro/services/team-augmentation). В обоих случаях смету под задачу мы присылаем в течение двух рабочих дней после того, как разберёмся в требованиях. ### Сколько стоит разработка на Nuxt Работа на Nuxt оценивается по опубликованным тарифам страницы [веб-разработки](https://ru.nerdy.pro/services/web-development): маркетинговый или контентный сайт — 450 000–1 080 000 ₽, веб-приложение с авторизацией — 1 350 000–3 600 000 ₽, а контентная платформа на headless CMS в руках редакторов — 1 800 000–4 500 000 ₽. Это те же опубликованные тарифы, что и в таблице цен пилларной страницы: обе рисуются из одних ключей, поэтому разойтись они не могут. Миграция стоит как тариф, в который она попадает: перенос маркетингового сайта на Nuxt — работа тарифа маркетингового сайта, перенос продуктового фронтенда с авторизацией — тариф веб-приложения; существующий API в обоих случаях остаётся на месте. ### Частые вопросы Что чаще всего спрашивают команды, рассматривающие Nuxt для нового проекта или миграции. #### Что такое Nuxt и почему его стоит выбрать? Nuxt — это full-stack веб-фреймворк на основе Vue, в котором серверный рендеринг, роутинг, серверные API-роуты и оптимизация изображений работают из коробки. Практическая причина выбрать его: страницы приходят настоящим HTML, поэтому поисковики и превью ссылок читают их без выполнения JavaScript. Это самый мощный рычаг уровня фреймворка для органического поиска. Наш собственный сайт работает на нём, так что рекомендуем мы его по опыту эксплуатации, а не по сравнительной таблице. #### Nuxt или Next.js? Это честные эквиваленты: Nuxt для Vue — то же, что Next.js для React, и оба хорошо решают серверный рендеринг, роутинг и full-stack конвенции. Решающий фактор почти никогда не фреймворк, а то, под какую экосистему ваша команда может нанимать и что сможет поддерживать. Наша экосистема — Vue, собственный бизнес мы ведём на Nuxt, и именно эту операционную глубину мы и продаём. Если ваша команда глубоко в React и нанимает под него, правильный выбор — Next.js, и мы так и скажем. #### Можете ли вы перевести наш сайт на Nuxt без переписывания с нуля? Да, это и есть паттерн Formtastic. Ваш существующий бэкенд остаётся API, а интерфейс переезжает на Nuxt маршрут за маршрутом, начиная с публичных страниц, где серверный рендеринг окупается немедленно. Без заморозки функций, без переключения «большим взрывом», и каждый перенесённый маршрут измеряется против предшественника по скорости и поиску, прежде чем поедет следующий. Дисциплина одна и та же, уходите ли вы с серверных шаблонов, WordPress или клиентского single-page приложения. #### Действительно ли переход на Nuxt улучшит наше SEO? Если ваш текущий фронтенд рендерится в браузере или привязывает каждую страницу к медленным серверным шаблонам, обычно да, и измеримо. Серверный рендеринг помещает контент, мета-теги и структурированные данные в первый же HTML-ответ, а это самое значимое решение уровня фреймворка для органического поиска. Публичные страницы Formtastic после переезда выиграли и в SEO, и в скорости загрузки. Чего Nuxt не может — компенсировать слабый контент: если проблема в текстах, а не в рендеринге, мы вам об этом скажем. #### Сколько стоит проект на Nuxt? Быстрый маркетинговый или контентный сайт обычно стоит от 400 000 до 960 000 рублей и занимает две-четыре недели, веб-приложение с авторизацией — от 1 200 000 до 3 200 000 за один-три месяца, а контентная платформа на headless CMS в руках редакторов — от 1 600 000 до 4 000 000. Это опубликованные тарифы нашей страницы веб-разработки, а не стартовая позиция для звонка с менеджером. Миграция стоит как тариф, в который попадает, поскольку существующий API остаётся. #### Когда вы отсоветуете Nuxt? Для чисто внутреннего инструмента за логином, где индексировать нечего: обычное single-page приложение на Vue проще для понимания и дешевле в эксплуатации, и наши собственные заметки о технологиях говорят ровно это. А ещё — когда ваша команда предана React: с Next.js вы продолжаете нанимать из собственной экосистемы. Nuxt оправдывает свою сложность, когда страницы должны находиться, расшариваться или быстро открываться с первого захода; когда ничего из этого не нужно, мы порекомендуем то, что проще. Прочитайте кейс [миграции Formtastic](https://ru.nerdy.pro/portfolio/formtastic), посмотрите, почему мы берём [Nuxt](https://ru.nerdy.pro/technologies/nuxt), а не чистый [Vue](https://ru.nerdy.pro/technologies/vue), когда важно SEO, или начните с пилларной страницы [веб-разработки](https://ru.nerdy.pro/services/web-development). ## Разработка приложений на Flutter https://ru.nerdy.pro/services/flutter-app-development Разработка приложений на Flutter — это когда ваши приложения для iOS и Android, а там, где это оправдано, и веб-приложение, создаются из одной кодовой базы, одной командой, на инструментарии Flutter от Google. Если сделать её хорошо, она обходится **на 30–40% дешевле двух нативных приложений**, и разрыв растёт на протяжении всей жизни продукта, потому что каждая будущая функция и каждое исправление пишутся один раз, а не дважды. Если плохо — получается приложение, которое ощущается слегка чужим на обеих платформах. Вот почему сам фреймворк значит меньше, чем то, в чьих он руках. Мы — команда, которая выпускает Flutter в продакшен с 2019 года и почти ничем другим не занимается. Семь из восьми проектов в нашем [портфолио](https://ru.nerdy.pro/portfolio) — приложения на Flutter: от живой инвестиционной платформы до парка из пятнадцати white-label-приложений, собранных из одной кодовой базы. Наши инженеры поддерживают [опенсорсные Flutter-пакеты](https://ru.nerdy.pro/open-source) на pub.dev, а наши цены опубликованы прямо на этой странице, а не озвучиваются в личной переписке — мы предпочитаем выложить цифры и спорить о них, чем начинать разговор с «зависит от». Полный разбор того, из чего они складываются, — в нашем [гайде по стоимости](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026). - **30-40%** Ниже стоимость разработки: Одна кодовая база вместо двух нативных приложений — и разрыв растёт вместе с сопровождением - **2-3x** Быстрее в оба стора: Одна команда выпускает iOS и Android одновременно - **7** Flutter-приложений выпущено: В продакшене, с кейсами, которые можно прочитать - **6** Опенсорсных пакетов: Поддерживаются нашими инженерами на pub.dev и не только ### Кто будет делать ваше Flutter-приложение Перечислить фреймворки может кто угодно. Flutter-студию от студии, которая когда-то использовала Flutter, отличает продакшен-работа, которую можно изучить, — вот наша. #### Живое инвестиционное приложение под рыночными данными в реальном времени [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) — инвестиционное приложение для Isarvest GmbH: живые рыночные котировки, отслеживание портфеля и графики, которые остаются плавными, пока котировки стримятся по WebSocket с бэкенда на Go, который мы тоже построили. Шесть месяцев до обоих сторов. Самое сложное в таком приложении — не нарисовать график, а удержать частоту кадров, когда данные приходят каждые несколько сотен миллисекунд; это задача о границах ребилдов, которую мы разобрали [в инженерных деталях](https://ru.nerdy.pro/blog/extraetf-realtime-fintech-flutter-go). #### Два нативных приложения, объединённые в одну кодовую базу [Formtastic](https://ru.nerdy.pro/portfolio/formtastic) годами существовал в виде отдельных приложений для iOS и Android — каждая функция делалась дважды, релизы расходились по времени. Мы объединили продукт в единую кодовую базу на Flutter, не нарушив работу для клиентов, которые уже на него полагались, а затем продолжили строить: поддержка iPad, офлайн-синхронизация, голосовой ввод для полей форм. Это самое наглядное «до и после», которое мы можем показать о том, что единая кодовая база делает с роадмапом, — и ниже клиент говорит об этом своими словами. #### Пятнадцать брендированных приложений из одной кодовой базы Для платформы кэшбэка и лояльности под NDA одна кодовая база на Flutter производит [парк из более чем пятнадцати полностью брендированных приложений](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform) — у каждого своё имя, тема и карточка в сторе, — плюс приложение для мерчантов и админ-панель, запускающую новый бренд без участия инженеров. Этот проект вырос в отдельное направление — [разработку white-label-приложений](https://ru.nerdy.pro/services/whitelabel-app-development) — и в [подробно описанную архитектуру](https://ru.nerdy.pro/blog/white-label-app-platform-flutter). Меньше и быстрее, но с той же дисциплиной: [Arcana](https://ru.nerdy.pro/portfolio/arcana), AI-собеседник, стримящий markdown на 60 fps через тысячи сообщений, занял три недели. [OneTwoDo](https://ru.nerdy.pro/portfolio/onetwodo), двусторонний маркетплейс услуг, — семь. [Jepta](https://ru.nerdy.pro/portfolio/jepta), приложение для соседей с чатами и каналами в реальном времени, вышло с полным набором функций меньше чем за шесть месяцев. > Долгое время Formtastic существовал в виде двух отдельных нативных приложений — одного для iOS, другого для Android. Каждую функцию приходилось делать дважды, релизы на двух платформах расходились по времени, а поддержка двух кодовых баз незаметно съедала наш роадмап. > > Команда Nerdy Production объединила всё в единую кодовую базу на Flutter, не нарушив работу продукта, на который полагаются наши клиенты. Теперь оба приложения получают одни и те же функции одновременно, опыт идентичен на iPhone и Android, а время вывода в продакшен резко сократилось. Мы поддерживаем одну кодовую базу вместо двух, и команда наконец тратит силы на новые функции, а не на синхронизацию двух приложений. *[Julian Garbotz](https://www.linkedin.com/in/julian-garbotz), Управляющий директор, Formtastic GmbH* ### Как работает Flutter: секрет его производительности Причина, по которой приложения на Flutter не несут «гибридного» штрафа к производительности, — архитектурная, и она стоит двух минут, даже если вы никогда не будете читать Dart. #### Архитектура рендеринга Flutter 1. Компиляция напрямую в нативный код В отличие от гибридных фреймворков, работающих через JavaScript-мосты, Flutter компилируется заранее в нативный машинный код ARM. Между вашим приложением и процессором нет интерпретатора, поэтому время запуска и скорость выполнения находятся в том же диапазоне, что и у приложений на Swift или Kotlin. 2. Собственный графический движок Flutter рисует каждый пиксель сам — через Skia, 2D-движок, стоящий за Chrome и Android, и его преемника Impeller на современных устройствах. Вместо сборки интерфейса из платформенных UI-компонентов он рендерит его напрямую на канвас экрана. Поэтому Flutter-приложение выглядит и ведёт себя одинаково на любой платформе, а полный контроль над каждой анимацией — поведение по умолчанию, а не борьба. 3. Декларативный UI-фреймворк Дерево виджетов Flutter перестраивает только изменившиеся части интерфейса, используя эффективный алгоритм диффа, похожий на реактовский. Сложные экраны с сотнями анимированных элементов держат 60 fps — 120 fps на поддерживающих дисплеях — при условии, что границы ребилдов заданы аккуратно; именно такая аккуратность и отличает продакшен-работу на Flutter от демо. 4. Оптимизированная слоистая архитектура Движок (C/C++) отвечает за рендеринг, компоновку текста и ввод-вывод; фреймворк (Dart) предоставляет API поверх него. Это разделение держит критичные для производительности операции на нативной скорости, а код, в котором работает ваша команда, остаётся продуктивным и тестируемым. #### Почему производительность — не проблема - Анимации 60/120 FPS: Плавные анимации на всех платформах без потерь кадров - Быстрый запуск: Приложения стартуют за миллисекунды, как нативные - Компактный размер бинарников: Разделение кода и tree shaking уменьшают размер приложения - Низкое потребление памяти: Эффективное управление памятью сохраняет отзывчивость ### Flutter vs React Native vs нативная разработка Сравниваем Flutter с другими подходами к мобильной разработке | Характеристика | Flutter (Рекомендуем) | React Native | Нативная (Swift/Kotlin) | | --- | --- | --- | --- | | Производительность | Нативная скорость | Почти нативная | Нативная | | Скорость разработки | Очень высокая | Высокая | Низкая (2 кодовые базы) | | Единообразие интерфейса | Пиксельная точность | Зависит от платформы | Нужен двойной дизайн | | Повторное использование кода | 90-95% | 70-85% | 0% (отдельные приложения) | | Hot Reload | Доли секунды | Доступно | Ограничено | | Поддержка платформ | 6 платформ | iOS, Android, Web | Только одна платформа | | Порог входа | Низкий (Dart) | Низкий (JavaScript) | Высокий (2 языка) | | Стоимость разработки | На 30-40% ниже | На 20-30% ниже | Максимальная (2 команды) | | Сопровождение | Единая кодовая база | Платформенные фиксы | Удвоенное сопровождение | | Поддерживается | Google | Meta* | Apple / Google | > **Главное:** Flutter обеспечивает нативную производительность при существенно меньших затратах на разработку и сопровождение. React Native — достойный вариант, особенно для команды, глубоко погружённой в React, — но модель рендеринга Flutter, единообразие интерфейса и охват платформ делают его более сильным выбором по умолчанию для большинства продуктов. Полный разбор компромиссов — в статьях [Flutter vs React Native в 2026 году](https://ru.nerdy.pro/blog/flutter-vs-react-native-2026) и [Flutter vs нативная разработка в 2026 году](https://ru.nerdy.pro/blog/flutter-vs-native-2026). ### Когда Flutter — неправильный выбор Рекомендация чего-то стоит, только если она может прозвучать и в другую сторону, — поэтому вот случаи, когда мы отговариваем от собственной услуги. - **Продукт — это платформенная возможность.** Глубокий сценарий для Apple Watch, продукт, построенный вокруг виджетов, приложение вокруг API, существующего только на одной платформе, — кроссплатформенная экономия здесь не главное, и честный ответ — нативная разработка. - **Ваша команда уже нативная.** Если у вас сильные инженеры на Swift и Kotlin, устоявшаяся кодовая база и нет ценового давления от двойной поставки, смена фреймворка решает проблему, которой у вас нет. Та же честность работает и в обратную сторону для [миграций](https://ru.nerdy.pro/services/flutter-app-development/flutter-migration): та страница начинается с рассказа о том, кому мигрировать не стоит. - **Вы хотите общую логику, но нативный UI.** [Kotlin Multiplatform](https://ru.nerdy.pro/technologies/kmp) делает бизнес-логику общей, сохраняя нативный интерфейс каждой платформы. Это другая ставка — больше платформенной аутентичности, меньше переиспользования UI, — и для некоторых команд она верная. Мы честно сравниваем оба подхода в посте [Flutter vs Kotlin Multiplatform в 2026 году](https://ru.nerdy.pro/blog/flutter-vs-kotlin-multiplatform-2026) — и скажем об этом на первом звонке, а не на последнем. Всё остальное — потребительские продукты, маркетплейсы, финтех, медицина, внутренние инструменты, продукты, которые должны существовать в обоих сторах при одном бюджете, — территория, где Flutter, по нашему опыту семи продакшен-приложений, выигрывает по существу. ### Что мы делаем Услуга одна; работа приходит в разных формах. Каждая из этих страниц публикует собственный объём, цифры и честные сценарии неудачи: - **[Разработка MVP](https://ru.nerdy.pro/services/flutter-app-development/mvp)** — минимальное приложение, способное ответить на ваш бизнес-вопрос, в обоих сторах за два-три месяца, по опубликованной цене. - **[Финтех-приложения](https://ru.nerdy.pro/services/flutter-app-development/fintech)** — инвестиционные, банковские и платёжные продукты: рыночные данные в реальном времени, точная денежная арифметика, комплаенс. ExtraETF — доказательство. - **[Медицинские приложения](https://ru.nerdy.pro/services/flutter-app-development/healthcare)** — телемедицина и продукты для ментального здоровья: видеоконсультации, переживающие реальные сети, и данные пациентов под дисциплиной HIPAA и GDPR. - **[E-commerce-приложения](https://ru.nerdy.pro/services/flutter-app-development/e-commerce)** — каталог, оформление заказа и платежи, от одной витрины до брендированного парка приложений; у тарифа из таблицы цен ниже наконец есть собственная страница. - **[Приложения-маркетплейсы](https://ru.nerdy.pro/services/flutter-app-development/marketplace)** — двусторонние продукты, обе роли из одной кодовой базы. OneTwoDo — доказательство. - **[Социальные и комьюнити-приложения](https://ru.nerdy.pro/services/flutter-app-development/social)** — чаты, каналы и живые ленты на одном слое реального времени. Jepta — доказательство. - **[Миграция с React Native и натива](https://ru.nerdy.pro/services/flutter-app-development/flutter-migration)** — экран за экраном, add-to-app, без даунтайма — и страница, которая начинается с рассказа о том, когда этого делать не стоит. - **[Интеграция add-to-app](https://ru.nerdy.pro/services/flutter-app-development/add-to-app)** — Flutter внутри вашего существующего нативного приложения: новые функции сразу на обеих платформах, без обязательства мигрировать. - **[Спасение и производительность](https://ru.nerdy.pro/services/flutter-app-development/app-rescue)** — аудит, работа с частотой кадров и стабилизация Flutter-приложения, которое сбоит в продакшене; работа оценивается по симптомам, а не по списку фич. - **[White-label-платформы](https://ru.nerdy.pro/services/whitelabel-app-development)** — одна кодовая база, парк брендированных приложений и ловушки гайдлайна Apple 4.2.6, исключённые архитектурой с самого начала. - **[Из чат-бота в приложение](https://ru.nerdy.pro/services/chatbot-to-app)** — Telegram- или WhatsApp-бот с реальной аудиторией, превращённый в приложение в сторах без её потери. - **[Расширение команды](https://ru.nerdy.pro/services/team-augmentation)** — senior Flutter-инженеры в вашей команде, в вашем репозитории и ваших спринтах, когда вам нужны руки, а не поставка под ключ. Если приложение уже существует и вопрос в его состоянии, а не в роадмапе — передача от агентства, прототип, собранный ИИ, кодовая база, в которой вы сомневаетесь, — начните с [аудита AI-кода](https://ru.nerdy.pro/services/ai-code-audit). ### Как мы работаем **Проект с фиксированным объёмом.** Мы отвечаем за результат целиком: дискавери, дизайн, архитектура, разработка, подача в сторы. Одна команда, отвечающая за дату запуска. Как это выглядит по неделям, описано в нашем [процессе разработки](https://ru.nerdy.pro/blog/flutter-app-development-process), а поэтапный таймлайн — в статье [Сколько времени занимает разработка Flutter-приложения?](https://ru.nerdy.pro/blog/how-long-to-build-a-flutter-app) **Staff augmentation.** Наши инженеры присоединяются к вашим, под вашим управлением. Лучший вариант, когда инженерное руководство уже есть и нужна глубина во Flutter, — наш [гайд по найму Flutter-разработчиков](https://ru.nerdy.pro/blog/hire-flutter-developers-2026) честно говорит, когда это лучше агентского контракта, а когда нет. Какой бы путь ни подошёл, смету под задачу мы присылаем в течение двух рабочих дней после того, как разберёмся в требованиях, — а инженерные практики, которые мы отказываемся урезать при любом бюджете, везде одни и те же: CI/CD с первой недели, тесты там, где ошибка стоит денег, и границы модулей, позволяющие v2 расти без переписывания. ### Стоимость разработки приложений на Flutter Прозрачные цены на ваш проект на Flutter #### MVP Быстрая проверка идеи **1,4–2,7 млн ₽** 2-3 месяца - Приложения для iOS и Android - 5-8 ключевых функций - Базовый UI/UX-дизайн - Интеграция REST API - Аутентификация пользователей - Базовая аналитика - Публикация в сторах [Начать](https://ru.nerdy.pro/contact?intent=flutter-app-development) #### Бизнес-приложение Полнофункциональное решение **2,7–5,4 млн ₽** 3-5 месяцев - iOS, Android и Web - 15-20 функций - Индивидуальный UI/UX-дизайн - Расширенные API-интеграции - Push-уведомления - Офлайн-режим - Аналитика и мониторинг - Админ-панель [Начать](https://ru.nerdy.pro/contact?intent=flutter-app-development) #### E-commerce / Retail (Популярно) Продавайте онлайн стильно **4,5–8,1 млн ₽** 4-6 месяцев - Все платформы + десктоп - Каталог и поиск товаров - Корзина и оформление заказа - Интеграция платежей - Отслеживание заказов - Отзывы и рейтинги - Управление складом - Панели администратора и продавцов [Начать](https://ru.nerdy.pro/contact?intent=flutter-app-development) #### Enterprise / AI Сложные и масштабируемые решения **от 8,1 млн ₽** 6+ месяцев - Все платформы - Интеграция AI/ML - Сложные бизнес-процессы - Корпоративная безопасность - Индивидуальная серверная инфраструктура - Расширенная аналитика - Мультитенантная архитектура - Выделенная поддержка [Начать](https://ru.nerdy.pro/contact?intent=flutter-app-development) > **Возможен индивидуальный расчёт стоимости** > > Эти диапазоны основаны на типичных требованиях, но двух одинаковых проектов не бывает. После обсуждения ваших задач, сроков и сложности мы подготовим подробную смету. Свяжитесь с нами для бесплатной консультации и точного расчёта стоимости. Что именно влияет на стоимость приложения — в материалах [Стоимость разработки Flutter-приложения в 2026 году](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026) и [кейсы снижения затрат на мобильную разработку](https://ru.nerdy.pro/blog/mobile-cost-reduction-case-studies). ### Часто задаваемые вопросы Всё, что нужно знать о наших услугах разработки на Flutter #### Что такое Flutter и почему стоит выбрать его для моего приложения? Flutter — это опенсорсный UI-инструментарий от Google для создания нативно компилируемых приложений для мобильных устройств, веба и десктопа из единой кодовой базы. Бизнес-аргумент структурный: одна кодовая база означает одну команду, один релизный цикл и каждую функцию, написанную один раз, — поэтому разработка на Flutter обычно обходится на 30–40 процентов дешевле двух нативных приложений, и разрыв растёт на протяжении жизни продукта по мере накопления затрат на сопровождение. Инженерный аргумент в том, что Flutter компилируется в нативный код и рисует интерфейс сам, поэтому избегает и штрафа к производительности гибридных фреймворков, и расхождения платформ при поддержке двух реализаций. #### Сколько стоит разработка приложения на Flutter? Наши опубликованные тарифы: от $15,000 до $30,000 за MVP, от $30,000 до $60,000 за полноценное бизнес-приложение, от $50,000 до $90,000 за e-commerce-продукт и от $90,000 за enterprise-проекты — точные диапазоны находятся в разделе цен на этой странице, а не за созвоном с отделом продаж. Цифру двигают в первую очередь объём и во вторую — интеграции. Полный разбор того, из чего складывается сумма, включая затраты, которые команды забывают заложить в бюджет, — в нашем гайде по стоимости разработки Flutter-приложения в 2026 году. #### Сколько времени занимает разработка приложения на Flutter? MVP занимает два-три месяца от старта до живой карточки в сторе. Полноценное бизнес-приложение — три-пять месяцев, e-commerce- или enterprise-проект — четыре-шесть и больше. Календарь редко съедает сам код: платёжные потоки, всплывающие посреди проекта, раунды дизайна, доступ к сторонним API и цикл подачи в сторы — вот где на самом деле сдвигаются сроки. Наш поэтапный разбор — в статье «Сколько времени занимает разработка Flutter-приложения». #### Могут ли приложения на Flutter работать так же быстро, как нативные? Да. Flutter компилируется заранее в нативный ARM-код и рендерит через собственный графический движок, поэтому анимации держат 60 кадров в секунду — 120 на поддерживаемых дисплеях — без накладных расходов на мост, которые несут гибридные фреймворки. На Flutter работают продакшен-приложения вроде Google Ads, Alibaba и автомобильного приложения BMW, а наш собственный ExtraETF держит частоту кадров под живым потоком рыночных данных. Честная оговорка: производительность — свойство инженерии, а не фреймворка. Ребилды виджетов без заданных границ уронят кадры во Flutter точно так же, как утёкшие обсерверы — в Swift. #### Чем Flutter отличается от React Native? Flutter компилируется в нативный код и рисует интерфейс сам через собственный движок рендеринга; React Native работает через мост с нативными компонентами каждой платформы. На практике это даёт Flutter больший запас производительности, попиксельно идентичный интерфейс на всех платформах, 90–95 процентов переиспользования кода против 70–85 у React Native и шесть поддерживаемых платформ, включая десктоп. React Native остаётся достойным выбором для команд, глубоко погружённых в React. Наше полное сравнение, включая случаи, где выигрывает React Native, — в статье «Flutter vs React Native в 2026 году». #### Хорош ли Flutter для веб- и десктоп-приложений? С оговорками. Flutter для веба сильнее всего в сценариях, похожих на приложение, — дашборды, порталы, инструменты за логином, — где рендеринг на канвасе является преимуществом, а не издержкой; для контентных сайтов, живущих за счёт SEO и мгновенной первой отрисовки, лучше подходит классический веб-стек, и мы говорим это, хотя продаём и то и другое. Поддержка десктопа — Windows, macOS и Linux — готова к продакшену и особенно хороша для внутренних инструментов и приложений-компаньонов, разделяющих кодовую базу с мобильными. #### Можете ли вы взять на себя или спасти существующее Flutter-приложение? Да — часть нашей лучше всего задокументированной работы началась именно так, включая живой телемедицинский продукт, который мы стабилизировали, не выводя его из продакшена. Передача начинается с аудита кодовой базы: архитектура, управление состоянием, покрытие тестами и конкретные симптомы, из-за которых вы к нам обратились. Вы получаете письменный отчёт с находками и план по шагам до любых разговоров о пересборке, потому что иногда ответ — две недели точечных исправлений, а не переписывание; и когда это так, мы так и говорим. #### Вы делаете и бэкенд, а не только приложение? Да. ExtraETF работает на бэкенде на Go, который мы построили для раздачи рыночных данных в реальном времени; платформа Formtastic включает бэкенд и веб наряду с приложениями; а для MVP мы осознанно берём управляемые бэкенды вроде Firebase, когда писать свой с нуля означало бы тратить ваш бюджет на инфраструктурную рутину. Выбор между собственным и управляемым — инженерное решение, которое мы принимаем открыто на этапе оценки, потому что оно двигает и цену, и сроки. #### Останется ли Flutter надёжной ставкой через пять лет? Настолько надёжной, насколько в этой индустрии вообще бывают ставки. Flutter разрабатывается Google в открытую, регулярно выпускает стабильные релизы, лежит в основе критичных для выручки приложений самой Google вроде Google Ads и имеет одно из крупнейших кроссплатформенных сообществ и экосистем пакетов. Ни один выбор фреймворка не свободен от риска — поэтому более прочная страховка — качество кода: хорошо структурированное Flutter-приложение с ясными границами модулей сохраняет бизнес-логику переносимой, что бы индустрия ни сделала дальше. #### Для каких отраслей вы делаете приложения на Flutter? Глубже всего мы работаем в финтехе — инвестиционные, банковские и платёжные продукты — и в медицине, включая телемедицину; у каждой вертикали своя страница услуги с комплаенс- и инженерной спецификой. Помимо них мы выпускали маркетплейсы, социальные и комьюнити-продукты, платформы лояльности и потребительские приложения на базе ИИ. Через все эти проекты переносится продакшен-дисциплина: работа с данными в реальном времени, офлайн-поведение, доступность и требования ревью в сторах. #### Как начать проект с вами? Расскажите через контактную форму, что вы делаете, и приложите всё, что уже существует, — спецификацию, прототип, приложение, уже опубликованное в сторах. Мы вернёмся с вопросами, а не с презентацией, и как только разберёмся в требованиях, вы получите смету под задачу в течение двух рабочих дней: что входит, что явно не входит, сколько это стоит и сколько времени займёт. Если Flutter — не тот инструмент для вашего продукта, именно в этом разговоре мы вам об этом скажем. Начните с цифр: [сколько стоит Flutter-приложение в 2026 году](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026), [сколько времени занимает разработка](https://ru.nerdy.pro/blog/how-long-to-build-a-flutter-app) и [во что обходится сопровождение после запуска](https://ru.nerdy.pro/blog/flutter-app-maintenance-cost). Или начните с работ: кейсы [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) и [Formtastic](https://ru.nerdy.pro/portfolio/formtastic) — самое близкое к тому, чтобы увидеть нас посреди проекта. *Meta Platforms Inc. признана экстремистской организацией, её деятельность запрещена на территории РФ. ## Разработка приложений-маркетплейсов https://ru.nerdy.pro/services/flutter-app-development/marketplace Разработка приложений-маркетплейсов — это инженерия двусторонних продуктов: приложений, где одна группа что-то предлагает, а другая это находит — услуги, аренда, подработки, вещи с рук, помощь по запросу. От обычной разработки она отличается одним структурным свойством: вы выпускаете два продукта, которые должны запуститься как один. Каждая функция существует дважды под разными углами — объявление создаёт одна сторона, а просматривает другая, бронирование запрашивает один, а подтверждает другой, — и продукт работает только тогда, когда работают оба опыта. Flutter подходит этой форме по прямолинейной экономической причине: маркетплейс и так несёт двойную продуктовую поверхность, и платить сверху ещё и двойную платформенную цену — ровно так двусторонние бюджеты и умирают. Одна кодовая база покрывает сценарии покупателя и продавца и на iOS, и на Android — а там, где роли расходятся настолько, что становятся отдельными приложениями, оба всё равно собираются из одного кода, ровно так же, как это делает наш [white-label-парк](https://ru.nerdy.pro/services/whitelabel-app-development). #### Какие продукты мы делаем - **Маркетплейсы локальных услуг** — предложения и те, кто их ищет, в одном районе, как в [OneTwoDo](https://ru.nerdy.pro/portfolio/onetwodo), который мы спроектировали и выпустили под ключ - **On-demand-продукты** — запрос, матчинг, выполнение: уборка, ремонт, сценарии в духе доставки - **Маркетплейсы аренды и бронирования** — инвентарь с календарями, доступностью и проблемами двойного бронирования, которые они приносят - **Peer-to-peer-товары** — объявления, предложения и чат, где происходит сама сделка - **B2B- и нишевые маркетплейсы** — где предложение курируется, а онбординг одной из сторон — операционная задача, которую приложение должно поддерживать ### Кто будет делать ваш маркетплейс #### Живой маркетплейс, спроектированный и выпущенный за семь недель [OneTwoDo](https://ru.nerdy.pro/portfolio/onetwodo) — двусторонний маркетплейс локальных услуг для Perform Connect Studios S.L.: уборка, ремонт, сантехника и многое другое; объявления размещают исполнители, а просматривают соседи. Мы сделали продукт целиком: дизайн, Flutter-клиент и всё, на чём он работает. Мультивалютные цены, локализация на уровне объявления и лента, отфильтрованная по языку и локации, вышли в пределах тех же семи недель, в 2024 году. Этот проект — ещё и наглядный пример честного скоупинга маркетплейса. Двусторонний продукт обычно подразумевает бэкенд-команду раньше всего остального; OneTwoDo вышел без написания этого слоя — Firebase для аккаунтов, объявлений, фотографий и аналитики, — что и позволило одной команде нести дизайн, клиент и бэкенд одновременно. Подходит ли такая архитектура *вашему* маркетплейсу — вопрос скоупинга, на который мы отвечаем до сметы, потому что он двигает и цифру, и календарь. [Страница про MVP](https://ru.nerdy.pro/services/flutter-app-development/mvp) рассказывает ту же историю с точки зрения валидации. #### Смежный опыт: ленты, чат, реальное время Маркетплейс — это обычно лента плюс разговор плюс транзакция. Каждую из этих частей мы уже доводили до продакшена: лента с учётом геопозиции, чаты и каналы реального времени в [Jepta](https://ru.nerdy.pro/portfolio/jepta), стриминговый чат [Arcana](https://ru.nerdy.pro/portfolio/arcana), держащий тысячи сообщений при 60 fps, и платёжная дисциплина, описанная на нашей [странице про финтех](https://ru.nerdy.pro/services/flutter-app-development/fintech), — под руководством основателя, который вёл карточный процессинг на масштабе. ### Что на самом деле требуют приложения-маркетплейсы #### Холодный старт — продуктовое решение, которому приложение должно служить Каждый маркетплейс открывается пустым, и приложение либо помогает, либо делает хуже. Помощь конкретна: каталог, которым можно пользоваться ещё до критической массы (курируемые категории вместо голой строки поиска), онбординг стороны предложения настолько лёгкий, что исполнитель публикует объявление за минуты с телефона, и географический фокус, встроенный в модель данных, — выиграть один район лучше, чем быть пустым везде; именно поэтому лента OneTwoDo фильтруется по локации и языку на уровне запроса, а не как довесок. #### Две роли, одна кодовая база — решение принимается осознанно Одно приложение с переключателем ролей или два приложения из общего кода? Ответ свой у каждого продукта: переключатель ролей — когда большинство пользователей со временем могут делать и то и другое (peer-to-peer-товары), отдельные приложения — когда сторона исполнителя является рабочим инструментом со своими процессами (on-demand-флоты). Машинерию для обоих вариантов мы выпускали: потребительский парк кэшбэк-платформы и её отдельное приложение мерчанта собираются из одной кодовой базы. Неизменно одно: обе стороны специфицируются вместе, потому что каждая функция маркетплейса — это один сценарий, пересекающий два экрана, принадлежащие разным людям. #### Поиск и матчинг — это и есть продукт Покупатель судит о маркетплейсе по тому, показывает ли первый экран что-то релевантное. Это серверная работа — индексированный поиск, фильтры, отражающие то, как предложение реально описано, ранжирование, балансирующее свежесть, близость и качество, — поданная через клиент, который держит позицию скролла стабильной при пагинации и рисует карточки с тяжёлыми изображениями без рывков на среднем железе. Механика каталога пересекается с нашей [страницей про e-commerce](https://ru.nerdy.pro/services/flutter-app-development/e-commerce); разница в том, что инвентарь маркетплейса хаотичен, создан пользователями и всегда отчасти устарел — и UX обязан это переваривать. #### Машинерия доверия: отзывы, профили, чат Незнакомцы совершают сделки, когда приложение даёт им на это причины. Верифицированные профили, отзывы, устойчивые к мести и спаму, и чат внутри приложения, где реально происходит сделка, — с фотографиями, предложениями и достаточной структурой, чтобы поддержка потом могла восстановить, что пошло не так. Чат — это инженерия реального времени (набор текста, доставка, переподключение поверх WebSocket), а увод разговоров с платформы — утечка, которую нужно закладывать в дизайн, а не правило модерации, которое починится само. #### Платежи, выплаты и деньги между ними Деньги маркетплейса сложнее денег магазина: платёж приходит, комиссия удерживается, выплата уходит, а спор может случиться в любой точке между ними. Выбор провайдера здесь важнее интерфейса — рельсы маркетплейс-класса вроде Stripe Connect существуют ровно для того, чтобы сценарии в духе эскроу и онбординг продавцов (с его обязательствами по KYC) были комплаенс-проблемой провайдера, а не вашей. Суммы — целые числа в наименьших единицах валюты от края до края, по [правилу, которое не гнётся](https://ru.nerdy.pro/blog/floating-point-and-money). И оба стора не берут комиссии с физических товаров и офлайн-услуг — но в момент, когда вы продаёте цифровые предметы, включаются правила встроенных покупок, и эту линию стоит провести на скоупинге, а не на ревью. #### Ревью сторов при пользовательском контенте Объявления, фотографии, чат и отзывы делают маркетплейс UGC по определению, и гайдлайн Apple 1.2 требует полного аппарата: модерация контента, механизм жалоб, блокировка пользователей и опубликованные правила. Команды обнаруживают это в последнюю неделю и выходят с опозданием; мы закладываем это в объём — вместе с удалением аккаунта и декларациями о данных, которые требуют оба стора. ### Почему Flutter для маркетплейса | Требование | Как это решает Flutter | | --- | --- | | Две роли × две платформы | Одна кодовая база там, где натив означал бы поверхность на четыре сборки | | Оба стора на запуске | Предложение и спрос редко делят одну платформу; без одного из сторов двусторонняя воронка режется вдвое | | Ленты с тяжёлыми изображениями на дешёвых телефонах | Компиляция в нативный код держит сетки объявлений плавными на среднем Android | | Чат, который ощущается мгновенным | Слой реального времени, построенный один раз, обслуживает обе роли — проверено на Jepta и Arcana | | v1, которую можно итерировать еженедельно | Hot reload быстрее секунды, пока вы узнаёте, что на самом деле нужно вашей ликвидности | Где нативная разработка всё ещё выигрывает: если продукт по сути — возможность одной платформы (сценарий, начинающийся с App Clip, глубокая привязка к платформенному железу), кроссплатформенная экономия не главное, и мы так и скажем. Общая страница услуги — [разработка на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ### Как мы работаем **Проект с фиксированным объёмом.** Мы полностью отвечаем за поставку — дискавери, дизайн, архитектура, разработка, публикация в сторах. OneTwoDo — это ровно эта модель. Наш [процесс разработки](https://ru.nerdy.pro/blog/flutter-app-development-process) описывает её неделя за неделей. **Staff augmentation.** Наши инженеры входят в вашу команду, в ваш репозиторий и спринты — см. [расширение команды](https://ru.nerdy.pro/services/team-augmentation). В обоих случаях смету под задачу мы присылаем в течение двух рабочих дней после того, как разберёмся в требованиях. ### Сколько стоит приложение-маркетплейс Первый релиз, доказывающий ликвидность, — объявления, поиск, профили, чат или бронирование, один платёжный сценарий — это, как правило, проект от MVP до уровня Business: от 1,4–2,7 млн ₽ за лёгкую валидационную сборку на managed-бэкенде вроде OneTwoDo до 2,7–5,4 млн ₽ за три-пять месяцев, когда продукту нужны собственный бэкенд, отзывы и сценарии выплат. Отдельные приложения исполнителей, платёжные рельсы маркетплейс-класса с эскроу или операционный инструментарий уводят в enterprise-работу: от от 8,1 млн ₽. Это те же опубликованные тарифы, что и на странице [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development) — вертикаль не получает второго прайс-листа. Что двигает цифру маркетплейса внутри них: две роли — это одно приложение или два, насколько умный матчинг реально нужен v1 и где между «ссылкой на Stripe-чекаут» и «удержанными средствами с выплатами» находится ваш денежный поток. ### Частые вопросы Что чаще всего спрашивают основатели, которые строят двусторонние продукты на Flutter. #### Подходит ли Flutter для приложений-маркетплейсов? Да, и аргумент структурный: маркетплейс и так удваивает вашу продуктовую поверхность, потому что каждая функция существует и для стороны предложения, и для стороны спроса, — а Flutter не даёт вам платить сверху ещё и двойную платформенную цену. Одна кодовая база покрывает сценарии покупателя и продавца на iOS и Android, ленты объявлений с тяжёлыми изображениями остаются плавными на средних устройствах, потому что Flutter компилируется в нативный код, а слой чата реального времени, нужный маркетплейсу, строится один раз. Мы спроектировали и выпустили OneTwoDo, живой двусторонний маркетплейс услуг, за семь недель ровно на этом стеке. #### Сколько стоит разработка приложения-маркетплейса? Лёгкий первый релиз на managed-бэкенде начинается с нашего опубликованного тарифа MVP, маркетплейс с собственным бэкендом, отзывами и сценариями выплат — это проект уровня Business за три-пять месяцев, а отдельные приложения исполнителей или платёжные рельсы в духе эскроу оцениваются как enterprise-работа — диапазоны опубликованы на этой странице и на странице разработки на Flutter, а не называются кулуарно. Три вопроса, которые двигают цифру сильнее всего: одно приложение или два, насколько умным должен быть матчинг на запуске и насколько глубоко идёт денежный поток. #### Покупатель и продавец — одно приложение или два? Зависит от того, что такое сторона продавца — идентичность или рабочее место. Переключатель ролей внутри одного приложения подходит продуктам, где многие пользователи со временем делают и то и другое, как в peer-to-peer-товарах. Отдельные приложения подходят продуктам, где исполнители работают в приложении весь день и им нужны процессы, которых покупатели никогда не видят, как в on-demand-флотах. Оба приложения всё равно собираются из одной кодовой базы на Flutter — мы ведём парк из пятнадцати потребительских приложений плюс отдельное приложение мерчанта из одной кодовой базы, поэтому второе приложение — это результат сборки, а не второй проект. #### Как вы решаете проблему пустого маркетплейса? Холодный старт выигрывают операции и фокус, но именно приложение решает, накапливаются ли эти усилия. Конкретно: онбординг исполнителя настолько лёгкий, что предложение публикуется с телефона за минуты, каталог, полезный ещё до критической массы, — курируемые категории вместо пустой строки поиска, и географический фокус, встроенный в модель данных, чтобы продукт мог выигрывать по одному району за раз. Лента OneTwoDo фильтруется по локации и языку на уровне запроса ровно по этой причине. #### Как работают платежи и выплаты в маркетплейсе? Через платёжные рельсы маркетплейс-класса, а не через обычное оформление заказа: деньги приходят от покупателя, комиссия удерживается, выплата уходит продавцу, и спор может прервать любой шаг. Провайдеры вроде Stripe Connect существуют, чтобы онбординг продавцов, удержанные средства и связанные с ними обязательства по KYC были комплаенс-проблемой провайдера, а не вашей, и ранний выбор этих рельсов стоит больше, чем любой объём работы над интерфейсом оформления заказа. В приложении суммы — целые числа в наименьших единицах валюты от края до края, а состояние платежа принадлежит серверу и никогда не додумывается клиентом. #### Что требует ревью App Store от маркетплейса? Относитесь к маркетплейсу как к пользовательскому контенту, потому что объявления, фотографии, чат и отзывы делают его таковым. Гайдлайн Apple 1.2 ожидает метод модерации, механизм жалоб, возможность блокировать пользователей и опубликованные правила — приложение без них может быть отклонено независимо от того, насколько отполировано всё остальное. Добавьте универсальные требования — удаление аккаунта и декларации о данных — и это становится полноценным пакетом работ. Мы закладываем его в объём с самого начала, и это одна из причин, почему наши сабмиты — фаза, а не лотерея. Прочитайте [кейс OneTwoDo](https://ru.nerdy.pro/portfolio/onetwodo) о полной семинедельной разработке, [разбор сроков](https://ru.nerdy.pro/blog/how-long-to-build-a-flutter-app) о том, куда уходит календарь, или начните с пилларной страницы [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ## Разработка социальных и комьюнити-приложений https://ru.nerdy.pro/services/flutter-app-development/social Разработка социальных и комьюнити-приложений — это инженерия продуктов, где контент создают сами пользователи: чаты и групповая переписка, каналы и форумы, ленты постов и комментариев, соседские и тематические сообщества. От обычной разработки она отличается двумя структурными вещами. Контент живой — сообщение, реакция, новый пост должны появиться сейчас, а не при следующем обновлении, — поэтому под всем лежит слой реального времени. И контент принадлежит другим людям, а это приносит модерацию, блокировки и обязательства перед ревью сторов, которые продуктовые планы регулярно обнаруживают слишком поздно. Flutter хорошо справляется с этой категорией: списки чатов, ленты и экраны с тяжёлыми медиа — ровно тот кастомный, насыщенный анимацией интерфейс, который он рисует одинаково на iOS и Android из одной кодовой базы, — а сообщество, запустившееся на одной платформе, урезало свой сетевой эффект вдвое, что делает оба стора при одном бюджете структурной необходимостью, а не экономией. #### Какие продукты мы делаем - **Комьюнити-платформы** — чаты, каналы, посты, комментарии и лента, как в [Jepta](https://ru.nerdy.pro/portfolio/jepta), выпущенной целиком за шесть месяцев - **Продукты вокруг переписки** — где разговор и есть продукт, от групповых чатов до ИИ-компаньонов вроде [Arcana](https://ru.nerdy.pro/portfolio/arcana) - **Сети по интересам и по соседству** — сообщества, ограниченные локацией или темой, с локальными лентами - **Комьюнити-слои внутри других продуктов** — чат, отзывы или форум при маркетплейсе, медицинском продукте или бренде — см., как это стыкуется с работой над [маркетплейсами](https://ru.nerdy.pro/services/flutter-app-development/marketplace) и [медициной](https://ru.nerdy.pro/services/flutter-app-development/healthcare) ### Кто будет делать ваше комьюнити-приложение #### Полноценная соседская платформа, выпущенная целиком [Jepta](https://ru.nerdy.pro/portfolio/jepta) — гиперлокальное комьюнити-приложение для Netgineers GmbH: соседские чаты, каналы бизнеса, посты, комментарии и лента, которая перестраивается вокруг того места, где стоит пользователь. Она вышла в оба стора из одной кодовой базы на Flutter, уложившись в шестимесячное окно, с полным набором функций в первом релизе — потому что слой реального времени, навигационный граф и подход к состоянию были построены один раз и переиспользовались, и каждая дополнительная поверхность оставалась небольшим изменением, а не новой интеграцией. Для продукта, чей роадмап — «больше поверхностей», именно эта архитектура и накапливает выигрыш. #### Производительность чата на пределе разумного [Arcana](https://ru.nerdy.pro/portfolio/arcana) — чат, держащий тысячи стриминговых markdown-сообщений при 60 fps: три недели разработки и самый тяжёлый случай рендеринга, который встречает интерфейс переписки. [YouMi](https://ru.nerdy.pro/portfolio/youmi) добавила другую дисциплину: чат внутри телемедицинского продукта, где оборванный разговор — не досадная мелочь, а сорванный приём. На этих трёх проектах чат-стек — транспорт, состояние, рендеринг, переподключение — это мышца, которую мы тренировали многократно, а не функция, которую мы впервые строили бы на вашем продукте. > Больше всего меня беспокоили шесть месяцев на приложение такого охвата. Jepta — это соседские чаты, каналы бизнеса, посты, комментарии и лента, которая перестраивается вокруг того места, где вы сейчас находитесь: длинный список экранов, фиксированная дата и трезвое ожидание, что половина уедет во второй релиз. > > Команда Nerdy Production сначала построила общий фундамент — один слой реального времени, одну модель навигации, — и после этого каждый новый экран стал дешёвым. Мы вышли на iOS и Android с полным набором функций, чат надёжно работает с первого дня, а дорабатывать приложение с тех пор — небольшая задача, а не проект. Мы получили тот продукт, который заказывали, и к той дате, к которой заказывали. *[Evgeny Syrtsov](https://www.linkedin.com/in/evgeny-syrtsov/), Генеральный директор, Netgineers GmbH* ### Что на самом деле требуют комьюнити-приложения #### Один слой реального времени, а не по одному на функцию Чат, присутствие, реакции, живые счётчики комментариев — наивная сборка даёт каждому собственную обвязку и рушится под весом сопровождения. Долговечная сборка — это один слой WebSocket с одной политикой переподключения, одним правилом упорядочивания сообщений и одним местом, где есть ответ на вопрос «что происходит, когда сокет обрывается посреди скролла», — ровно так была устроена Jepta, и поэтому её послерелизные экраны остались дешёвыми. Транспортные решения — как схлопываются обновления, что и как часто серверу разрешено пушить — важнее всего, что есть в дереве виджетов: та же дисциплина веерной рассылки, которую наш [разбор ExtraETF](https://ru.nerdy.pro/blog/extraetf-realtime-fintech-flutter-go) описывает для рыночных данных. #### Лента — это политика упорядочивания, одетая в интерфейс Хронологическая, ранжированная или — как в Jepta — перестраивающаяся вокруг того, где пользователь физически находится: политика ленты — это характер продукта, и исполнять её нужно на уровне запроса, чтобы она оставалась корректной при пагинации, pull-to-refresh и элементах, прилетающих, пока пользователь скроллит. Задача клиента — впитывать живые вставки, не дёргая позицию скролла, держать ячейки с тяжёлыми медиа плавными на среднем железе и корректно деградировать, когда деградирует сеть. #### Уведомления, которые информируют, не выжигая Пуш — это сердцебиение комьюнити-продукта и его самый быстрый путь к удалению приложения. Инженерная половина неказиста и обязательна: настройки на уровне разговора и канала, дайджесты («12 новых сообщений», а не двенадцать отдельных уведомлений), диплинки, приземляющиеся на нужное сообщение, и счётчики на бейдже, согласные с реальностью. Половина про сдержанность — это продуктовый дизайн, и мы возражаем, когда план уведомлений читается как схема выжимания вовлечённости: такая схема оплачивается удержанием — метрикой, которой сообщество на самом деле живёт. #### Модерация — требование запуска, а не функция роста Сторы заставляют выучить то, что сообщества иначе узнают болезненно: пользовательский контент требует метода модерации, способа пожаловаться на контент, способа заблокировать пользователя и опубликованных правил — без них Apple отклоняет по гайдлайну 1.2. Помимо прохождения ревью, машинерия должна существовать операционно: жалобы, попадающие в очередь к человеку, повторные нарушители, поднимающиеся на поверхность, и блокировки, реально разрывающие каждую поверхность, где двое пользователей могли бы встретиться. Мы закладываем это в объём первого релиза, потому что дособирать систему блокировок поверх чатов, комментариев и лент позже — это переписывание рёбер социального графа. #### Идентичность, приватность и форма графа Кто кого может видеть, находить, кому писать — решается до того, как существует модель данных, потому что каждый более поздний ответ — это миграция. Соседские продукты добавляют приватность геопозиции: лента Jepta знает, где вы стоите, и дизайн обязан сделать это фичей, а не утечкой — огрублённые координаты по умолчанию, настоящие только там, где продукту они правда нужны, и удаление аккаунта, которое действительно выплетает человека из разговоров, которые он покидает. ### Почему Flutter для комьюнити-продуктов | Требование | Как это решает Flutter | | --- | --- | | Оба стора на запуске | Сообщество, разрезанное по платформам, — половина сообщества; одна кодовая база делает оба стора вариантом по умолчанию | | Рендеринг уровня чата | Скомпилированный в натив интерфейс держит 60 fps в разговорах со стримингом, анимациями и тяжёлыми медиа — проверено на Arcana | | Кастомный, насыщенный брендом интерфейс | Flutter рисует каждый пиксель; идентичность сообщества не собирается из стоковых платформенных виджетов | | Роадмап из «больше поверхностей» | Один слой реального времени и один навигационный граф делают каждый новый экран небольшим изменением — результат Jepta | | Итерации, пока сообщество формируется | Hot reload быстрее секунды, пока вы узнаёте, что ваши люди на самом деле делают вместе | Где нативная разработка всё ещё выигрывает: если продукт по сути — платформенная возможность (продукт, ведомый виджетами, глубокое присутствие на Watch, продукт, примыкающий к iMessage), кроссплатформенная экономия не главное, и мы так и скажем. Общая страница услуги — [разработка на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ### Как мы работаем **Проект с фиксированным объёмом.** Мы полностью отвечаем за поставку — дискавери, дизайн, архитектура, разработка, публикация в сторах. Jepta — эта модель в полном масштабе. Наш [процесс разработки](https://ru.nerdy.pro/blog/flutter-app-development-process) описывает её неделя за неделей. **Staff augmentation.** Наши инженеры входят в вашу команду, в ваш репозиторий и спринты — см. [расширение команды](https://ru.nerdy.pro/services/team-augmentation). В обоих случаях смету под задачу мы присылаем в течение двух рабочих дней после того, как разберёмся в требованиях. ### Сколько стоит комьюнити-приложение Сфокусированный первый релиз — чат или лента, профили, уведомления и модерационный минимум, который требуют сторы, — это проект уровня Business: 2,7–5,4 млн ₽, за три-пять месяцев. Платформа масштаба Jepta — чаты, каналы, посты, комментарии и лента с учётом геопозиции в одном релизе — или платформа с тяжёлыми медиапайплайнами и операционным инструментарием модерации уходит в enterprise-работу: от от 8,1 млн ₽. Это те же опубликованные тарифы, что и на странице [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development) — вертикаль не получает второго прайс-листа. Что двигает цифру комьюнити-проекта внутри них: сколько поверхностей реального времени выходит в v1, медиапайплайн (текст дёшев, видео — нет) и сколько модерационного инструментария должно операционно существовать в первый день. ### Частые вопросы Что чаще всего спрашивают команды, которые строят социальные и комьюнити-продукты на Flutter. #### Подходит ли Flutter для социальных и комьюнити-приложений? Да. Списки чатов, ленты и экраны с тяжёлыми медиа — это кастомный, насыщенный анимацией интерфейс, ровно то, что Flutter рисует одинаково на iOS и Android из одной кодовой базы, компилируясь в нативный код, поэтому разговоры остаются на 60 кадрах в секунду даже во время стриминга сообщений. Не менее важна арифметика запуска: сообщество, вышедшее на одной платформе, урезало свой сетевой эффект вдвое, а одна кодовая база делает запуск в обоих сторах вариантом по умолчанию, а не удвоенным бюджетом. Мы выпустили Jepta, полноценную соседскую платформу, ровно этим способом. #### Сколько стоит разработка комьюнити-приложения? Сфокусированный первый релиз с чатом или лентой, профилями, уведомлениями и модерационным минимумом, который требуют сторы, — это проект уровня Business за три-пять месяцев; платформа масштаба Jepta — чаты, каналы, посты, комментарии и лента с учётом геопозиции в одном релизе — уходит в enterprise-работу. Диапазоны опубликованы на этой странице и на странице разработки на Flutter. Сильнее всего цифру двигают количество поверхностей реального времени в первом релизе и то, входит ли в медиапайплайн видео. #### Как построить чат реального времени во Flutter-приложении? Как один общий слой реального времени, а не как функцию, прикрученную к каждому экрану: транспорт на WebSocket с одной политикой переподключения, одним правилом упорядочивания сообщений и одним ответом на вопрос, что происходит, когда соединение обрывается посреди разговора. Рендеринг — отдельная дисциплина: сужение перестроек так, чтобы новое сообщение обновляло одну ячейку, а не весь список, — и мы доводили её до чата, держащего тысячи стриминговых markdown-сообщений при 60 fps. Построенный один раз, этот слой обслуживает чат, присутствие, реакции и живые счётчики разом — поэтому поздние экраны Jepta и были дешёвыми. #### Что требует Apple от приложения с пользовательским контентом? Гайдлайн 1.2, проверяемый на ревью: метод фильтрации неприемлемого контента, механизм жалоб для пользователей, возможность блокировать агрессивных пользователей и опубликованные правила сервиса. Приложение без любого из этих пунктов может быть отклонено независимо от качества, и машинерия должна работать операционно — жалобы доходят до человека, блокировки разрывают каждую поверхность, где двое пользователей могли бы встретиться. Мы закладываем модерацию в объём первого релиза, потому что дособирать систему блокировок поверх чатов, комментариев и лент позже — значит переписывать рёбра социального графа. #### Масштабируется ли комьюнити-приложение на Flutter до большого числа пользователей? Клиент масштабируется, если рендеринг дисциплинирован; настоящие вопросы масштабирования живут на транспорте и бэкенде. Что и как часто серверу разрешено пушить, как схлопываются обновления под нагрузкой, как упорядочивание ленты вычисляется на уровне запроса и как устроена веерная рассылка, когда один пост достигает тысяч устройств, — эти решения, а не UI-фреймворк, определяют потолок. Ровно этот паттерн мы выстраивали и под живые рыночные данные, и под комьюнити-трафик — и он переносится целиком. #### Запускаться сразу и на iOS, и на Android? Для комьюнити-продукта — почти всегда да, и сильнее, чем для любой другой категории: ваши пользователи приглашают друг друга, и каждое приглашение, пересекающее границу платформ, проваливается, если вы вышли в один стор. Одна кодовая база на Flutter производит оба приложения, поэтому предельная стоимость второго стора мала — что превращает структурный риск в строку сметы. Исключение — сознательно закрытое бета-сообщество, где одна платформа может быть выбором очерёдности, а не стратегией запуска. Прочитайте [кейс Jepta](https://ru.nerdy.pro/portfolio/jepta) о полной шестимесячной разработке, [кейс Arcana](https://ru.nerdy.pro/portfolio/arcana) о рендеринге чата на пределе сложности — или начните с пилларной страницы [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ## Разработка финтех-приложений https://ru.nerdy.pro/services/flutter-app-development/fintech Разработка финтех-приложений — это проектирование и разработка мобильных приложений, которые перемещают, отображают или учитывают деньги: инвестиционные и торговые приложения, необанки, платёжные сервисы и кошельки, иншуртех. От обычной разработки она отличается тремя вещами: данные меняются прямо на глазах у пользователя, планку безопасности задают регуляторы, а не ваша команда, и арифметическая ошибка здесь — это финансовая потеря, а не косметический баг. Flutter подходит для финтеха, потому что рисует интерфейс сам. Кастомный график, дашборд портфеля или список операций ведут себя одинаково на iOS и Android из одной кодовой базы — примерно на 30–40% дешевле, чем делать тот же продукт дважды нативно. То, что напрямую касается платформы, — аппаратное хранение ключей, платёжные шторки, часть KYC-SDK — идёт через platform channels: это рутина, но это реальная работа. #### Какие продукты мы делаем - **Инвестиционные и торговые приложения** — портфели, списки наблюдения, заявки и экраны с графиками, которые не тормозят при живых котировках - **Необанки и банковские клиенты** — счета, переводы, выписки, управление картами и онбординг со сканированием документов - **Платёжные продукты и кошельки** — оплата, история операций, регулярные платежи и нативные платёжные шторки - **Личные финансы и кэшбэк** — агрегация, категоризация и механика вознаграждений, как в [кэшбэк-платформе](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform), которую мы построили - **Иншуртех** — расчёт стоимости страховки, оформление страховых случаев и управление полисами, где пайплайн документов и фото важнее дашборда ### Кто будет делать ваше финтех-приложение Две вещи отличают команду, способную сделать финансовое приложение, от команды, которая делала просто приложения. #### Платёжная инфраструктура, проверенная на масштабе Nerdy Production возглавляет [Илья Никсан](https://ru.nerdy.pro/team/nixan), ранее CTO QIWI — одной из крупнейших платёжных платформ на своём рынке, где он руководил примерно 12 инженерными командами: от веб-продуктов до карточного процессинга, зоны PCI-DSS и бесконтактных платежей. Он построил бесконтактную оплату картой на Android через Host Card Emulation поверх ISO/IEC 14443, с платёжным протоколом EMV Contactless (Visa PayWave), и выпустил NFC-оплату картой примерно за год до того, как на рынке появился собственный Android Pay от Google. Он также был архитектором терминала Evotor — устройства на форкнутом AOSP, работающего на кассах в продакшене. Что это значит для вас: человек, который ведёт вашу разработку, эксплуатировал регулируемую платёжную инфраструктуру, отвечал за зону PCI-DSS и выпускал card-present сценарии оплаты, а не просто писал приложения, которые вызывают платёжный API. #### Финтех-продукт, уже опубликованный в обоих сторах Мы сделали [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) для Isarvest GmbH в 2022 году — инвестиционное приложение на Flutter с бэкендом на Go: рыночные данные в реальном времени, интерактивные графики и учёт портфеля, опубликованное в App Store и Google Play. Технический разбор — в статье [Как мы делали финтех-приложение реального времени на Flutter и Go](https://ru.nerdy.pro/blog/extraetf-realtime-fintech-flutter-go), включая веерную рассылку по WebSocket, которая держит поток котировок, не перегружая клиент. ### Что на самом деле требуют финтех-приложения В каждом финансовом проекте возникают пять задач. Вот как мы решаем каждую. #### Рыночные и транзакционные данные в реальном времени Цены, балансы и статусы заявок меняются непрерывно, и интерфейс должен это переваривать, не роняя кадры и не теряя позицию скролла. В ExtraETF решением стал сервис на Go, раздающий рыночные данные через WebSocket, вместе с клиентом, который сужает перестройки до реально изменившейся серии. Наивная реализация — перестраивать поддерево виджетов на каждом тике — это ровно то, из-за чего экраны с графиками дёргаются на среднем Android-железе. Решения, которые здесь действительно важны, принимаются на транспорте, а не в дереве виджетов: как часто серверу разрешено пушить, схлопываются ли обновления в окно и что делает клиент, когда сокет обрывается посреди сессии и переподключается уже к другой цене. Именно ошибку в переподключении пользователи и замечают — потому что она показывает им число, которое было верным тридцать секунд назад. #### Безопасность и комплаенс У нас нет сертификации PCI-DSS или SOC 2, и если агентство намекает на обратное — вам что-то продают. Что мы действительно делаем — строим так, чтобы сертификация была для вас достижимой задачей, а не поводом переписывать всё с нуля: - Учётные данные в iOS Keychain и Android Keystore, а не в shared preferences - TLS с пиннингом сертификатов и планом ротации, который не ломает старых клиентов - Биометрия и код-пароль на чувствительных действиях, а не только при запуске - Шифрование локального хранилища для всего, что кэшируется на устройстве - Никаких персональных и финансовых данных в логах, крэш-репортах и аналитике - Карточные данные вне зоны вашего приложения везде, где это позволяет SDK провайдера Архитектурные ограничения, которые накладывают PCI-DSS, GDPR, SOC 2 и KYC/AML, — это решения, которые принимают заранее или оплачивают потом; ровно этот опыт наш основатель вынес из карточного процессинга. На практике это значит решить заранее, каких данных ваше приложение вообще имеет право касаться: ввод карты отдан SDK провайдера, чтобы сырой PAN не доходил до вашего кода; персональные данные разделены так, чтобы запрос на удаление по GDPR был запросом к базе, а не археологией; журнал аудита спроектирован вместе с функцией, а не прикручен, когда его впервые попросят. Архитектуру, которой мы придерживаемся, и места, где она незаметно ломается, разбирает наш [материал про шифрование в мобильных приложениях](https://ru.nerdy.pro/blog/encryption-explained). #### Точная денежная арифметика Деньги никогда не бывают числом с плавающей точкой. В IEEE 754 `0.1 + 0.2` не равно `0.3`, и в финансовом приложении эта ошибка округления вырастает в несошедшуюся сверку. Мы храним суммы целыми числами в наименьших единицах валюты — в копейках, а не в рублях — или используем decimal-тип, а правила округления держим явными и покрытыми тестами на каждой границе — отображение, API, хранилище. Подробный разбор — в статье [Почему 0.1 + 0.2 ≠ 0.3](https://ru.nerdy.pro/blog/floating-point-and-money). #### Платежи, подписки и доступы Интеграция платёжных шлюзов, встроенные покупки, тарифы подписок и проверка доступов — вот где у финансовых приложений копятся граничные случаи: восстановление покупок на новом устройстве, подписка, истёкшая посреди сессии, платёж, прошедший у провайдера, но не подтвердившийся на вашем бэкенде. Состояние доступа мы держим на сервере, а клиент лишь отражает его — иначе получается пользователь, который заплатил и не может попасть в то, что купил. Есть и политика сторов, на которой команды регулярно спотыкаются. Apple и Google требуют использовать свои встроенные покупки для цифровых товаров, тогда как настоящие финансовые операции — движение собственных денег пользователя, покупка ценных бумаг — идут через платёжного провайдера. По какую сторону этой линии находится ваш продукт, определяет и интеграцию, и исход ревью, и выяснить это стоит до того, как функцию начнут делать, а не на отправке в стор. #### Производительность и надёжность У пользователей финансовых приложений низкая терпимость к спиннеру там, где должен быть баланс. Это значит 60fps на экранах с графиками — на реальных устройствах, а не на флагманских симуляторах, offline-first, чтобы кэш отрисовывался мгновенно, пока грузятся свежие данные, и состояния ошибок, которые говорят, что произошло, вместо тихого показа устаревшей цифры. Вопрос «когда это число было верным в последний раз?» мы считаем полноценной частью состояния интерфейса и тестируем на среднем Android-железе, которое реально в руках у ваших пользователей. ### Почему Flutter для финтеха Честный аргумент за Flutter здесь: | Требование | Как это решает Flutter | | --- | --- | | iOS и Android одной командой | Одна кодовая база, примерно на 30–40% дешевле двух нативных | | Кастомный финансовый UI — графики, дашборды | Flutter рисует пиксели сам, поэтому сложный UI идентичен на обеих платформах | | Плавность под живыми данными | Компилируется в нативный код; 60fps достижимы при аккуратном сужении перестроек | | Платформенные примитивы безопасности | Keychain, Keystore и биометрия — через platform channels | | SDK вендоров для KYC и платежей | У многих есть Flutter-пакеты; чисто нативные требуют обёртки через platform channels | Экономия здесь — не скидка на инженерию, а снятие дублирующей работы: один набор бизнес-правил, одна логика форматирования денег, один набор экранов, которые тестируют дважды, а не строят дважды. Где нативная разработка всё ещё выигрывает: если ваш продукт по сути тонкая обёртка вокруг платформенной возможности — глубокая интеграция с Apple Wallet или card-present NFC-сценарий, завязанный на стек одной платформы, — преимущество кроссплатформенности схлопывается, и честным ответом может быть нативная разработка. Мы скажем, если это ваш случай. Общая страница услуги — [разработка на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ### Как мы работаем **Проект с фиксированным объёмом.** Мы полностью отвечаем за поставку — от дискавери и архитектуры до разработки и публикации в сторах. Подходит, когда нужно передать понятный объём и иметь команду, отвечающую за результат. **Staff augmentation.** Наши инженеры входят в вашу команду, в ваш репозиторий и спринты, под ваше управление. Подходит, когда инженерное руководство уже есть и нужны дополнительные Flutter-разработчики — см. [расширение команды](https://ru.nerdy.pro/services/team-augmentation) и наш [гайд по найму Flutter-разработчиков](https://ru.nerdy.pro/blog/hire-flutter-developers-2026), который честно говорит и о том, когда нас брать не стоит. В обоих случаях смету под задачу мы присылаем в течение двух рабочих дней после того, как разберёмся в требованиях. ### Сколько стоит финтех-приложение Сфокусированный первый релиз — аккаунты, базовые транзакции и один-два флагманских экрана — это проект уровня Business: 2,7–5,4 млн ₽, за три-пять месяцев. Брокерский или банковский клиент с живыми рыночными данными, KYC-онбордингом и интеграциями платёжных провайдеров — это enterprise-работа, от от 8,1 млн ₽. Это те же опубликованные тарифы, что и на странице [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development) — вертикаль не получает второго прайс-листа. То, что двигает цифру финтех-проекта внутри этих диапазонов, редко оказывается интерфейсом: это количество и зрелость сторонних интеграций, требуемый вашему продукту уровень комплаенса и то, какая часть бэкенда ваша, а какая — провайдера. Все три момента всплывают на этапе оценки — поэтому смета приходит после вопросов, а не до них. ### Частые вопросы Что чаще всего спрашивают команды, которые строят финансовые продукты на Flutter. #### Подходит ли Flutter для финтех-приложений? Да. Flutter хорошо подходит для финтех-приложений, потому что рисует интерфейс сам: кастомные финансовые экраны — графики, дашборды портфеля, списки операций — ведут себя одинаково на iOS и Android из одной кодовой базы, обычно на 30–40 процентов дешевле, чем разработка двух нативных приложений. Он компилируется в нативный код, поэтому производительность под живыми рыночными данными достижима. Компромисс в том, что платформенные возможности вроде аппаратного хранения ключей, платёжных шторок и части KYC-SDK доступны через platform channels, а не напрямую: это рутинная, но реальная инженерная работа. #### Достаточно ли Flutter безопасен для банковских приложений? Да, и он уже используется в продакшен-приложениях банков и брокеров. Flutter не меняет вашу защищённость, потому что использует те же платформенные примитивы, что и нативное приложение: iOS Keychain, Android Keystore, биометрические API и пиннинг сертификатов — всё через platform channels. Безопасность финансового приложения определяется дисциплиной реализации, а не UI-фреймворком. На практике мы видим три провала: учётные данные в незащищённом хранилище, персональные данные в логах и отсутствие пиннинга — и все три встречаются в нативных кодовых базах ровно так же часто. #### Потянет ли Flutter рыночные данные в реальном времени? Да, и это та часть, которая больше всего выигрывает от осознанной инженерии. Мы сделали ExtraETF, работающее инвестиционное приложение, на Flutter с бэкендом на Go, который раздаёт рыночные данные через WebSocket. Типичный провал — перестраивать большие поддеревья виджетов на каждом тике цены, из-за чего на средних устройствах падают кадры. Лечится это сужением перестроек до реально изменившихся данных и правильной гранулярностью обновлений на транспорте, а не проталкиванием каждого тика прямо в дерево виджетов. #### Как считать деньги во Flutter-приложении? Никогда не числами с плавающей точкой. В IEEE 754 0.1 плюс 0.2 не равно 0.3, и в финансовом приложении эта ошибка округления накапливается до несошедшейся сверки. Мы храним денежные суммы целыми числами в наименьших единицах валюты — в копейках, а не в рублях — либо используем decimal-тип, и делаем правила округления явными и покрытыми тестами на каждой границе, где деньги показываются, передаются или сохраняются. Это требование корректности, а не вопрос вкуса. #### Сколько стоит разработка финтех-приложения? Сфокусированный первый релиз со счетами, основными операциями и одним-двумя ключевыми экранами обычно занимает от трёх до пяти месяцев разработки. Брокерское или банковское приложение с живыми рыночными данными, KYC-онбордингом и интеграциями платёжных провайдеров идёт дольше, потому что сроки задают сторонние интеграции и циклы ревью, а не работа над интерфейсом. Разбор того, как объём, бэкенд, интеграции и комплаенс складываются в итоговую цифру, — в нашем гайде по стоимости разработки на Flutter. #### Поддерживают ли Flutter-приложения платежи и подписки? Да. Flutter-приложения интегрируют платёжные шлюзы, нативные встроенные покупки для цифровых товаров и тарифы подписок с проверкой доступов. Инженерная сложность не в основном сценарии, а в граничных случаях: восстановление покупок на новом устройстве, подписка, истёкшая посреди сессии, и платежи, прошедшие у провайдера, но не подтвердившиеся на бэкенде. Состояние доступа мы держим на сервере, а клиент лишь отражает его, поэтому заплативший пользователь никогда не потеряет доступ из-за предположения, сделанного на клиенте. #### Есть ли у вас сертификация PCI-DSS или SOC 2? Нет, и стоит скептически смотреть на любую студию разработки приложений, которая предлагает сертификацию как аргумент продажи вместо рассказа о том, как она строит. Сертификация относится к организации, которая обрабатывает данные, — для большинства финтех-продуктов это вы и ваш платёжный провайдер. Мы даём архитектуру, которая держит карточные данные вне зоны вашего приложения там, где это позволяет провайдер, и основателя, который отвечал за зону PCI-DSS и вёл карточный процессинг в крупной платёжной платформе, — поэтому ограничения закладываются в проект с самого начала, а не обнаруживаются на аудите. Разбор стоимости — в статье [Стоимость разработки Flutter-приложения в 2026 году](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026), а посмотреть, как выглядит выпущенный финтех-продукт на Flutter, можно в [кейсе ExtraETF](https://ru.nerdy.pro/portfolio/extraetf). ## Разработка e-commerce-приложений https://ru.nerdy.pro/services/flutter-app-development/e-commerce Разработка e-commerce-приложений — это инженерия мобильных витрин: каталоги товаров, корзины, оформление заказа, отслеживание доставки и механики лояльности, которые возвращают покупателя. От обычной разработки она отличается тремя конкретными вещами: каталог больше устройства (поэтому поиск, кэширование и доставка изображений решают, каким приложение ощущается), оформление заказа — место, где каждый дефект превращается в потерянную выручку, а одна и та же витрина часто должна существовать в нескольких экземплярах — на бренд, на рынок, на валюту. Flutter подходит ритейлу необычно хорошо, потому что рисует интерфейс сам: сетка товаров, промо в формате сторис или кастомный сценарий оформления заказа ведут себя одинаково на iOS и Android из одной кодовой базы — примерно на 30–40% дешевле, чем строить ту же витрину дважды нативно. То, что напрямую касается платформы, — шторки Apple Pay и Google Pay, часть SDK платёжных провайдеров — идёт через platform channels: это рутина, но это реальная инженерная работа. #### Какие продукты мы делаем - **Приложения-витрины** — каталог, поиск, корзина, оформление заказа, отслеживание доставки и отзывы для одного бренда - **Приложения лояльности и кэшбэка** — баллы, вознаграждения и предложения, как в [кэшбэк-платформе](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform), которую мы построили и ведём как парк приложений - **White-label-парки для ритейла** — одна кодовая база, производящая брендированное приложение для каждого мерчанта или рынка; платформенная работа вынесена в отдельную [страницу услуги](https://ru.nerdy.pro/services/whitelabel-app-development) - **Приложения для мерчантов** — парное приложение продавца или менеджера магазина, которое мы сделали для той же платформы - **Витрины маркетплейсов** — где каталог принадлежит многим продавцам; двусторонняя динамика разобрана на нашей [странице про маркетплейсы](https://ru.nerdy.pro/services/flutter-app-development/marketplace) ### Кто будет делать ваше e-commerce-приложение Скажем прямо, что содержит наше портфолио: витрины одного бренда, которую мы могли бы назвать, в нём нет. Зато в нём есть сложные части коммерции — выпущенные многократно и до сих пор работающие в продакшене. #### Парк из пятнадцати брендированных потребительских приложений Для платформы кэшбэка и лояльности под NDA одна кодовая база на Flutter производит [больше пятнадцати полностью брендированных приложений](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform) — каждое со своим именем, темой и карточкой в сторе, — плюс приложение мерчанта по другую сторону каждой транзакции и админ-панель, которая запускает новый бренд без участия инженеров. Восемь месяцев разработки, и архитектура [подробно описана](https://ru.nerdy.pro/blog/white-label-app-platform-flutter). Если ваша ритейл-стратегия — больше одной витрины, это ровно та же форма задачи. #### Платежи, знакомые изнутри Nerdy Production возглавляет [Илья Никсан](https://ru.nerdy.pro/team/nixan), ранее CTO QIWI — одной из крупнейших платёжных платформ на своём рынке, где его команды вели карточный процессинг и отвечали за зону PCI-DSS. Связь с магазином прямая: оформление заказа — это платёжная интеграция, одетая в витрину, и человек, который ведёт вашу разработку, эксплуатировал другую сторону этой интеграции. Дисциплины, которые из этого следуют, — деньги целыми числами в наименьших единицах валюты, состояние доступа на сервере, карточные данные вне зоны вашего приложения — те же, что полностью описывает наша [страница про финтех](https://ru.nerdy.pro/services/flutter-app-development/fintech). #### Механика каталога на масштабе маркетплейса [OneTwoDo](https://ru.nerdy.pro/portfolio/onetwodo) — не магазин, но это просматриваемый каталог объявлений с фотографиями, поиском, мультивалютными ценами и лентой, отфильтрованной по языку и локации, — спроектированный и выпущенный за семь недель. Механика «красиво показать большую коллекцию вещей на среднем телефоне» переносится целиком. ### Что на самом деле требуют e-commerce-приложения В каждом ритейл-проекте возникают пять задач. Вот как мы решаем каждую. #### Каталог больше устройства Каталог живёт на сервере; приложение держит над ним подвижное окно. За этой фразой прячется большая часть инженерии: постраничные запросы, сохраняющие позицию скролла, поиск, который терпит опечатки и отвечает за миллисекунды, изображения, нарезанные и закэшированные так, чтобы сетка товаров не съедала мобильный трафик, и страница товара, которая мгновенно рисуется из закэшированных данных списка, пока детали грузятся следом. Сделайте это неправильно — и приложение будет казаться медленным везде, хотя каждый отдельный запрос выглядит нормально. #### Оформление заказа, где отказ — основной сценарий Платежи падают: карты отклоняются, челленджи 3-D Secure истекают по таймауту, приложение сворачивают посреди оплаты, сеть обрывается между подтверждением у провайдера и тем моментом, когда о нём узнаёт ваш бэкенд. Оформление заказа проектируется вокруг этих случаев, а не вокруг счастливого пути: идемпотентное создание заказа, чтобы повтор не мог списать деньги дважды, состояние платежа на сервере, а клиент лишь отражает его, и однозначный ответ на экране для любого исхода. «Платёж прошёл у провайдера, но не подтвердился на бэкенде» — класс багов, который стоит реальных денег, и его убирают на этапе архитектуры. #### Нативные платёжные шторки — нативно Apple Pay и Google Pay конвертируют на мобильных заметно лучше, чем формы ручного ввода карты, и оба доступны из Flutter через platform channels поверх собственного SDK каждой платформы. Мы подключаем их рядом с карточным сценарием провайдера, а не вместо него, и держим сырые карточные данные внутри SDK провайдера, чтобы ваша зона PCI оставалась настолько маленькой, насколько позволяет продукт. #### Цены — это деньги, а деньги — не float Каждая цена, скидка, налоговая строка и итог — это целые числа в наименьших единицах валюты или decimal-тип, и никогда не число с плавающей точкой, где `0.1 + 0.2` не равно `0.3`, а итог корзины может разойтись с суммой её строк. Правила округления явные и покрыты тестами на каждой границе, где деньги показываются, передаются или сохраняются. Длинная версия — в статье [Почему 0.1 + 0.2 ≠ 0.3](https://ru.nerdy.pro/blog/floating-point-and-money). #### Правила сторов, которые ритейл-команды обнаруживают поздно Оба стора берут комиссию с цифровых товаров, но не с физических: продавать физические товары через собственного платёжного провайдера можно, а вот подмешивание цифровых бонусов в ритейл-приложение — то место, где начинаются проблемы на ревью. Гайдлайн Apple 4.2.6 — вторая ловушка: парк тонких, почти одинаковых брендированных приложений — ровно то, что он и призван отклонять, и проектирование white-label-платформы так, чтобы она его проходила, — наша специализация, а не сюрприз на двадцатой неделе. Удаление аккаунта, декларации о данных и сроки ревью закладываются в план отдельной фазой — как и на каждой странице этого сайта. ### Почему Flutter для e-commerce | Требование | Как это решает Flutter | | --- | --- | | Оба стора при одном бюджете | Одна кодовая база, примерно на 30–40% дешевле двух нативных витрин | | Интерфейс точно по бренду | Flutter рисует каждый пиксель, поэтому дизайн-система ваша, а не платформы | | Быстрые сетки товаров на дешёвых телефонах | Компиляция в нативный код держит 60 fps там, где реально сидят покупатели, — на среднем Android | | Скорость сезонных итераций | Hot reload быстрее секунды; промо-экран выходит за дни, а не за релизные циклы | | Больше одной витрины | Та же кодовая база масштабируется от одного бренда до парка — см. [white-label-платформу](https://ru.nerdy.pro/services/whitelabel-app-development) | Где нативная разработка всё ещё выигрывает: если ваша витрина по сути обёртка вокруг возможности одной платформы — сценарий, построенный вокруг App Clip, глубокая интеграция с Wallet как сам продукт, — кроссплатформенная экономия не главное, и мы так и скажем. Общая страница услуги — [разработка на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ### Как мы работаем **Проект с фиксированным объёмом.** Мы полностью отвечаем за поставку — дискавери, архитектура, разработка, публикация в сторах. Наш [процесс разработки](https://ru.nerdy.pro/blog/flutter-app-development-process) описывает, как это выглядит неделя за неделей. **Staff augmentation.** Наши инженеры входят в вашу команду, в ваш репозиторий и спринты — см. [расширение команды](https://ru.nerdy.pro/services/team-augmentation). В обоих случаях смету под задачу мы присылаем в течение двух рабочих дней после того, как разберёмся в требованиях. ### Сколько стоит e-commerce-приложение Витрина — каталог и поиск, корзина и оформление заказа, интеграция платёжного шлюза, отслеживание доставки, отзывы — это отдельный тариф на странице [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development): 4,5–8,1 млн ₽, за четыре-шесть месяцев. Мультивендорная платформа, брендированный парк приложений или глубокая интеграция с ERP и складом — это enterprise-работа, от от 8,1 млн ₽. Это те же опубликованные тарифы, что и в таблице цен пилларной страницы — обе страницы рисуются из одних ключей, поэтому разойтись они не могут. Что двигает цифру ритейл-проекта внутри них: количество платёжных методов и рынков, какую часть каталожной машинерии уже даёт ваш бэкенд и не является ли «одно приложение» на самом деле парком. ### Частые вопросы Что чаще всего спрашивают команды, которые строят ритейл- и e-commerce-продукты на Flutter. #### Подходит ли Flutter для e-commerce-приложений? Да. Flutter рисует интерфейс сам, поэтому сетка товаров, промо-экран или кастомное оформление заказа выглядят и ведут себя одинаково на iOS и Android из одной кодовой базы — обычно на 30–40 процентов дешевле, чем разработка двух нативных витрин. Он компилируется в нативный код, что сохраняет плавность скролла на средних Android-устройствах, где на самом деле происходит заметная часть ритейл-трафика. Платформенные части — шторки Apple Pay и Google Pay, часть SDK платёжных провайдеров — доступны через platform channels: это рутинная, но реальная инженерная работа. #### Сколько стоит разработка e-commerce-приложения? Наш опубликованный e-commerce-тариф покрывает каталог и поиск, корзину и оформление заказа, интеграцию платёжного шлюза, отслеживание доставки, отзывы и управление складом за четыре-шесть месяцев — точный диапазон открыт на этой странице и на странице разработки на Flutter, а не спрятан за звонком с менеджером. Мультивендорные платформы и парки брендированных приложений оцениваются как enterprise-работа. Цифру двигает в первую очередь количество платёжных методов и рынков, во вторую — то, какую часть каталожной машинерии уже даёт ваш бэкенд. #### Может ли Flutter-приложение использовать Apple Pay и Google Pay? Да, через platform channels поверх собственного платёжного SDK каждой платформы — в виде нативной платёжной шторки, которую пользователи узнают. Нативные шторки конвертируют на мобильных измеримо лучше, чем формы ручного ввода карты, поэтому мы подключаем их рядом с карточным сценарием провайдера, а не вместо него. Сырой ввод карты остаётся внутри SDK платёжного провайдера, что держит ваше приложение вне зоны PCI везде, где провайдер это позволяет. #### Потянет ли Flutter большой каталог товаров? Да, при правильной архитектуре: каталог живёт на сервере, а приложение держит над ним постраничное окно, поиск выполняется на бэкенде, изображения нарезаются и кэшируются под экран, а страницы товаров мгновенно рисуются из закэшированных данных списка, пока детали грузятся следом. Провальный сценарий — не Flutter, а отношение к каталогу на сто тысяч позиций как к списку, который нужно скачать. Мы выпускали механику браузинга на масштабе маркетплейса — с мультивалютными ценами и лентами, отфильтрованными по локации. #### Мы уже продаём через сайт. Нужно ли нам приложение? Не всегда, и мы так и скажем, когда честный ответ — хорошо сделанный мобильный веб. Приложение окупает бюджет за счёт поверхности удержания, которой у сайта нет: пуш-уведомления об акциях и статусах заказов, иконка на домашнем экране, нативные платёжные шторки и механики лояльности, награждающие за возвращение. Если большая часть выручки — повторные покупатели, аргументы обычно сильные; если это разовый поисковый трафик, вкладывайтесь сначала в веб — его мы тоже делаем. #### Может ли одна кодовая база производить несколько брендированных магазинов? Да — это наш самый прямой аргумент в коммерции. Одна построенная нами кодовая база на Flutter производит больше пятнадцати полностью брендированных потребительских приложений плюс приложение мерчанта, с админ-панелью, которая запускает новый бренд без участия инженеров. Ловушка политики сторов — гайдлайн Apple 4.2.6, существующий, чтобы отклонять парки почти одинаковых приложений, и white-label-платформу нужно с самого начала проектировать так, чтобы она его проходила. Полная архитектура описана на нашей странице white-label-услуги и в сопутствующей инженерной статье. Разбор стоимости — в статье [Стоимость разработки Flutter-приложения в 2026 году](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026), посмотрите, [как устроен парк платформы лояльности](https://ru.nerdy.pro/blog/white-label-app-platform-flutter), или начните с пилларной страницы [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ## Разработка MVP https://ru.nerdy.pro/services/flutter-app-development/mvp Разработка MVP — это создание минимального приложения, способного ответить на конкретный вопрос вашего бизнеса: будут ли этим пользоваться, будут ли за это платить, тот ли это сценарий, который людям на самом деле нужен. От обычной разработки она отличается одним структурным свойством: результат — это не набор функций, а ответ. Всё, что не приближает вас к этому ответу, — объём, за который вы платите дважды: сначала — чтобы построить, потом — чтобы поддерживать, пока не удалите его. MVP на Flutter обычно занимает **два-три месяца** от старта до публикации в сторах и стоит 1,4–2,7 млн ₽. Обе цифры — из нашего [гайда по стоимости](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026), а на что именно уходят эти месяцы, поэтапно разобрано в [разборе сроков](https://ru.nerdy.pro/blog/how-long-to-build-a-flutter-app): мы лучше опубликуем их и будем спорить по существу, чем начнём разговор с «зависит от многого». Flutter на этой стадии важнее, чем на любой другой. MVP проверяет спрос, а не платформы, и вы заранее не знаете, из какого стора придут первые сто настоящих пользователей. Одна кодовая база выводит приложение в оба стора — это разница между «проверить идею» и «проверить идею на Android». #### Что входит в MVP - **5–8 ключевых функций** — те, от которых зависит ответ, и никаких других - **iOS и Android** — одна кодовая база, одна команда - **Managed-бэкенд** вместо написанного с нуля, если только продукт не требует обратного - **Интерфейс на библиотечных компонентах** — Material и Cupertino, стилизованные, а не перерисованные, чтобы дизайн не был на критическом пути - **Аналитика и сбор крэш-репортов** с первого дня: MVP без телеметрии не отвечает ни на один вопрос - **Публикация в обоих сторах**, в которой подача считается этапом, а не кнопкой ### Кто будет делать ваш MVP Две вещи отличают команду, способную выпустить MVP, от команды, которая делает приложения. #### Полноценный продукт, спроектированный и выпущенный за семь недель [OneTwoDo](https://ru.nerdy.pro/portfolio/onetwodo) — двусторонний маркетплейс локальных услуг для Perform Connect Studios S.L.: объявления об уборке, ремонте, сантехнике и прочем, которые публикуют и просматривают люди в одном районе. Мы сделали продукт целиком: продуктовый дизайн, Flutter-клиент и всё, на чём это работает. Семь недель. Он здесь из-за того, *как* был устроен объём. Двусторонний маркетплейс обычно сначала подразумевает бэкенд-команду и только потом всё остальное — аккаунты, сессии, база объявлений, хранилище фотографий, сервер под всем этим, — и именно туда обычно уходят первые три месяца небольшой команды. OneTwoDo вышел без единой строки этого слоя: Firebase Authentication для аккаунтов, Firestore для объявлений и профилей, Cloud Storage для фотографий, Analytics и Crashlytics вместо дашбордов, которые иначе пришлось бы строить. Ничего не нужно поднимать, патчить и держать на дежурстве. Именно это решение позволило одной команде вести дизайн, клиент и бэкенд одновременно, и оно продолжало окупаться: мультивалютные цены, локализация каждого объявления и лента с фильтром по языку и локации оказались клиентской работой поверх уже существующего слоя данных, а не бэкенд-задачей, за которой следует клиентская. Подробности — в [кейсе](https://ru.nerdy.pro/portfolio/onetwodo). #### Мы публикуем цифры, а не говорим «зависит от многого» Любое агентство отвечает на «сколько времени» и «сколько денег» вилкой такой ширины, что пользы от неё нет. Наши цифры опубликованы на этой странице и на странице [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development), а поэтапный разбор — в статьях [Сколько времени занимает разработка Flutter-приложения](https://ru.nerdy.pro/blog/how-long-to-build-a-flutter-app) и [Стоимость разработки Flutter-приложения в 2026 году](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026). Если ваш проект в них не укладывается, мы скажем это на этапе оценки — единственном моменте, когда эта информация чего-то стоит. Что мы ещё выпускали такого размера: [Arcana](https://ru.nerdy.pro/portfolio/arcana), AI-компаньон для гаданий на таро, чей Flutter-клиент — стриминговый чат, держащий тысячи сообщений на 60fps, — занял три недели; и [Telegram-бот с готовой аудиторией, превращённый в приложение в сторах за шесть недель](https://ru.nerdy.pro/blog/telegram-bot-to-app-6-weeks); этот план собран из нашей практики, а не описывает один клиентский проект, и он быстрый именно потому, что продуктовый вопрос уже был закрыт в другом месте. ### Что на самом деле требует MVP Шесть вещей определяют, выйдет ли MVP за три месяца или превратится в проект втрое длиннее запланированного. #### Список вырезанного, которого вы действительно придерживаетесь Нижняя граница в два месяца реальна, но условна: объём, помещающийся на одну страницу; один человек, который может принять решение, не собирая комитет; и отсутствие принципиально нового технического риска — никакого видеопайплайна, ML на устройстве или платёжной инфраструктуры в новой юрисдикции. Три месяца — более частый исход, и лишний месяц почти никогда не про «код оказался сложнее». Это платёжный сценарий, появившийся на четвёртой неделе, незапланированный раунд дизайна или две недели ожидания доступа к API. Поэтому список вырезанного — результат этапа оценки, а не сноска к нему. Мы записываем, что осталось снаружи, так же явно, как то, что внутри, и будем возражать против добавлений уже в процессе разработки — не из вредности, а потому что каждое маленькое добавление стоит недели, а четыре маленьких добавления — это разница между запуском в этом квартале и в следующем. #### Объём, которого вы не видите «Простое приложение для учёта тренировок» — это пять экранов у вас в голове. На практике это онбординг, аутентификация через email, Google и Apple — у каждого свои граничные случаи, — собственно функция, настройки, профиль, удаление аккаунта (требование обоих сторов) и платёжный сценарий, если продукт монетизируется. Ничего из этого не лишнее, и ничего из этого вы не имели в виду, когда описывали приложение. Назвать это заранее — и есть большая часть честной оценки. Здесь же происходит откровенный разговор о вилке: невидимый объём примерно постоянен, поэтому в двухмесячном проекте он съедает куда большую долю, чем в шестимесячном. #### Бэкенд, который не пришлось писать с нуля Для большинства MVP managed-бэкенд — не компромисс, а верное инженерное решение, и OneTwoDo это доказывает. Firebase, Supabase или тонкий REST-сервис поверх управляемой базы закрывают аккаунты, данные, файлы и аналитику без команды, которая всё это эксплуатирует, и масштабируются далеко за ту точку, где MVP уже ответил на свой вопрос. Когда мы будем отговаривать. Если ядро продукта — то, чего managed-бэкенд не умеет: тяжёлая собственная бизнес-логика, Fan-out в реальном времени под нагрузкой, регуляторный режим, диктующий физическое расположение данных. Мы скажем, на какой вы стороне, до сметы, потому что это двигает цифру. #### Телеметрия, иначе MVP не отвечает ни на что MVP существует, чтобы дать доказательства. Выпустить его без аналитики, сбора крэш-репортов и заранее описанного набора событий — это потратить весь бюджет и получить в ответ мнение вместо данных. Мы согласовываем на этапе оценки те несколько событий, которые соответствуют вашему настоящему вопросу, — активация, целевое действие, отвал, которого вы опасаетесь, — и подключаем их до релиза, а не после того, как первая когорта уже пришла и ушла. #### То, что мы отказываемся урезать MVP получает свою скорость за счёт сокращения функций, а не за счёт сокращения инженерии. Что остаётся при любом объёме: границы модулей, позволяющие второй версии расти без переписывания; CI/CD с первой недели, чтобы сборка запускалась одной командой; тесты на путях, где ошибка стоит денег; и явные состояния ошибок вместо бесконечно крутящегося спиннера. В этом разница между MVP и прототипом, и здесь стоит быть точным. Прототип одноразовый по замыслу. MVP — первая версия продукта, который вы намерены оставить, поэтому и код должен быть тем, что можно оставить. А «приберёмся после запуска» так редко случается именно потому, что подтверждённый продукт немедленно порождает более срочную работу, чем уборка. #### Публикация в сторах — это этап Закладывайте одну-две недели. Само ревью обычно занимает 24–48 часов, а вот работа вокруг него — нет: карточки в сторах, скриншоты под каждый обязательный размер устройства, политика конфиденциальности и декларации о данных, которых теперь требуют оба стора, и путь удаления аккаунта, который Apple проверит. Команды, которые узнают об этом на последней неделе, опаздывают по причинам, никак не связанным с их приложением. ### Почему Flutter для MVP Честный аргумент за Flutter здесь: | Требование | Как это решает Flutter | | --- | --- | | Оба стора за один бюджет | Одна кодовая база, примерно на 30–40% дешевле двух нативных разработок — самый сильный рычаг именно на масштабе MVP | | Итерации на глазах у пользователей | Hot reload за доли секунды: правка оказывается на устройстве быстрее, чем стартует нативная пересборка | | Дизайн не на критическом пути | Виджеты Material и Cupertino держат интерфейс, пока собственная дизайн-система вам ещё не нужна | | Одна команда для найма | Flutter-разработчики, а не iOS-команда плюс Android-команда плюс координация между ними | | v2 без переписывания | Кодовая база MVP и есть кодовая база продукта: то же приложение дорастает до полноценного бизнес-приложения, а не заменяется | Где нативная разработка всё ещё выигрывает: если проверяемый вопрос и есть платформенная возможность — глубокий сценарий на Apple Watch, продукт вокруг виджетов, что-то на API, существующем только на одной платформе, — то преимущество кроссплатформенности схлопывается, и честным ответом может быть нативная разработка. Мы скажем, когда это так. Общая страница услуги — [разработка на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ### Как мы работаем **Проект MVP с фиксированным объёмом.** Мы отвечаем за поставку целиком — дискавери, дизайн, архитектура, разработка, подача в сторы. Подходит, когда вы хотите передать определённый объём и иметь одну команду, отвечающую за дату запуска. Как это выглядит по неделям, описано в нашем [процессе разработки](https://ru.nerdy.pro/blog/flutter-app-development-process). **Из прототипа в продакшен.** Если у вас уже что-то работает — сборка в Cursor, Bolt или Lovable либо длинные выходные с AI-агентом, — продуктовый вопрос может быть частично закрыт, и работа тут другая: сначала аудит, потом закрытие разрыва между «работает в демо» и «безопасно перед живыми пользователями». Это [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit), а пошаговый разбор — в статье [Как довести AI-прототип до продакшена](https://ru.nerdy.pro/blog/ai-prototype-to-production). **Staff augmentation.** Наши инженеры входят в вашу команду, в ваш репозиторий и ваши спринты, под вашим управлением. Подходит, когда у вас уже есть инженерное руководство и нужны дополнительные Flutter-разработчики — см. [расширение команды](https://ru.nerdy.pro/services/team-augmentation) и наш [гайд по найму Flutter-разработчиков](https://ru.nerdy.pro/blog/hire-flutter-developers-2026), где честно сказано, когда не стоит брать нас. В любом из вариантов мы присылаем смету под задачу в течение двух рабочих дней после того, как разберёмся в требованиях. ### Частые вопросы Вопросы, которые чаще всего задают основатели, планирующие первый релиз на Flutter. #### Что такое MVP в разработке приложений? Минимально жизнеспособный продукт — это наименьшее приложение, способное ответить на конкретный вопрос бизнеса: будут ли этим пользоваться, будут ли платить, тот ли это сценарий, который людям нужен. Ударение здесь на «жизнеспособный», а не на «минимальный»: продукт должен быть достаточно хорош, чтобы реакция настоящего пользователя что-то значила, — поэтому MVP — это не прототип и не кликабельный макет. На практике это 5–8 ключевых функций, настоящий бэкенд, аналитика, привязанная к заданному вопросу, и живая карточка в сторах. Всё сверх этого — объём, за который вы платите дважды: чтобы построить и чтобы поддерживать, пока не удалите. #### Сколько времени занимает разработка MVP? Два-три месяца от старта до публикации в сторах для MVP на Flutter сразу под iOS и Android. Нижняя граница в два месяца реальна, но условна: объём, помещающийся на одну страницу, один человек, принимающий решения без комитета, и отсутствие принципиально нового технического риска — видеопайплайна, машинного обучения на устройстве, платёжной инфраструктуры в новой юрисдикции. Три месяца встречаются чаще, и лишний месяц редко приходится на инженерию. Это платёжный сценарий, появившийся на четвёртой неделе, незапланированный раунд дизайна или две недели ожидания доступа к чужому API. #### Сколько стоит разработка MVP? Наш MVP-уровень опубликован на этой странице и на странице разработки на Flutter, а не сообщается приватно по запросу, и покрывает 5–8 функций под iOS и Android с managed-бэкендом и интерфейсом на библиотечных компонентах. Цифру двигает в первую очередь объём, во вторую — интеграции: каждая значимая сторонняя интеграция — это одна-две недели, а невидимый объём из онбординга, аутентификации, настроек, удаления аккаунта и платежей примерно постоянен, поэтому в маленьком проекте он занимает большую долю, чем в крупном. Полный разбор того, что формирует цифру, — в нашем гайде по стоимости разработки на Flutter. #### Что должно быть в MVP, а что стоит вырезать? Внутри: функции, от которых зависит вопрос, один чистый путь через них, аналитика на тех моментах, которые дают ответ, и обязательная по закону обвязка, без которой нельзя выпускаться, — в 2026 году это в том числе удаление аккаунта. Снаружи: вторая роль пользователя, админка, которую полгода можно вести в таблице, собственная дизайн-система, офлайн-режим и любая функция, оправданная пользователем, которого вы ещё не встречали. Рабочая проверка — изменится ли то, что вы узнаете из запуска, если это убрать. Если нет — это v2, а записанный список вырезанного и есть результат этапа оценки, а не сноска к нему. #### Придётся ли потом переписывать MVP? Нет, если это делалось как MVP, а не как прототип. Прототип одноразовый по замыслу; MVP — первая версия продукта, который вы намерены оставить, поэтому в нём должны быть границы модулей, позволяющие функциям расти, непрерывная интеграция с первой недели, тесты на путях, где ошибка стоит денег, и настоящие состояния ошибок. Это стоит дней, а не недель, и это разница между тем, чтобы v2 была развитием, и тем, чтобы она была стартом заново. Ради скорости мы режем функции, уникальность дизайна и инфраструктуру, которую можно арендовать, — но никогда структуру самого кода. #### Можно ли сделать MVP на базе AI-прототипа? Да, и всё чаще проекты именно так и начинаются. Сборка в Cursor, Bolt или Lovable часто означает, что продуктовый вопрос уже частично закрыт, и это действительно ценно, — но разрыв между «работает в демо» и «безопасно перед живыми пользователями» конкретен и предсказуем: открытые ключи и отсутствующая авторизация, некэшированные вызовы API с многократным перерасходом, чувствительные данные, уходящие в логи или третьим сторонам. Правильный путь в этом случае — сначала аудит, потом работа по выпуску, а не пересборка, которая выбрасывает уже полученные знания. #### Нужно ли выпускать MVP сразу в оба стора? Обычно да, и это самый сильный аргумент в пользу Flutter именно на этой стадии. MVP проверяет спрос, а не платформы, и вы редко знаете заранее, из какого стора придут первые настоящие пользователи, — так что релиз в один стор означает проверку идеи на аудитории одной платформы и догадки о втором. Поскольку одна кодовая база Flutter даёт оба приложения, дополнительные затраты на второй стор невелики, а это ровно тот размен, который нужен первому релизу. Исключение — продукт, чьё ядро есть платформенная возможность: там кроссплатформенная экономия не про это. Поэтапный разбор сроков — в статье [Сколько времени занимает разработка Flutter-приложения](https://ru.nerdy.pro/blog/how-long-to-build-a-flutter-app), а как выглядит полноценный продукт, спроектированный и выпущенный за семь недель, — в [кейсе OneTwoDo](https://ru.nerdy.pro/portfolio/onetwodo). ## Разработка White-Label приложений https://ru.nerdy.pro/services/whitelabel-app-development ### Разработка White-Label мобильных приложений White-label приложение — это один продукт, проданный многократно: то же приложение, переоформленное под новый бренд и опубликованное заново для каждого из ваших партнёров, франчайзи или рынков. Сложность не в том, чтобы сделать одно приложение. Сложность — в том, чтобы создать единую кодовую базу, которая может стать десятью, пятьюдесятью или сотней брендированных приложений без роста затрат на поддержку с каждым новым запуском. Мы проектируем и строим white-label платформы, где каждое новое брендированное приложение — это конфигурация, а не форк, поэтому одна команда выпускает и поддерживает весь парк приложений. - **1** Кодовая база: Все брендированные приложения используют один исходный код - **Часы** На запуск бренда: А не недели разработки на каждое приложение - **Без лимита** Брендированных приложений: Предельная стоимость стремится к нулю - **1** Правка → все приложения: Одно изменение доходит до всего парка ### Почему Flutter — лучший выбор для White-Label приложений White-label — это именно тот сценарий, для которого создан Flutter. Разрабатывать парк брендированных приложений нативно — значит поддерживать две кодовые базы (Swift и Kotlin), умноженные на число брендов. Это непосильное бремя поддержки. Flutter сводит это к **одной кодовой базе, одной команде, всем платформам**. Это та же основа, что и в нашей [разработке приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development), масштабированная с одного приложения до целого парка. #### Что Flutter даёт парку White-Label приложений - Единый источник истины: iOS и Android для каждого бренда из одной кодовой базы на Dart — без расхождений по платформам и брендам - Пиксельная точность тем: Flutter сам отрисовывает каждый пиксель, поэтому тема бренда применяется одинаково на всех устройствах и платформах - Нативная производительность: Компиляция в нативный ARM-код — каждое брендированное приложение ощущается полноценным, а не обёрткой - Одна правка — весь парк: Исправленная ошибка или выпущенная функция попадает во все приложения в следующем релизе > **Итог:** для одного приложения Flutter — отличный выбор. Для парка white-label приложений это единственный выбор, при котором поддержка остаётся посильной: одна кодовая база вместо (2 платформы × N брендов) проектов, которые приходится постоянно синхронизировать. ### Гибкая система стилизации и фиче-гейтинга Ядро white-label платформы — это слой конфигурации, который превращает одну кодовую базу во множество разных приложений. Мы строим систему, где всё, что определяет бренд, живёт в конфигурации, отдельно от кода. **Гибкая стилизация**: цвета, типографика, логотипы, иконки приложений, сплэш-экраны и цветовой режим (светлый или тёмный) задаются для каждого бренда. Нетехнические менеджеры настраивают внешний вид бренда через админ-интерфейс — без разработчика, без изменения кода, без запроса на пересборку. **Фиче-гейтинг**: не у всех брендов одинаковый набор функций. Наша система фиче-флагов включает или отключает функциональность для каждого приложения — премиум-бренды открывают продвинутые модули, региональные приложения переключают функции под конкретный рынок, а пилотные бренды получают ранний доступ — и всё это из конфигурации. #### Настраивается под бренд, а не кодируется под бренд - Тема и брендинг: Цвета, шрифты, иконки, сплэш и цветовой режим для каждого приложения - Фиче-флаги: Включайте и отключайте возможности для каждого бренда независимо - Идентичность в сторе: Название приложения, идентификаторы бандлов и метаданные стора для каждого листинга - Платежи и интеграции: Конфигурация платежей, API и сторонних сервисов для каждого бренда ### Автоматизация сборки и развёртывания Пятнадцать приложений — это пятнадцать сборок, пятнадцать настроек подписи и пятнадцать публикаций в сторах на каждый релиз. Именно на ручной работе white-label проекты обычно и рушатся. Мы автоматизируем весь конвейер сборки и релиза, чтобы выпуск всего парка был единой повторяемой операцией. Мы используем [Fastlane](https://fastlane.tools) — отраслевой стандарт автоматизации для мобильных приложений — для управления подписью и provisioning-профилями, сборки и подписи бинарников iOS и Android, а также публикации каждого приложения в [App Store](https://docs.fastlane.tools/getting-started/ios/appstore-deployment/) и [Google Play](https://docs.fastlane.tools/getting-started/android/release-deployment/) вместе с метаданными и скриншотами. Нативная конфигурация сборки, нужная каждому бренду — идентификаторы приложений, идентификаторы бандлов, номера версий, подпись, иконки — генерируется автоматически из конфигурации бренда, поэтому настройки отдельных приложений никогда не правятся вручную. #### Автоматизировано от формы до стора - Нативная конфигурация под бренд: Настройки сборки Android и iOS генерируются из конфигурации, без ручной правки - Подписанные сборки: Автоматическая подпись и provisioning для всего парка - Публикация в сторах: Fastlane загружает бинарники, метаданные и скриншоты в оба стора - Повторяемые релизы: Каждый новый бренд наследует тот же проверенный путь деплоя ### Веб-админка Системой управляют через веб-админку, которую мы строим на **Nuxt**. Здесь менеджер создаёт новое брендированное приложение — вводит название бренда, загружает ресурсы, выбирает тему и цветовой режим, отмечает включённые функции и заполняет метаданные стора. Отправка формы генерирует конфигурацию бренда и запускает автоматизированный конвейер сборки. Результат: новое брендированное приложение запускает **сам нетехнический сотрудник**, а не разработчики. Число опубликованных приложений растёт, а инженерная команда — нет. Такие бэк-офисы — отдельная дисциплина: как мы их строим, рассказывает наша страница [админ-панелей и дашбордов](https://ru.nerdy.pro/services/web-development/admin-dashboards). ### Модели публикации: ваши аккаунты, ваш выбор То, как ваши приложения отображаются в сторах, — это стратегическое решение, и мы поддерживаем любую модель, которая подходит вашему бизнесу: | Модель публикации | Единый аккаунт разработчика | Аккаунт на приложение | Гибкая / смешанная (Чаще всего) | | --- | --- | --- | --- | | Присутствие в сторе | Все приложения под вашим аккаунтом | Каждый бренд на своём аккаунте | Комбинация по брендам при необходимости | | Разделение брендов | Приложения привязаны к одному издателю | Полное разделение по брендам | Разделение там, где это важно | | Накладные расходы на настройку | Минимальные — один аккаунт | Максимальные — аккаунт на приложение | Сбалансированные | | Лучше всего для | Одна компания, много суббрендов | Независимые партнёрские бренды | Растущие партнёрские сети | > **Не уверены, какая модель?** > > Конвейер поддерживает все три, и вы можете передумать позже — приложения могут публиковаться под единым аккаунтом разработчика, под отдельным аккаунтом на каждое приложение или в любом гибком сочетании. Мы поможем выбрать, исходя из независимости брендов, партнёрских соглашений и политики сторов. > Нам нужно было не одно приложение, а целое семейство брендированных приложений — по новому приложению на каждого партнёра, которого мы подключаем. Разрабатывать и поддерживать каждое из них нативно означало бы утонуть в затратах на разработку ещё до того, как мы вышли бы в ноль. Вся модель работает только тогда, когда запуск ещё одного брендированного приложения почти ничего не стоит. > > Команда Nerdy Production дала нам ровно это: одну кодовую базу на Flutter, которая превращается в любое количество полностью брендированных приложений, и админ-панель, где наша собственная команда запускает новый бренд, не трогая код. Одно исправление попадает во все приложения, новые партнёры запускаются за часы, а не за недели, а стоимость следующего бренда близка к нулю. Они превратили то, что казалось невозможным, в повторяемый процесс — и позволили нам наращивать число приложений, не наращивая команду. *[Evgeny Syrtsov](https://www.linkedin.com/in/evgeny-syrtsov/), Генеральный директор, Netgineers GmbH* ### Цены на разработку White-Label приложений Прозрачные цены на создание и сопровождение white-label платформы #### Стартовая платформа Одно приложение, готовое к масштабированию **2,7–5 млн ₽** 1-2 месяца - Flutter-приложение (iOS и Android) - Система тем и брендинга - Основа для фиче-флагов - Автоматизация сборки и деплоя - Публикация под единым аккаунтом - До 2 стартовых брендов [Начать](https://ru.nerdy.pro/contact?intent=whitelabel-app-development) #### Платформа для парка (Популярно) Создана для множества брендов **5–9 млн ₽** 2-3 месяца - Всё из Стартовой - Веб-админка (Nuxt) - Запуск новых приложений без разработчиков - Автоматическая публикация в сторах - Гибкие модели публикации - Опциональное приложение для продавцов / партнёров [Начать](https://ru.nerdy.pro/contact?intent=whitelabel-app-development) #### Корпоративный парк Большой масштаб и индивидуальная разработка **от 9 млн ₽** 3+ месяцев - Всё из Платформы для парка - Кастомный бэкенд и multi-tenant API - Продвинутый фиче-гейтинг - Отдельный аккаунт разработчика на приложение - Аналитика по всему парку - Выделенная поддержка и SLA [Начать](https://ru.nerdy.pro/contact?intent=whitelabel-app-development) > **Доступна индивидуальная оценка** > > Каждая white-label платформа уникальна. Эти диапазоны приблизительны — мы предоставим детальную оценку после того, как разберёмся, сколько брендов вы планируете запустить, какой у вас набор функций и какая стратегия публикации. Свяжитесь с нами для бесплатной консультации. ### Часто задаваемые вопросы Всё, что нужно знать о разработке white-label приложений #### Что такое white-label приложение? White-label приложение — это одно приложение, которое можно переоформить и переопубликовать как множество отдельных, независимо брендированных приложений. У каждого своё название, логотип, цвета и листинг в сторе, но все они работают на одной общей кодовой базе — поэтому вы создаёте и поддерживаете один продукт, выпуская множество брендированных приложений. #### Почему Flutter — лучший выбор для white-label приложений? Создание white-label приложений нативно означало бы поддержку двух кодовых баз (Swift и Kotlin) для каждого бренда. Flutter позволяет собирать iOS и Android для каждого бренда из одной кодовой базы на Dart, с пиксельной точностью тем и нативной производительностью. Одна правка или функция доходит до всего парка в следующем релизе, что делает этот подход единственным, при котором поддержка остаётся посильной с ростом масштаба. #### Как создаются и запускаются новые брендированные приложения? Через веб-админку, которую мы строим на Nuxt. Нетехнический менеджер вводит название бренда, загружает ресурсы, выбирает тему, цветовой режим и включённые функции, заполняет метаданные стора. Отправка формы генерирует конфигурацию бренда и запускает автоматизированный конвейер сборки и деплоя (на базе Fastlane), который собирает подписанные приложения и публикует их в App Store и Google Play. #### Могут ли у разных брендов быть разные функции? Да. Мы строим гибкую систему фиче-гейтинга, чтобы возможности можно было включать и отключать для каждого бренда из конфигурации. Премиум-бренды могут открывать продвинутые модули, региональные приложения — переключать функции под конкретный рынок, а пилотные бренды — получать ранний доступ, и всё это без изменения общего кода. #### Можно ли публиковать приложения под отдельными аккаунтами разработчика? Да — приложения могут публиковаться под единым аккаунтом разработчика, под отдельным аккаунтом на каждое приложение или в любом гибком сочетании. Модель публикации — ваш выбор, исходя из независимости брендов, партнёрских соглашений и политики сторов, и автоматизированный конвейер поддерживает все три варианта. Ритейл и программы лояльности — самая частая форма этой работы: кейс [платформы кэшбэка и лояльности](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform) показывает эту архитектуру в живом продукте, а наша страница [разработки e-commerce приложений](https://ru.nerdy.pro/services/flutter-app-development/e-commerce) описывает коммерческую сторону, к которой подключается такой парк приложений. ## Расширение команды на Flutter https://ru.nerdy.pro/services/team-augmentation ### Что такое Flutter staff augmentation Flutter staff augmentation — это модель найма, при которой проверенные Flutter-инженеры входят в вашу существующую команду и работают под вашим управлением — в вашем репозитории, спринтах и инструментах, — а трудоустройством, бенефитами и содержанием пула занимаемся мы. Вы получаете senior-инженеров на Flutter за недели вместо месяцев, которые уходят на наём в штат, и без долгосрочных обязательств перед штатным сотрудником. Модель создана для команд, у которых уже есть инженерное руководство и не хватает только рук на Flutter — чтобы успеть к дедлайну, закрыть пробел в навыках или гибко менять состав под роадмап. Если вы хотите передать продукт с понятным объёмом и полностью переложить ответственность за поставку на нас — это уже [разработка приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development). Не уверены, какая модель подходит? Мы разобрали это честно в посте [«Как нанять Flutter-разработчиков в 2026 году»](https://ru.nerdy.pro/blog/hire-flutter-developers-2026). - **6** Flutter-инженеров: Senior-разработчики, готовые встроиться в вашу команду - **8+ лет** Опыт во Flutter: Пишем на Flutter с 2018 года, в разработке с 2010 - **2-4 нед** Срок онбординга: От подбора до продуктивности в вашем коде - **4+ ч** Пересечение по времени: С рабочими часами ЕС и востока США ### Модели сотрудничества: выберите, насколько хотите управлять сами Staff augmentation — один из трёх способов работать с нами. Какой из них подойдёт, зависит от того, какую часть поставки вы хотите взять на себя. | Модель | Staff Augmentation (Максимум гибкости) | Выделенная команда | Проектная разработка | | --- | --- | --- | --- | | Кто управляет | Вы — ваши лиды и процесс | Самоуправляемая команда с нашим лидом | Мы — полная ответственность за поставку | | Когда подходит | Есть инженерное руководство, нужны руки | Нужен результат, но нет Flutter-управления | Чёткий объём и фиксированный дедлайн | | Скорость старта | От дней до 2-4 недель на инженера | 2-4 недели на сборку команды | 1-3 недели до старта | | Гибкость | Масштабирование вверх/вниз помесячно | Изменение состава команды на вехах | Только согласованный объём | | Модель оплаты | За инженера, помесячно | За команду, помесячно | Фиксированная стоимость проекта | > **Когда augmentation НЕ подходит** > > Staff augmentation работает, только если вы можете управлять инженерами — есть кому владеть архитектурой, ревьюить PR и расставлять приоритеты. Если такого ресурса нет, augmentation-инженеры начнут работать вразнобой, и результат окажется хуже, чем у агентства. В этом случае выбирайте [проектную разработку](https://ru.nerdy.pro/services/flutter-app-development) и отдавайте ответственность за результат нам. Честность экономит ваши деньги в любом случае. ### Как это работает От первого звонка до продуктивного инженера в вашем коде — пять шагов. 1. **Расскажите о задаче и стеке.** Роль, уровень, ваша архитектура и задача, которую нужно закрыть. 2. **Мы подбираем проверенных инженеров.** Шорт-лист из нашего пула — только те, кто подходит под стек и домен. 3. **Вы собеседуете и одобряете.** Решение за вами; никто не входит в команду без вашего «да». 4. **Онбординг за 2–4 недели.** Инженер подключается к репозиторию, спринтам и инструментам и начинает выпускать фичи. 5. **Масштабируйте вверх или вниз.** Добавляйте инженеров на пик и отпускайте, когда работа сделана, — на месячных условиях. ### Почему наши Flutter-инженеры Когда человек входит в вашу команду, вы доверяете его суждениям о вашем коде. Мы проверяем то, что реально отличает хорошего инженера, а не ключевые слова в резюме. #### Что доказал каждый наш инженер - Реально выпущенные приложения: Живут в App Store и Google Play — финтех, real-time и потребительские приложения, а не слайды - След в open-source: Опубликованные пакеты на pub.dev и контрибьюции, которыми пользуются разработчики по всему миру - Владение архитектурой и state management: Обоснованные мнения о Riverpod, BLoC и Provider — и когда что применять - Дисциплина тестирования и CI/CD: Автосборки, подпись, выкладка в сторы, feature flags и поэтапные раскатки Посмотреть, какие продукты выпускают наши инженеры, можно в [портфолио](https://ru.nerdy.pro/portfolio) — насыщенный графиками финтех-интерфейс [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) и полностью кастомная дизайн-система [Arcana](https://ru.nerdy.pro/portfolio/arcana) — а также в наших [open-source Flutter-пакетах](https://ru.nerdy.pro/open-source). С нашим augmentation вы получаете инженеров, которые уже прошли через сложные места продакшена на Flutter: platform channels, интеграцию нативных SDK и 60fps на слабых устройствах. Познакомьтесь с теми, кто войдёт в вашу команду, на [странице команды](https://ru.nerdy.pro/team). ### Ставки Прозрачные помесячные ставки за инженера. Мы — команда с корнями в Восточной Европе, поэтому наши смешанные ставки лежат в диапазоне **4,5–8,6 тыс. ₽/час**: senior Flutter-инженер за долю стоимости in-house в США, без комиссий рекрутерам и долгосрочных обязательств. #### Middle-инженер Уверенная поставка на вашем стеке **630–900 тыс. ₽** за инженера / месяц - 3-5 лет Flutter - Поставка функций в ваших спринтах - Работа внутри вашей архитектуры - Код-ревью и тестирование [Запросить разработчика](https://ru.nerdy.pro/contact?intent=staff-augmentation) #### Senior-инженер (Чаще всего берут) Ведёт фичи от начала до конца **900 тыс. ₽ – 1,3 млн ₽** за инженера / месяц - 5+ лет Flutter - Архитектура и технические решения - Platform channels и нативные SDK - CI/CD и релиз-инжиниринг - Менторит вашу команду [Запросить разработчика](https://ru.nerdy.pro/contact?intent=staff-augmentation) #### Lead / архитектор Задаёт направление команде **1,2–1,6 млн ₽** за инженера / месяц - 8+ лет, Flutter с ранних версий - Архитектура систем и приложений - Ведёт выделенную команду - Держит планку на код-ревью [Запросить разработчика](https://ru.nerdy.pro/contact?intent=staff-augmentation) > **Индивидуальная смета за два рабочих дня** > > Это типовые месячные диапазоны для инженера на полную занятость в вашей команде — точная ставка зависит от уровня, стека и длительности, доступны частичная занятость и команды из нескольких инженеров. Как augmentation соотносится с наймом in-house или агентства — в посте [«Как нанять Flutter-разработчиков в 2026»](https://ru.nerdy.pro/blog/hire-flutter-developers-2026) и в нашем [разборе стоимости](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026). [Расскажите, что нужно](https://ru.nerdy.pro/contact?intent=staff-augmentation) — и мы пришлём смету под ваш проект. ### Часто задаваемые вопросы Всё, что нужно знать о расширении команды нашими Flutter-инженерами. #### Что такое Flutter staff augmentation? Flutter staff augmentation — это модель, при которой вы встраиваете одного или нескольких проверенных Flutter-инженеров в свою существующую команду. Они работают в вашем репозитории, ваших спринтах и под вашим управлением, но наняты и предоставлены внешним партнёром. Это позволяет добавить senior-инженеров на Flutter за недели — без времени на рекрутинг, расходов на бенефиты и долгосрочных обязательств штатного найма. #### Чем staff augmentation отличается от найма агентства? При staff augmentation инженеры входят в вашу команду, и вы управляете ими каждый день, поэтому код и контекст остаются в вашем репозитории. При проектной разработке в агентстве поставкой управляет агентство и передаёт вам готовый продукт. Выбирайте augmentation, когда у вас есть инженерное руководство и не хватает рук; выбирайте проект в агентстве, когда хотите полностью передать очерченный объём. #### Как быстро разработчик может выйти? Проверенный инженер из нашего пула обычно выходит за несколько дней и становится продуктивным в вашем коде за две–четыре недели. Поскольку он уже знает Flutter, время на разгон уходит на ваш домен и архитектуру, а не на язык или тулинг. Это намного быстрее, чем два–четыре месяца, которые обычно занимает новый in-house-найм. #### Сколько стоит нанять выделенного Flutter-разработчика? Выделенный Flutter-разработчик через staff augmentation обычно стоит 560 000–1 440 000 ₽ за инженера в месяц в зависимости от уровня, при наших восточноевропейских смешанных ставках порядка 4 000–7 600 ₽/час. Вы платите помесячную ставку без комиссий рекрутерам и долгосрочных трудовых обязательств и можете остановиться, когда работа сделана. Смету под проект пришлём за два рабочих дня. #### Можно ли масштабировать команду вверх или вниз? Да — гибкость и есть суть модели. Вы можете добавить инженеров на пик и отпустить их, когда работа сделана, на месячных условиях, без выходных пособий и рекрутингового цикла. Это позволяет подгонять число Flutter-инженеров под роадмап, а не держать фиксированный штат. #### Кто управляет разработчиками? Вы. Augmentation-инженеры работают под вашими лидами, в вашем процессе и спринтах, как любой член команды, — поэтому augmentation работает, только если у вас есть ресурс инженерного управления. Если вы предпочли бы, чтобы ответственность за результат нёс кто-то другой, лучше подойдёт выделенная команда с нашим лидом или полная проектная разработка. #### Как вы проверяете Flutter-инженеров? У каждого инженера, которого мы предлагаем, есть реально выпущенные приложения, живущие в App Store и Google Play, а у большинства — публичный след на pub.dev или в open-source. Мы проверяем владение архитектурой и state management, дисциплину тестирования и CI/CD, опыт с platform channels и нативными SDK. Вы собеседуете и одобряете каждого инженера до его выхода — никто не попадёт в команду без вашего «да». ## Спасение и оптимизация производительности Flutter-приложений https://ru.nerdy.pro/services/flutter-app-development/app-rescue Спасение — это проект, который начинается с симптомов, а не со списка фич. Приложение уже существует, оно живое, и что-то в нём сбоит: падают кадры, обрываются сессии, растут краши, каждый релиз готовится дольше предыдущего — или команда, которая его строила, перестала отвечать. Спасательная работа скоупится по этим симптомам, выстраивается по серьёзности и выходит релизами в продукт, который всё это время остаётся в продакшене. Это не то же самое, что построить приложение, и доказательства здесь нужны другие. Пообещать пересборку может кто угодно; спасение судят по тому, перестала ли сбоить та конкретная вещь, которая сбоила, — измеримо и не сломав то, что ещё работает. Поэтому каждое спасение у нас начинается с аудита и письменного плана — и поэтому, когда план говорит «две недели исправлений», а не «переписывание», мы так и говорим. #### С чем к нам приходят - **Производительность** — подтормаживания на скролле, рывки анимаций, медленный старт, списки, задыхающиеся на реальных объёмах данных - **Стабильность** — растущий уровень крашей, обрывы сессий, утечки памяти, всплывающие только после минут реального использования - **Брошенные проекты** — агентство исчезло, в репозитории неразбериха, и никто не уверен, где лежат ключи подписи - **Кодовые базы, написанные ИИ** — сгенерированный прототип нашёл реальных пользователей и теперь должен их пережить; входная точка для таких случаев — [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit) - **Застрявшие роадмапы** — архитектура, в которой каждая фича выходит медленнее предыдущей: симптом столь же реальный, как краш ### Спасения, на которых стоит эта страница #### Живой телемедицинский продукт, стабилизированный в продакшене [YouMi](https://ru.nerdy.pro/portfolio/youmi) — сервис онлайн-консультаций с психологом, чьё Flutter-приложение жило в обоих сторах и не справлялось ровно с тем, ради чего существовало: сессии обрывались, сообщения в чате не доходили, а утечки памяти в навигационном стеке были такими, что приложение падало при переходах между экранами. За две недели мы перестроили WebSocket-слой с полноценным жизненным циклом соединения — автоматическое переподключение, очередь сообщений при смене сети, — добавили сквозные статусы доставки и переписали навигационную архитектуру на современных API роутинга Flutter. Приложение при этом ни разу не покинуло сторы. В телемедицине соединение и *есть* продукт — поэтому именно этот проект открывает страницу: спасение измеряется тем, ради чего продукт существует. #### Производительность, проверенная на пределе разумного Чтобы чинить медленный рендеринг, нужно уметь строить быстрый. [Arcana](https://ru.nerdy.pro/portfolio/arcana) держит чат из тысяч стриминговых markdown-сообщений при 60 fps; [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) держит частоту кадров под живым потоком рыночных данных. Эти потолки важны для спасательной работы, потому что калибруют диагноз: профилируя ваше приложение, мы отличаем «Flutter этого не может» — что редко бывает правдой — от «этот шторм перестроек можно убрать правильным скоупингом», что обычно и оказывается настоящим выводом. ### Как проходит спасение #### 1. Аудит Мы разбираем кодовую базу — архитектуру, управление состоянием, покрытие тестами, состояние зависимостей — в привязке к конкретным симптомам, из-за которых вы обратились, и профилируем приложение на реальных устройствах, а не доверяем симулятору. Вы получаете письменный отчёт с находками и последовательный план. Иногда этот план — две недели исправлений; иногда — поэтапный рефакторинг; изредка — честная рекомендация пересобрать, с приложенной аргументацией. #### 2. Стабилизация Симптомы высшей серьёзности исправляются первыми и выходят через поэтапные раскатки, чтобы облегчение дошло до пользователей в первых же релизах, а не в конце проекта. Приложение сохраняет свой релизный ритм — заморозка добавила бы к сбою, который у вас уже есть, ещё один: сбой роадмапа. #### 3. Закрепление Исправления обрастают тестами, чтобы сбой не мог тихо вернуться, CI прогоняет их на каждом изменении, а мониторинг крашей и производительности настраивается так, чтобы ловить регрессии раньше, чем это сделают отзывы. В этом разница между спасением и абонементом на повторные визиты. #### 4. Передача Проект заканчивается кодовой базой в состоянии, в котором ею может владеть команда — ваша, или наша в рамках [расширения команды](https://ru.nerdy.pro/services/team-augmentation), если хотите, чтобы рабочие руки остались. Отчёт с находками, план и мониторинг в любом случае остаются вашими. ### Дисциплины, на которые опирается спасение #### Работа с частотой кадров Проблемы производительности сначала профилируются, потом чинятся — на среднем железе, под реальными данными, с открытым таймлайном. Обычные находки: незаскоупленные перестройки, каскадом идущие по дереву виджетов; списки, перестраивающие строки, которые должны переиспользоваться; работа во время build, которой там не место. Производительность — свойство инженерии, а не фреймворка: та же дисциплина, что держит стриминговый чат на 60 fps, убирает рывки с продуктового экрана. #### Рефакторинг управления состоянием Многие сбоящие приложения сбоят структурно: состояние размазано по экранам, зависимости неявные, одно изменение отзывается пятью регрессиями. Мы рефакторим управление состоянием поэтапно — экран за экраном, под защитой тестов, пока приложение продолжает релизиться, — вместо того чтобы объявлять переписывание. Цель — архитектура, в которой следующая фича дешевле предыдущей: именно так «застрявший роадмап» из списка симптомов начинает двигаться в обратную сторону. #### Надёжность реального времени Оборванные сессии и недоставленные сообщения — это баги жизненного цикла: соединения без политики переподключения, сообщения без статусов доставки, сокеты, молча умирающие при смене сети. Лекарство — полноценный жизненный цикл соединения, построенный один раз, с ясным владельцем, — дисциплина, которую демонстрирует проект YouMi, и причина, по которой он занял две недели, а не квартал. #### Память и навигация Утечки, роняющие приложение после десяти минут использования, прячутся в удерживаемых экранах, слушателях, переживающих свои виджеты, и навигационных стеках, которые не отпускают то, что в них запушили. Мы переписываем навигацию на современных API роутинга Flutter там, где этого требует архитектура — у YouMi требовала, — и проверяем исправление профилями памяти, а не оптимизмом. #### Передачи от исчезнувших агентств Перехват начинается с археологии: доступ к репозиторию, ключи подписи, аккаунты в сторах, контракты бэкенда, которые никто не записал. Мы проходили такое восстановление раньше и знаем его ловушки — ключи, существующие только на ноутбуке бывшего подрядчика, витрины в сторах, оформленные не на тот аккаунт, — и первый результат аудита в таких случаях — просто инвентаризация того, что вы на самом деле контролируете. Это негламурная работа, но без неё никакая инженерия не имеет смысла. #### Когда это действительно переписывание Некоторые приложения приходят к нам уже за пределами экономически разумного ремонта: архитектура воюет с фреймворком, зависимости устарели на годы, тестов, на которые можно опереться при рефакторинге, нет. Когда аудит говорит это, мы это говорим — письменно, с аргументацией, — и разговор превращается в [пересборку с ограниченным объёмом](https://ru.nerdy.pro/services/flutter-app-development), опирающуюся на всё, что нашёл аудит. Чего мы делать не будем — выставлять счета за спасение кодовой базы, про которую уже знаем, что это снос. ### Сколько стоит спасение Аудит — проект с фиксированным объёмом: 450 тыс. ₽ – 1,4 млн ₽, тот же тариф полного аудита, что опубликован на странице [аудита AI-кода](https://ru.nerdy.pro/services/ai-code-audit), — машинерия одна и та же независимо от того, писало код агентство, внутренняя команда или модель. Работа по стабилизации затем скоупится по последовательному плану аудита, симптом за симптомом, — вы утверждаете работу порциями, а не подписываетесь вслепую. Если хотите, чтобы рабочие руки остались и после, действует опубликованная ставка [расширения команды](https://ru.nerdy.pro/services/team-augmentation): 900 тыс. ₽ – 1,3 млн ₽ за senior-инженера в месяц. Мы не называем цену стабилизации до аудита: цифра, произнесённая без взгляда на кодовую базу, — догадка в деловом костюме, а большинство команд, доходящих до спасения, ровно на таких сметах уже однажды обжигались. ### Частые вопросы Что чаще всего спрашивают команды, чьё Flutter-приложение сбоит в продакшене. #### Можете ли вы перехватить Flutter-приложение, которое построило другое агентство? Да — часть нашей лучше всего задокументированной работы началась ровно так, включая живой телемедицинский продукт, который мы стабилизировали, не выводя из продакшена. Перехват начинается с аудита кодовой базы и инвентаризации того, что вы на самом деле контролируете: доступ к репозиторию, ключи подписи, аккаунты в сторах, документация бэкенда. Вы получаете письменный отчёт с находками и последовательный план до любого разговора о пересборке — потому что иногда ответ — «две недели исправлений», а не переписывание, и когда это так, мы так и говорим. #### Как вы ускоряете медленное Flutter-приложение? Профилируя до того, как что-либо менять, — на среднем железе, под реальными объёмами данных, с открытым таймлайном производительности. Обычные причины — незаскоупленные перестройки виджетов, идущие каскадом по дереву, списки, перестраивающие строки вместо переиспользования, и тяжёлая работа во время build. Сам Flutter редко оказывается потолком: фреймворк держит 60 кадров в секунду на стриминговых, насыщенных анимацией экранах, когда рендеринг дисциплинирован, — так что лекарство почти всегда в правильном скоупинге работы, а не в борьбе с фреймворком. #### Нужно ли нам полное переписывание? Обычно нет, и честный ответ даёт именно аудит. У большинства сбоящих приложений есть конкретные, исправимые причины — отсутствующий жизненный цикл соединения, размазанное состояние, утечки в навигации, — с которыми справляется последовательный рефакторинг, пока приложение продолжает релизиться. Изредка кодовая база действительно за пределами экономически разумного ремонта — тогда аудит говорит это письменно, с аргументацией, и разговор превращается в пересборку с ограниченным объёмом. Единственное, чего не стоит принимать ни от кого, — рекомендацию переписать, сделанную до чтения кода. #### Что вам нужно от нас, чтобы начать? Доступ к репозиторию, проект, который собирается, и всё, что есть из аккаунтов в сторах, ключей подписи и документации бэкенда, — плюс симптомы так, как вы их видите, простым языком. Недостающие части — норма для спасательных ситуаций; часть аудита при перехвате — установить, что вы на самом деле контролируете, и восстановить то, что возможно. Актуальная продакшен-сборка и доступ к краш-репортингу, если он есть, заметно сокращают диагностику. #### Останется ли приложение живым во время спасения? Да — это требование к формату работы, а не выражение надежды. Исправления выходят через поэтапные раскатки в порядке серьёзности, так что пользователи чувствуют облегчение в первых же релизах, а приложение сохраняет обычный релизный ритм. Именно так мы стабилизировали живой телемедицинский продукт, не выводя его из сторов. Спасение, требующее выключить продукт, — это пересборка под чужим именем, и мы сказали бы вам это ещё на этапе аудита. #### Можете ли вы спасти Flutter-приложение, сгенерированное ИИ? Да, и это всё более частый запрос: сгенерированный прототип находит реальных пользователей и встречается с продакшеном, где быстро всплывают дыры в безопасности, неконтролируемые расходы на API и срезанные углы в архитектуре. Наш аудит AI-кода существует ровно для такого профиля кодовой базы и проходит так же, как любой спасательный аудит: отчёт с находками, план по серьёзности, затем исправления. Сгенерированный код не безнадёжен — он просто ломается по паттернам, которые мы научились искать первыми. Прочитайте [кейс YouMi](https://ru.nerdy.pro/portfolio/youmi) — спасение, на котором построена эта страница; загляните на страницу [аудита AI-кода](https://ru.nerdy.pro/services/ai-code-audit) — про машинерию аудита; или начните с пилларной страницы [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ## Add-to-App: Flutter внутри вашего нативного приложения https://ru.nerdy.pro/services/flutter-app-development/add-to-app Add-to-app — официальный механизм Flutter для встраивания Flutter-модуля в существующее нативное приложение для iOS или Android. Приложение-хост остаётся ровно тем, чем было: ваша кодовая база на Swift или Kotlin, ваш релизный процесс, ваши пользователи, — а Flutter появляется внутри него одним скомпилированным модулем и рисует те экраны, которые вы решите ему отдать. Каждый из этих экранов пишется один раз и работает на обеих платформах. Эта страница — для команд, которые хотят Flutter *внутри* приложения, которое у них уже есть: нативный продукт, который никто не собирается переписывать, но каждая следующая фича обходится вдвое дороже, потому что её приходится строить отдельно для iOS и Android. Если ваша настоящая цель — со временем оказаться целиком на Flutter, это другой проект с той же механикой: читайте [миграцию с React Native и натива](https://ru.nerdy.pro/services/flutter-app-development/flutter-migration) — та страница честно называет её переписыванием. Здесь не переписывается ничего: нативное приложение остаётся продуктом, а Flutter зарабатывает своё место фича за фичей. #### Какие проекты мы берём - **Пилотная фича** — одна ограниченная по объёму фича, построенная на Flutter и выпущенная внутри обоих ваших нативных приложений, где стоимость интеграции и отдача измеряются, а не обсуждаются на словах - **Выравнивание функциональности** — экран, который есть в вашем iOS-приложении и которого нет в Android-версии (или наоборот), построенный один раз для обеих платформ вместо второго раза для одной - **Новая поверхность** — программа лояльности, флоу бронирования, чат: самостоятельное дополнение к зрелому нативному продукту, выходящее на обе платформы за один бюджет - **Фундамент для внедрения** — каркас модуля, интеграция роутера, обвязка CI и контракты platform channels, настроенные как следует, чтобы фичи дальше выпускали ваши собственные инженеры ### Кому это подходит — а кому лучше мигрировать Add-to-app окупается при конкретных условиях, и мы предпочитаем назвать их прямо, а не продавать интеграцию команде, которой нужно другое. #### Признаки того, что add-to-app подходит - Каждая фича роадмапа строится дважды, а iOS- и Android-версии приложения начали расходиться по возможностям - Приложение слишком большое или слишком проверенное, чтобы его переписывать, поэтому «просто возьмите Flutter» никогда не было реальным вариантом - Вы хотите оценить Flutter на настоящей продакшен-работе — одна фича, реальные пользователи, замеры — прежде чем брать на себя что-то большее - Две платформенные команды трудно укомплектовать поровну, и новые поверхности буксуют на той платформе, где в этом квартале не хватает рук #### Когда это НЕ правильный выбор - Приложение настолько маленькое, что свежая сборка на Flutter или поэкранная [миграция](https://ru.nerdy.pro/services/flutter-app-development/flutter-migration) обойдётся дешевле, чем поддержка шва между нативом и Flutter, — у интеграции есть накладные расходы, и маленький хост их не амортизирует - Решение прийти к 100% Flutter уже принято — тогда ведите проект как миграцию с первого дня, с картой миграции, а не сползайте в неё незаметно - Приложение уже на React Native — встраивание второго кроссплатформенного рантайма рядом с первым усугубляет проблему; реалистичные варианты для такой команды — остаться как есть или мигрировать - Нужная вам фича — глубоко платформенная: интерфейс, построенный на виджетах, или функциональность прежде всего для Watch, — здесь Flutter не тот инструмент, и мы так и скажем > **Честный вывод:** гибридное приложение — это постоянный шов: два тулчейна, два набора идиом и граница, которую обязан понимать каждый инженер команды. Add-to-app стоит этой цены, когда хост слишком большой, чтобы его переписывать, а роадмап продолжает платить налог на две платформы. Если ни то ни другое не про ваше приложение, лучшим ответом будет одна из соседних страниц — и на первом созвоне мы скажем, какая именно. ### Как проходит поэтапное внедрение #### Как устроен путь add-to-app 1. **Нативное приложение остаётся продуктом** Ваши iOS- и Android-приложения продолжают выходить без изменений — без заморозки, без параллельного переписывания, без перехода на горизонте 2. **Встраивается модуль Flutter** Один модуль Flutter подключается к обеим оболочкам и использует их навигацию, аутентификацию и аналитику через зафиксированные контракты 3. **Выходит пилотная фича** Ограниченная фича строится один раз на Flutter и выпускается внутри обоих приложений — с замерами размера и времени старта до и после 4. **Внедрение растёт фича за фичей** Каждая новая поверхность, попадающая в модуль, — та, которую вы не строили дважды, и каждая — это решение, а не обязательство 5. **Финал решаете вы** Оставайтесь гибридом сколько угодно, передайте модуль своей команде или превратите набранный темп в полную миграцию: каждая граница фичи — точка остановки Этот процесс сознательно использует ту же машинерию, что и наша [услуга миграции](https://ru.nerdy.pro/services/flutter-app-development/flutter-migration): модуль Flutter за общим роутером, экраны переносятся по одному. Разница — в пункте назначения: миграция в конце выводит нативный хост из эксплуатации, а внедрению это не обязательно. А значит, позже внедрение без потерь превращается в миграцию, если модуль её заслужит, — и ничего не выбрасывается. ### Чего add-to-app требует на самом деле #### Один движок с управляемым жизненным циклом Модуль Flutter приносит с собой движок Flutter, а движок — ресурс, которым хост обязан управлять осознанно: прогретый заранее, чтобы первый Flutter-экран открывался без видимой паузы на запуск; общий для всех точек входа, а не создаваемый на каждый экран; освобождаемый, когда платформа просит память назад. Ошибка здесь невидима в демо и очевидна в продакшене. Это первое, что мы строим, а не последнее, что тюним. #### Навигация через границу Пользователям всё равно, какой фреймворк отрисовал экран, на котором они находятся, и навигация обязана это учитывать: жесты «назад» ведут себя так, как принято на платформе, диплинки приземляются на Flutter-экраны так же надёжно, как на нативные, а состояние переживает переход в обе стороны. Контракт роутера между хостом и модулем — та часть архитектуры add-to-app, которая решает, будет шов невидимым или станет постоянным источником багов. #### Общие сервисы: заимствовать, а не дублировать В вашем приложении уже есть аутентификация, аналитика, сеть и фиче-флаги. Модуль Flutter должен заимствовать их через platform channels, а не отращивать собственные копии — именно так у гибридного приложения появляются две сессии, две схемы событий и метрики, которым никто не доверяет. Точное определение этих канальных контрактов — большая часть работы над фундаментом, и она окупается на каждой следующей фиче. #### Два тулчейна в одном пайплайне После интеграции ваш CI/CD собирает модуль Flutter и обе нативные оболочки, а версии модуля приходится согласовывать с двумя релизными циклами, которые не всегда движутся синхронно. Мы встраиваем сборку модуля в ваш существующий пайплайн — кэшируемую, воспроизводимую и живущую в вашем репозитории, а не на чьём-то ноутбуке, — потому что интеграция, которую не может собрать ваш CI, — не интеграция. #### Шов, с которым смогут жить ваши дизайнеры Flutter рисует свои пиксели сам, и это работает в обе стороны: он не унаследует ваши нативные компоненты, но точно воспроизведёт вашу дизайн-систему — как только токены, типографика и отступы перенесены в тему модуля. Мы делаем этот перенос в рамках фундамента, чтобы пользователь, скроллящий с нативного экрана на Flutter-экран, не мог понять, где проходила граница. #### Издержки: измеренные, а не заявленные Встраивание Flutter добавляет реальный вес: порядка нескольких мегабайт к Android-релизу и несколько больше на iOS, плюс запуск движка при первом открытии Flutter-экрана. Точные цифры зависят от вашего приложения — поэтому в поставку пилота входят замеры размера бинарника и времени старта до и после: решение идти дальше вы принимаете по данным собственного продукта, а не по бенчмарку из чьего-то блога. ### Прецеденты в продакшене Flutter поддерживает модель интеграции add-to-app уже много лет, и её самый известный пользователь — Google Pay, внедривший Flutter внутрь существующего нативного приложения с сотнями миллионов пользователей, прежде чем идти дальше. Паттерн — ровно тот, что описывает эта страница: встроить, выпустить одну поверхность, замерить, затем решить. Наш собственный опыт с этой машинерией пришёл из [миграционных проектов](https://ru.nerdy.pro/services/flutter-app-development/flutter-migration), где та же связка «модуль за роутером» переносит целые приложения; внедрение использует её же, но с куда меньшими обязательствами. ### Как мы работаем **Фундамент и пилот с фиксированным объёмом.** Мы интегрируем модуль в обе оболочки, переносим дизайн-токены, определяем канальные контракты и выпускаем первую фичу — ограниченный проект, где замеры входят в поставку. Наш [процесс разработки](https://ru.nerdy.pro/blog/flutter-app-development-process) описывает, как мы ведём такую работу неделя за неделей. **Встроенные инженеры.** Наши Flutter-инженеры входят в вашу нативную команду — в ваш репозиторий и ваши спринты — и развивают модуль рядом с людьми, которые владеют хостом, — см. [расширение команды](https://ru.nerdy.pro/services/team-augmentation). Это естественный формат, когда пилот себя доказал и модуль стал местом, где регулярно оказываются задачи вашего роадмапа. В обоих случаях смету под задачу мы присылаем в течение двух рабочих дней после того, как разберёмся в требованиях. ### Сколько стоит интеграция add-to-app Проект «фундамент плюс пилот» оценивается после того, как мы увидим приложение: стоимость интеграции определяет хост — его навигационная архитектура, его система сборки, то, как устроен доступ к его общим сервисам, — куда сильнее, чем сама пилотная фича, поэтому цифра, названная до взгляда на кодовую базу, была бы фикцией. Как ориентир: ограниченный пилот стоит заметно меньше самостоятельного приложения — опубликованные тарифы на странице [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development) начинаются с 1,4–2,7 млн ₽ за полноценный MVP, а пилотная фича внутри существующего хоста — лишь доля этого объёма. Для модели со встроенными инженерами действует опубликованная ставка [расширения команды](https://ru.nerdy.pro/services/team-augmentation): 900 тыс. ₽ – 1,3 млн ₽ за senior-инженера в месяц. А если честный финал роадмапа — приложение целиком на Flutter, называйте проект своим именем и оценивайте его как [миграцию](https://ru.nerdy.pro/services/flutter-app-development/flutter-migration), которую та страница сравнивает с полной пересборкой, — а не как внедрение, переросшее своё название. ### Частые вопросы Что чаще всего спрашивают команды, которые думают о Flutter внутри существующего нативного приложения. #### Можно ли добавить Flutter в существующее iOS- или Android-приложение? Да — ровно для этого и существует модель интеграции add-to-app у Flutter. Модуль Flutter встраивается в ваше существующее приложение на Swift или Kotlin, подключается к его навигации, аутентификации и аналитике через platform channels и рисует те экраны, которые вы решите в нём построить. Ваше приложение всё это время продолжает выходить без изменений; модуль добавляется рядом с существующим кодом, а не вместо него. Каждый Flutter-экран пишется один раз и работает внутри обоих ваших приложений — и iOS, и Android. #### Придётся ли мигрировать всё приложение на Flutter? Нет. Внедрение и миграция используют одну механику — модуль Flutter за общим роутером, — но миграция обязуется в конце вывести нативное приложение из эксплуатации, а внедрению это не обязательно. Каждая граница фичи — полноправная точка остановки: одни продукты остаются гибридом неограниченно долго, отдав Flutter несколько поверхностей, другие передают модуль собственной команде, третьи превращают набранный темп в полную миграцию, когда модуль её заслужил. Ничего построенного во время внедрения не выбрасывается, если позже вы решите пойти дальше. #### Насколько встраивание Flutter увеличивает размер приложения? Движок и модуль Flutter добавляют реальный вес — порядка нескольких мегабайт к релизной сборке Android и несколько больше на iOS. Точная цифра зависит от вашего приложения и содержимого модуля — поэтому наш пилотный проект включает замеры размера бинарника и времени старта до и после на вашем реальном продукте. Решение о внедрении вы принимаете по собственным цифрам, а не по обобщённому бенчмарку. #### Имеет ли add-to-app смысл для приложения на React Native? Механически это работает: Flutter встраивается в хост на React Native так же, как в полностью нативный. Стратегически это редко имеет смысл: вы получите два кроссплатформенных рантайма, два моста и три UI-идиомы в одном бинарнике — что усугубляет проблему сопровождения, а не решает её. Для команды на React Native, недовольной положением дел, реалистичные варианты — остаться как есть или мигрировать на Flutter экран за экраном; наша страница миграции начинается с того, кому не стоит делать ни того ни другого. #### Проверен ли add-to-app в продакшене? Да. Add-to-app — модель интеграции, которую Flutter поддерживает уже много лет, и её самый известный пользователь — Google Pay, встроивший Flutter в нативное приложение с сотнями миллионов пользователей, прежде чем ставить на него больше. Паттерн «встроить модуль, выпустить одну поверхность, замерить, затем решить» давно отработан. Оставшиеся инженерные риски — детали интеграции: жизненный цикл движка, навигация через границу, продублированные сервисы, — и именно их работа над фундаментом закрывает в первую очередь. #### Нужно ли нашим нативным инженерам учить Dart? Для пилота — нет: его строим мы, а ваша команда ревьюит границу модуля, а не Dart внутри него. Если внедрение Flutter растёт, часть ваших инженеров захочет работать в модуле, а Dart — небольшой шаг от Swift или Kotlin: строгая типизация, сборка мусора — и он куда ближе к тому, что они уже пишут, чем JavaScript. Модель со встроенными инженерами существует ровно для этого перехода: наши люди строят рядом с вашими, пока модуль не станет кодовой базой, которой ваша команда владеет, а не просто хостит. Прочитайте [страницу миграции](https://ru.nerdy.pro/services/flutter-app-development/flutter-migration), если пункт назначения — целиком Flutter; посмотрите [расширение команды](https://ru.nerdy.pro/services/team-augmentation) — про модель со встроенными инженерами; или начните с пилларной страницы [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ## Платформа детекции ботов в реальном времени https://ru.nerdy.pro/portfolio/realtime-bot-detection-platform ### Обзор проекта Значительную долю трафика, приходящего на коммерческий сайт, генерируют не люди. Часть этого трафика безобидна: поисковые краулеры, мониторинг доступности, генераторы превью. Остальное — нет: подстановка учётных данных, скрейперы, боты, выкупающие товар, и автоматические клики, которые тихо расходуют рекламные бюджеты и отравляют каждую цифру в аналитике, на основании которой бизнес принимает решения. Проблема в том, что всё это приходит через ту же входную дверь, что и настоящие клиенты, а к моменту, когда оно попадает в недельный отчёт, деньги уже потрачены. Наш клиент решил закрыть этот разрыв платформой, отвечающей на один вопрос — *человек это или машина?* — пока визит ещё продолжается. Не ночной batch-процесс и не отчёт: вердикт, возвращаемый в тот же момент, когда загружается страница, чтобы системы самого клиента могли отреагировать немедленно. Мы восемь месяцев строили этот продукт целиком — от браузерного сборщика сигналов через сервисы скоринга до дата-платформы под ними. Работа шла в течение 2022 года и завершилась в 2023-м. > Проект закрыт соглашением о неразглашении. Клиент, название продукта и пользовательский интерфейс опущены, а архитектура ниже описана на уровне формы, а не деталей реализации. ### Задача Инженерную работу определяли три ограничения, и каждое тянуло в свою сторону. **Всё должно было работать в реальном времени.** Вердикт, приходящий после того, как посетитель уже сконвертировался — или уже выжег бюджет, — это журнал аудита, а не защита. Скоринг должен был укладываться внутрь запроса, в бюджет задержки, достаточно малый, чтобы клиент добровольно поставил его в критический путь загрузки собственной страницы. **Всё должно было выдерживать высокую нагрузку.** Платформа работала под настоящим high-load: **десятки тысяч запросов в минуту** по всей клиентской базе, с трафиком, который взлетал без предупреждения — запуск кампании на стороне клиента и атака с другой стороны для планировщика мощностей выглядят одинаково. Пик никогда не был средним, и систему нужно было рассчитывать на пик — но так, чтобы на средней нагрузке она оставалась приемлемой по стоимости. **Всё должно было оставаться доказуемым.** Каждый вердикт должен был объясняться и допускать повторный разбор постфактум. Клиенты не действуют на основании необъяснённой оценки, а ни логика детекции, ни модели за ней не улучшаются, если под рукой нет сырых сигналов, породивших вчерашние решения. Это означало хранить всё — задача долговременного хранения и аналитики, стоящая прямо за задачей задержек. Реальное время, большой объём и полное хранение — три требования, которым не удовлетворяет ни одна отдельная база данных. Бо́льшая часть архитектуры ниже следует из отказа поступиться хотя бы одним из них. ### Сбор сигналов на чистом JavaScript Клиентская часть платформы — сборщик, который работает внутри страницы клиента и собирает доказательства, на основе которых выносится вердикт: характеристики браузера и окружения, поведение рендеринга и таймингов, паттерны взаимодействия и множество мелких несоответствий, отличающих настоящий браузер, которым управляет человек, от автоматизированного, надевшего user agent настоящего браузера. Он написан на **чистом JavaScript** — без фреймворка, без рантайма, добавляемого на этапе сборки, без зависимостей. Это было осознанное ограничение, а не предпочтение: - **Он работает на чужой странице.** Сборщик встроен в тысячи сайтов, которые мы не контролируем, рядом со всем, что эти сайты ещё загружают. Он не может рассчитывать на модульную систему, набор полифилов или конкретный базовый уровень браузера — и не должен конфликтовать с тем, что уже есть на странице. - **Он должен быть маленьким и быстрым.** Всё, что отгружается каждому посетителю каждого клиента, измеряется относительно перформанс-бюджета самого клиента. Рантайм фреймворка оказался бы больше, чем весь сборщик целиком. - **Он работает во враждебной среде.** Объекты измерения активно пытаются его обойти. Сбор сигналов должен быть устойчив к странице, которая может быть инструментирована, пропатчена или эмулирована вокруг него, — а это аргумент в пользу кода без прослоек между ним и браузерными API, которые он читает. Работа сборщика заканчивается на сборе и отправке сигналов. Все суждения выносятся на сервере, где логика не видна тому, о ком выносится суждение. ### Go на горячем пути Каждый сервис на пути запроса написан на **[Go](https://ru.nerdy.pro/technologies/go)**. Нагрузка близка к идеальной для языка форме: огромное количество мелких независимых, преимущественно I/O-bound запросов, которые нужно обрабатывать конкурентно с предсказуемой задержкой. На практике важнее всего оказались два свойства. Горутины сделали конкурентность на уровне запроса достаточно дешёвой, чтобы приём, обогащение и скоринг могли внутренне разветвляться без настройки пула потоков. А рантайм Go дал ту картину хвостовых задержек, на которой строилось продуктовое обещание: в системе реального времени важен не средний ответ, а самые медленные несколько процентов — ровно там, где более тяжёлый рантайм и тратит свой бюджет. Сам путь намеренно короткий: принять и провалидировать, определить контекст, прогнать правила детекции и модели по входящим сигналам, вернуть вердикт. Всё, что не обязано произойти до ответа — сохранение, агрегация, обогащение, обратная связь для моделей, — вытеснено с горячего пути в асинхронный конвейер позади него. ### От одной базы к дата-платформе Платформа стартовала на **[PostgreSQL](https://ru.nerdy.pro/technologies/postgres)**, и на старте это было правильным решением. Одна база хранила события, аккаунты, конфигурацию и агрегаты; один язык запросов отвечал на любой вопрос; весь продукт можно было держать в голове целиком, пока он ещё нащупывал свою форму. Столкновения с продакшен-объёмом это не пережило, и причину стоит сформулировать точно: дело было не в том, что «PostgreSQL слишком медленный», а в том, что от одного хранилища требовали быть четырьмя разными вещами одновременно. Высокочастотный append-only приёмник событий, хранилище быстрых выборок на пути запроса, аналитическое хранилище для агрегатов по миллиардам строк и поисковый индекс для разбора отдельных сессий — это четыре нагрузки с четырьмя противоречащими друг другу наборами требований. Тюнинг под любую из них ухудшал остальные. Поэтому мы их разделили и перевели платформу в **[Google Cloud](https://ru.nerdy.pro/technologies/gcp)**: **Redis** — состояние горячего пути. [Redis](https://ru.nerdy.pro/technologies/redis) держит всё, к чему решение о скоринге должно обратиться за единицы миллисекунд: свежее состояние по посетителю и сессии, счётчики частоты и интенсивности, вычисленную конфигурацию и проверки репутации. Именно это делает вердикт внутри запроса возможным в принципе, и именно это забирает на себя объём чтений, который иначе лёг бы на основную базу. **Pub/Sub** — шов между синхронным и асинхронным. Каждый оценённый запрос публикуется как событие, а ответ возвращается немедленно; всё, что дальше, читает уже оттуда. Это та развязка, которая позволяет пути запроса оставаться коротким, а остальную платформу — масштабировать, передеплоивать или временно притормаживать так, чтобы эндпоинт скоринга этого не заметил. И это же означает, что всплеск трафика попадает в очередь, а не в базу данных. **Объектное хранилище** — сырые события, навсегда. Каждый пакет сигналов архивируется в S3-совместимое объектное хранилище в исходном виде. Это самое дешёвое надёжное место для данных, будущее применение которых ещё неизвестно, и именно поэтому логику детекции можно ретроспективно прогонять по реальному историческому трафику, а не по его сводке. **BigQuery** — аналитический слой. Агрегации по всей истории событий, отчётность по качеству трафика, когортный анализ и анализ кампаний, а также исследовательская работа, стоящая за новыми эвристиками детекции — всё это выполняется здесь, в хранилище, построенном под сканы такого размера, и полностью вне операционного пути. **Elasticsearch** — расследования. Когда клиент оспаривает вердикт или аналитик идёт по следу нового паттерна, вопрос звучит как «покажи мне вот эти сессии с шестью фильтрами» — это интерактивный поиск, а не агрегация. Эту задачу решает Elasticsearch, поэтому исследовательские запросы никогда не касаются систем, обслуживающих трафик. **PostgreSQL** остался, но с гораздо более узкой зоной ответственности: аккаунты, конфигурация клиентов и те реляционные данные, которым действительно нужны транзакции и ограничения целостности. Именно снятие нагрузок, для которых он никогда не был предназначен, снова сделало его подходящим инструментом. ### Kubernetes в Google Cloud Платформа работает как контейнеризованные сервисы в кластере **[Kubernetes](https://ru.nerdy.pro/technologies/kubernetes)** в Google Cloud. С таким профилем трафика альтернатива всерьёз и не рассматривалась: нагрузка, меняющаяся на порядок за минуты, требует инфраструктуры, которая добавляет мощность сама, и сервисов, каждый из которых дёшево реплицировать. Здесь окупается разделение на небольшие сервисы на Go. У приёма, скоринга и асинхронных консьюмеров совсем разные кривые масштабирования, и, поскольку это отдельные деплойменты, каждый масштабируется по своему сигналу: слой приёма — по частоте запросов, консьюмеры — по глубине очереди. Rolling-деплои позволяли обновлять эндпоинт скоринга в рабочее время без окна обслуживания, что для сервиса, стоящего в пути загрузки страницы клиента, было жёстким требованием, а не удобством. ### Результаты Клиент завершил проект с продакшен-платформой, которая оценивает живой трафик в реальном времени, выдерживает десятки тысяч запросов в минуту с запасом на всплески в несколько раз выше и сохраняет каждый сырой сигнал, который когда-либо видела, — так что улучшение детекции, сделанное сегодня, проверяется на годах реального трафика, а не на синтетическом бенчмарке. Не менее важно, что архитектура разделяет зоны ответственности по тем границам, которые действительно значимы для бизнеса: горячий путь остаётся маленьким, быстрым и скучным, а анализ, расследования и работа с моделями происходят за очередью, где они могут расти сколь угодно, никогда не угрожая времени ответа, которое видит клиент. Именно такие системы мы берём целиком: высоконагруженные сервисы на [Go](https://ru.nerdy.pro/technologies/go), событийная дата-платформа и инфраструктура [Kubernetes](https://ru.nerdy.pro/technologies/kubernetes), на которой всё это работает. Сам сборщик — JavaScript без зависимостей, работающий в тысячах страниц, которые мы не контролируем, — это дисциплина, которую описывает наша страница [встраиваемых виджетов и скриптов](https://ru.nerdy.pro/services/web-development/embeddable-widgets). Если у вас похожая нагрузка — [расскажите нам о ней](https://ru.nerdy.pro/contact). ## Платформа кэшбэка и лояльности https://ru.nerdy.pro/portfolio/cashback-loyalty-platform ### Обзор проекта Наш клиент управляет платформой кэшбэка, которая позволяет офлайн-магазинам — кафе, салонам, бутикам, локальной рознице — вознаграждать своих клиентов и, что не менее важно, наконец-то узнать их поближе. У офлайн-бизнеса редко есть те данные о клиентах, которые онлайн-магазины принимают как должное: кто их постоянные посетители, когда они заходили в последний раз, что заставляет их возвращаться. Платформа закрывает этот пробел, вкладывая программу кэшбэка и лояльности в руки каждого участвующего магазина, а брендированное приложение — в карман каждого клиента. Бизнес-модель требует не одного приложения, а целого *семейства*: флагманского потребительского приложения, где покупатели находят участвующие магазины и отслеживают кэшбэк, отдельного приложения для мерчантов, чтобы магазины подключались и вели собственные программы вознаграждений, и растущего каталога white-label приложений — каждое из них полностью брендированное, самостоятельно публикуемое приложение кэшбэка для партнёрского бренда или розничной сети, и все они работают на одном продукте. Со временем экосистема выросла до **15+ работающих мобильных приложений**. Инженерная задача состояла не в том, чтобы хорошо сделать одно приложение, — а в том, чтобы создать единую кодовую базу, которая может стать любым числом брендированных приложений кэшбэка без линейного роста затрат на поддержку с каждым новым партнёром. Мы отвечали за всю систему целиком: общий потребительский клиент, приложение для мерчантов, парк white-label приложений, движок правил кэшбэка и бэкенд-сервисы, а также внутренние админ-инструменты, которые превратили «запуск нового брендированного приложения» из многодневной инженерной задачи в форму самообслуживания. > Проект закрыт соглашением о неразглашении. Клиент, партнёрские бренды, стоящие за white-label приложениями, и опубликованные листинги в сторах не называются — на скриншотах ниже продукт показан на тестовых данных. ![Главный экран приложения с балансом кэшбэка и бонусом за приглашение](https://ru.nerdy.pro/projects/cashback-loyalty-platform/images/01.webp) ![Страница магазина со ставкой кэшбэка за покупку](https://ru.nerdy.pro/projects/cashback-loyalty-platform/images/02.webp) ![Лента ваучеров и новостей магазинов](https://ru.nerdy.pro/projects/cashback-loyalty-platform/images/03.webp) ![Настройка отдельного white-label приложения](https://ru.nerdy.pro/projects/cashback-loyalty-platform/images/05.webp) ![Список white-label приложений в админке](https://ru.nerdy.pro/projects/cashback-loyalty-platform/images/06.webp) ### Одна кодовая база, много приложений Флагманское потребительское приложение и каждый white-label вариант работают на **единой кодовой базе**. Нет форков, нет веток под отдельные бренды и нет скопированных проектов, которые нужно синхронизировать. То, что различается между приложениями — брендинг, тема, фирменный стиль, фиче-флаги, конфигурация вознаграждений и метаданные для сторов, — выражено целиком через конфигурацию, которая живёт вне кода. Мы построили клиент вокруг системы flavor-ов и конфигураций: единый бинарный «чертёж», который определяет фирменный стиль на этапе сборки из конфигурационного бандла конкретного приложения. Цвета, типографика, логотипы, сплэш-экраны, иконки приложений, идентификаторы бандлов и наборы включённых функций инжектируются для каждого flavor-а. Ошибка, исправленная один раз, или функция, выпущенная один раз, попадает во все приложения парка в следующем релизе — нет расхождений между брендами, которые нужно поддерживать. Важно, что рычагами, определяющими каждый бренд, управляют **менеджеры без навыков программирования**. Через админ-интерфейс нетехнический сотрудник задаёт для нового приложения **иконки, цвета, яркость (светлую или тёмную тему) и часть подписей** — и эти настройки попадают прямо в сборку без участия разработчика. Те, кто отвечает за внешний вид бренда, могут менять его напрямую, а не заводить тикет и ждать инженеров. Это и есть ключевой экономический рычаг проекта: предельная стоимость шестнадцатого приложения близка к нулю, потому что это тот же код, что и у первого, только с другой конфигурацией. ### Нативная конфигурация сборки на Dart Жизнеспособность парка white-label приложений зависит от нативной конфигурации сборки — настроек Android и iOS, которые действительно важны для сторов. Идентификаторы приложений и бандлов, отображаемые названия приложений, номера версий и сборок, настройка подписи, ресурсы иконок и сплэш-экранов, а также записи по каждому flavor-у в `build.gradle` и в проекте Xcode — всё это должно быть корректным для каждого отдельного приложения и каждой отдельной сборки. Вместо того чтобы поддерживать это вручную, мы написали **собственный инструментарий на Dart, который программно управляет конфигурацией сборки для Android и iOS**. Получив конфигурационный бандл бренда, инструмент перезаписывает соответствующие записи в Gradle, манифесте, `Info.plist` и каталоге ресурсов под нужный flavor перед запуском сборки. Хранение этой логики на Dart означает, что она использует тот же источник истины, что и Flutter-клиент, и выполняется как полноценный шаг нашего процесса сборки — поэтому нативная конфигурация бренда генерируется детерминированно из его конфигурации и никогда не правится вручную. ### Приложение для мерчантов Офлайн-магазинам нужен был собственный инструмент — не урезанная версия потребительского приложения, а специально созданное приложение для управления программой вознаграждений и понимания своих клиентов. Из приложения для мерчантов владелец магазина за считанные минуты подключается к платформе, задаёт свои правила кэшбэка и скидок, подтверждает покупки клиентов для начисления кэшбэка и наконец видит, кто его клиенты: сколько новых, сколько вернувшихся, как часто они заходят и как работает каждая акция. Для многих из этих бизнесов это первое структурированное представление о своей клиентской базе за всё время. Мы построили его на том же фундаменте Flutter и общей библиотеке компонентов, что и потребительские приложения, поэтому обновления дизайн-системы и платформенные исправления распространяются на обе аудитории, а специфичные для мерчантов сценарии живут в собственных модулях. ### Настраиваемые правила кэшбэка и скидок Сердце платформы — гибкий **движок правил**, который позволяет каждому магазину проектировать собственные вознаграждения без нашей помощи. Через приложение для мерчантов магазин составляет правила: - **Кэшбэк за покупку** — процент или фиксированная сумма, возвращаемая на баланс клиента за каждую подходящую покупку. - **Бонус на день рождения** — вознаграждение, автоматически начисляемое около дня рождения клиента, чтобы вернуть его в магазин. - **Бонус за приглашение** — реферальное вознаграждение, которое выплачивается, когда действующий клиент приглашает друга, тот присоединяется и совершает первую покупку. Правила — это данные, а не код: каждый магазин выбирает своё сочетание, задаёт суммы, лимиты и сроки действия, а движок применяет их в момент покупки. Поскольку правила живут в конфигурации, магазин может сам запустить акцию ко дню рождения или изменить ставку кэшбэка — мгновенно, во всём своём приложении, — и мы не выпускаем для этого ни одного релиза. ### Админ-интерфейс и автоматизированный сборочный конвейер Самая заметная часть системы — внутренний админ-интерфейс. Подключение нового white-label бренда раньше было инженерной задачей: создать конфигурацию, зарегистрировать идентификаторы бандлов, сгенерировать ресурсы для подписи, настроить листинги в сторах и добавить новый таргет в CI. Мы свели всё это в единое веб-приложение, которым менеджеры управляют без написания кода. Менеджер заполняет форму — название бренда, тема, ресурсы, метаданные стора, набор функций — и система делает остальное: **Генерация конфигурации**: Админ-интерфейс сохраняет конфигурацию и ресурсы нового бренда и формирует конфигурационный бандл для конкретного flavor-а, который использует Flutter-клиент. **Регистрация в конвейере**: Таргет нового приложения автоматически регистрируется в CI/CD конвейере. Сборочная система подхватывает новый flavor, запускает наш собственный инструментарий на Dart для применения нативной конфигурации Android и iOS и выпускает подписанные артефакты для обеих платформ. **Запуски в режиме самообслуживания**: То, что раньше требовало участия разработчика для каждого нового бренда, стало повторяемым процессом на основе формы. Бизнес может масштабировать число опубликованных приложений, не масштабируя размер инженерной команды. ### Автоматизация сборки и деплоя на Fastlane Весь процесс сборки и релиза автоматизирован от начала до конца с помощью [Fastlane](https://fastlane.tools) — open-source инструментария для автоматизации сборки и развёртывания мобильных приложений. Мы используем его, чтобы стандартизировать каждый шаг, который иначе был бы ручным и подверженным ошибкам в масштабе 15+ приложений: управление подписью и provisioning-профилями, сборку и подпись бинарников iOS и Android, а также публикацию каждого приложения в [App Store](https://docs.fastlane.tools/getting-started/ios/appstore-deployment/) и [Google Play](https://docs.fastlane.tools/getting-started/android/release-deployment/) вместе с метаданными и скриншотами. Поскольку все приложения используют единую конфигурацию Fastlane, параметризованную по flavor-у, релиз всего парка — это единая повторяемая операция. Новый white-label бренд наследует ровно тот же проверенный путь деплоя, что и флагманское приложение, — без написания отдельных скриптов релиза. Админ-интерфейс — это приложение на Nuxt, работающее поверх наших PHP-сервисов и хранилища данных PostgreSQL, с инструментами на Ruby и Fastlane, оркестрирующими автоматизацию сборки и релиза. ### Бэкенд и сервисы В основе каждого приложения лежит общий бэкенд, который ведёт **реестр кэшбэка (ledger)**, движок правил, профили клиентов и конфигурацию по брендам. Единый набор сервисов обеспечивает работу всего парка приложений, а контекст бренда определяется для каждого запроса — поэтому клиент в любом white-label приложении, во флагманском приложении или магазин в приложении для мерчантов обращается к одному и тому же проверенному API, ограниченному его брендом. Реестр фиксирует каждое начисление и списание, поэтому балансы всегда поддаются аудиту, а профили клиентов дают каждому магазину то понимание своей аудитории, которого офлайн-рознице обычно не хватает. ### Отложенный deep linking и бонус за приглашение Фича, ради которой всё это понадобилось, — **бонус за приглашение**. Платформа растёт за счёт рефералов: довольный клиент приглашает друга, тот нажимает на ссылку-приглашение — но приложения у него, как правило, ещё нет. С обычным deep link контекст приглашения терялся в момент, когда друг попадал в стор: он устанавливал приложение, открывал его, оказывался на обычном главном экране — и связь между пригласившим и приглашённым исчезала, а вместе с ней и бонус за приглашение, обещанный обеим сторонам. Самый вирусный момент в продукте был сломан на самом важном шаге. Мы починили его с помощью **отложенного deep linking**, чтобы приглашение переживало установку. Поскольку весь парк использует одну кодовую базу, фича была построена один раз, и каждое брендированное приложение — флагман, приложение мерчанта и все white-label варианты — унаследовало её автоматически. Мы реализовали её штатными механизмами самих платформ, а не сторонним сервисом: лёгкий **iOS App Clip** перехватывает ссылку-приглашение ещё до того, как существует полное приложение, и передаёт её через общий контейнер App Group, а **Google Play Install Referrer API** несёт полезную нагрузку на Android. Очередь отложенных ссылок удерживает приглашение до тех пор, пока приложение не будет готово, а новый клиент не зарегистрируется и не войдёт в аккаунт, — только тогда реферал атрибутируется и бонус за приглашение начисляется и пригласившему, и новому клиенту, поэтому вознаграждение срабатывает корректно, даже несмотря на то, что путь проходит через установку и первый вход при холодном старте. Полный технический разбор подхода — включая то, почему перестало работать распространённое решение, — мы описали в статье [Отложенный deep linking после Firebase Dynamic Links: App Clips + Play Install Referrer](https://ru.nerdy.pro/blog/deferred-deep-linking-app-clips-install-referrer). ### Результаты Теперь клиент может запустить полностью брендированное, готовое к публикации приложение кэшбэка для нового партнёра за долю того времени, что требовалось раньше, — не отвлекая инженеров от продуктовой работы под каждый запуск. Архитектура с общей кодовой базой держит 15+ приложений в едином ритме: одно исправление, одна функция, один релиз доходят до всех сразу. Админ-интерфейс превратил повторяющееся инженерное «узкое место» в возможность самообслуживания, а автоматизированный конвейер сделал каждый новый запуск предсказуемым и повторяемым событием, а не отдельным проектом. А офлайн-магазины, которые раньше работали вслепую, теперь сами проводят кампании кэшбэка и скидок и впервые знают, кто их клиенты. Экономика в цифрах: - **15+ работающих приложений из одной кодовой базы** — без форков, без веток под отдельные бренды, без расхождений, которые пришлось бы сводить перед релизом. - **Предельная стоимость следующего брендированного приложения близка к нулю** — новый бренд это конфигурационный бандл плюс метаданные для сторов, а не новый инженерный проект. - **Запуск бренда превратился из многодневной инженерной задачи в форму самообслуживания**, которой управляют менеджеры без навыков программирования. - **Единая кодовая база на Flutter обходится примерно на 30–40% дешевле двух нативных приложений** — и поскольку на ней работает весь парк, эта экономия срабатывает заново с каждым брендом, накапливаясь по всем 15+ приложениям. Именно такие платформы мы создаём в рамках [разработки white-label приложений](https://ru.nerdy.pro/services/whitelabel-app-development) — одна кодовая база на Flutter, на которой работает целый парк брендированных приложений, — и этот проект стал опорным для направления лояльности и кэшбэка в нашей практике [разработки e-commerce приложений](https://ru.nerdy.pro/services/flutter-app-development/e-commerce). Админ-интерфейс, запускающий каждый бренд, — ровно тот внутренний бэк-офис, который описывает наша страница [админ-панелей и дашбордов](https://ru.nerdy.pro/services/web-development/admin-dashboards). ### FAQ #### Что такое white-label платформа приложений? Это единая кодовая база, которую можно ребрендировать и публиковать как любое число отдельных приложений с собственным фирменным стилем. На этой платформе флагманское приложение кэшбэка и каждое партнёрское приложение используют один и тот же Flutter-код; всё, что различается — цвета, логотипы, иконки, идентификаторы бандлов, фиче-флаги, конфигурация вознаграждений, метаданные для сторов, — выражено целиком через конфигурацию бренда, живущую вне кода. #### Как одна кодовая база на Flutter может обслуживать больше 15 разных приложений? Через систему flavor-ов и конфигураций: единый бинарный «чертёж» определяет фирменный стиль на этапе сборки из конфигурационного бандла конкретного приложения, а собственный инструментарий на Dart перезаписывает нативную конфигурацию сборки Android и iOS — записи Gradle, манифесты, Info.plist, каталоги ресурсов — под целевой бренд перед каждой сборкой. Ошибка, исправленная один раз, или функция, выпущенная один раз, попадает во все приложения парка в следующем релизе. #### Как новое брендированное приложение запускается без разработчиков? Через внутренний админ-интерфейс. Менеджер заполняет форму — название бренда, тема, иконки, цвета, метаданные для сторов, набор функций — а система генерирует конфигурационный бандл, регистрирует новое приложение в CI-конвейере и выпускает подписанные артефакты для iOS и Android. Затем Fastlane публикует каждое приложение в App Store и Google Play вместе с метаданными и скриншотами, поэтому релиз всего парка — это единая повторяемая операция. #### Не отклоняют ли сторы white-label приложения? Они отклоняют парки тонких, почти одинаковых «перекрасок» — именно для этого существует правило 4.2.6 у Apple. White-label платформа должна изначально проектироваться так, чтобы проходить ревью: каждое приложение в этом парке — полноценный брендированный продукт с собственной идентичностью, листингом в сторе и настроенным под бренд набором функций и вознаграждений, а не шаблон с заменённым логотипом. Конвейер публикации также поддерживает отдельные аккаунты разработчика для брендов, когда этого требуют партнёрские соглашения или политика стора. #### Сколько стоит парк white-label приложений по сравнению с отдельными приложениями? Первое приложение несёт стоимость платформы — общей кодовой базы, системы тем и фиче-флагов, движка правил, админ-панели и автоматизированного конвейера. Каждое следующее приложение обходится радикально дешевле, потому что новый бренд — это конфигурация, а не проект. Вдобавок единая кодовая база на Flutter обходится примерно на 30–40% дешевле, чем поддержка отдельных нативных приложений для iOS и Android, и эта экономия накапливается по всему парку. #### Как бонус за приглашение переживает то, что приложение ещё не установлено? Через отложенный deep linking, построенный на штатных механизмах платформ: лёгкий iOS App Clip перехватывает ссылку-приглашение до того, как существует полное приложение, и передаёт её через общий контейнер App Group, а на Android полезную нагрузку несёт Google Play Install Referrer API. Очередь отложенных ссылок удерживает приглашение, пока новый клиент не установит приложение, не зарегистрируется и не войдёт в аккаунт, — только тогда реферал атрибутируется и бонус начисляется обеим сторонам. ## Arcana https://ru.nerdy.pro/portfolio/arcana ### О проекте Arcana — AI-компаньон для чтения карт таро: пользователи общаются с ботом через чат-интерфейс и получают персональные расклады. Бэкенд с логикой AI и знаниями о таро разрабатывала команда клиента. Наша зона ответственности — Flutter-клиент: быстрый, тщательно проработанный чат-интерфейс для iOS и Android. Сама природа продукта сразу обозначила техническую проблему. Сессии в чате становятся длинными. Пользователи возвращаются ежедневно, история накапливается и может насчитывать тысячи сообщений. При этом AI стримит ответы в реальном времени — выводит форматированный текст с жирными выделениями, курсивом, заголовками и маркированными списками. Выстроить чат-экран, который корректно справляется с обоими сценариями при 60 fps на бюджетном железе, — вот в чём состояла суть нашей работы. ![Карта дня](https://ru.nerdy.pro/projects/arcana/images/01.webp) ![Стриминг ответа в чате](https://ru.nerdy.pro/projects/arcana/images/02.webp) ![Расклад карт](https://ru.nerdy.pro/projects/arcana/images/03.webp) ### Стриминг в реальном времени через SSE Ответы AI поступают потоком токенов, а не единым пакетом. Мы реализовали SSE-клиент на Flutter, который открывает постоянное HTTP-соединение с бэкендом и обрабатывает поток токенов по мере их поступления. Каждый входящий фрагмент добавляется к активному сообщению в чате, создавая привычный эффект «печатания» — пользователь видит, как интерпретация появляется слово за словом. Работа с SSE во Flutter потребовала аккуратного управления жизненным циклом соединения: переподключение при обрывах сети без потери частично полученного контента, буферизация неполных UTF-8-последовательностей на границах чанков, а также обновление только виджета активного сообщения, а не всего списка, при каждом новом токене. ### Рендеринг Markdown на лету AI форматирует ответы в Markdown: жирный шрифт для названий карт, курсив для ключевых слов, заголовки для разделов расклада, списки для ключевых тезисов. Поэтому рендерер чата должен был парсить и отображать Markdown инкрементально — обновляясь в реальном времени по мере поступления токенов. Мы разработали собственный инкрементальный рендерер Markdown, который обрабатывает растущую строку при каждом обновлении, не перепарсивая всё сообщение с начала. Неполные токены в конце текущего буфера (незакрытый `**` или недописанный заголовок) удерживаются в состоянии ожидания и отображаются как обычный текст, пока разделитель не закроется. Это исключает мерцание и скачки макета, сохраняя корректное форматирование по мере завершения ответа. ### Бенчмарки производительности на бюджетных устройствах Длинные истории чатов — норма для приложения с ежедневным использованием. Мы проектировали систему под 5 000+ сообщений без деградации и подтвердили это структурированными бенчмарками на бюджетных Android-устройствах — телефонах, представляющих нижний ценовой сегмент, где требования к производительности Flutter самые жёсткие. Наш набор бенчмарков прокручивал списки сообщений разного объёма (500, 2 000 и 5 000 сообщений) с одновременным активным SSE-стримингом, фиксируя время кадра на протяжении всего теста. В начальных сборках при быстром скролле с отметки 2 000+ сообщений наблюдались просадки кадров: проходы лейаута для пузырьков с Markdown-рендерингом становились дорогими. Мы устранили это несколькими точечными оптимизациями: **Ленивое кэширование лейаутов**: Отрендеренные Markdown-лейауты кэшируются по ID сообщения. Как только для пузырька рассчитан лейаут, его размеры и дерево виджетов переиспользуются при последующих прокрутках вместо повторного вычисления. **Виртуализация через Sliver**: Список чата построен на `SliverList` с кастомным делегатом, который не строит виджеты за пределами экрана. Инстанцируются только сообщения в пределах или вблизи видимого вьюпорта — дерево виджетов остаётся неглубоким независимо от общего числа сообщений. **Изолированные ребилды стрима**: Потоковое сообщение внизу списка управляется отдельно от истории. Обновления SSE-токенов инициируют точечный ребилд только виджета активного пузырька, не затрагивая виртуализированный список. После этих оптимизаций время кадра стабильно удерживалось ниже 16 мс во всех сценариях бенчмарка — включая историю из 5 000 сообщений с активным стримингом — на целевых бюджетных устройствах. ### Результаты Итоговый клиент остаётся плавным и отзывчивым при любой глубине истории и на любом устройстве целевого диапазона. Потоковые ответы отрисовываются в реальном времени с корректным Markdown-форматированием, а постоянные пользователи с сотнями сессий не замечают замедлений. Запас по производительности позволяет продукту расти, не пересматривая архитектуру рендеринга. Клиент Arcana был создан в рамках нашей [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development), где производительность рендеринга — один из ключевых приоритетов, и остаётся самым сложным кейсом рендеринга чата в нашей практике [разработки социальных и комьюнити-приложений](https://ru.nerdy.pro/services/flutter-app-development/social). ## ExtraETF https://ru.nerdy.pro/portfolio/extraetf ExtraETF — мобильная платформа для анализа ETF и фондового рынка с инструментами для отслеживания портфеля и исследования рынка. В центре проекта — обновления в реальном времени, удобная аутентификация и премиум-функции по подписке. ![Онбординг](https://ru.nerdy.pro/projects/extraetf/images/01.webp) ![Страница новостей](https://ru.nerdy.pro/projects/extraetf/images/02.webp) ![Страница тем](https://ru.nerdy.pro/projects/extraetf/images/03.webp) ![Статья](https://ru.nerdy.pro/projects/extraetf/images/04.webp) ![Страница акций](https://ru.nerdy.pro/projects/extraetf/images/05.webp) ![Детали акций](https://ru.nerdy.pro/projects/extraetf/images/06.webp) ### Задача Isarvest GmbH пришла к нам за мобильным приложением, которое сделает инвестирование в ETF доступнее — за счёт аналитики и рыночных данных в реальном времени. Приложению предстояло справляться со сложной визуализацией данных, живыми обновлениями цен и премиум-функциями и при этом оставаться быстрым и удобным на обеих платформах — iOS и Android. ### Наш подход Мы выбрали **Flutter**: приложение пишется один раз и выходит на обеих платформах одновременно. Это помогло уложиться в сжатые сроки запуска, не жертвуя качеством кода и паритетом функций. Система виджетов Flutter и его скорость отрисовки хорошо легли на финансовые графики и потоки данных в реальном времени, вокруг которых построено приложение. Для живых обновлений цен мы написали WebSocket-сервис на **Go**, который транслирует рыночные данные и веб-, и мобильным клиентам. Модель конкурентности Go и легковесные горутины держат тысячи одновременных WebSocket-соединений при скромном расходе ресурсов. ### Технические достижения То, что функции создавались один раз и выходили сразу везде, заметно ускорило разработку. Горячая перезагрузка сократила циклы итераций, а единая кодовая база избавила от целого класса платформенных расхождений. WebSocket-сервис на Go хорошо показал себя в продакшене: обновления цен доходили до клиентов с низкой задержкой даже по мере роста пользовательской базы, а серверы оставались дешёвыми в эксплуатации. Интеграция нативных функций платформы — OAuth от Apple и Google, push-уведомления, встроенные покупки — прошла гладко благодаря экосистеме плагинов Flutter: приложение ощущается нативным на каждой платформе, не теряя преимуществ общей кодовой базы. ### Влияние на бизнес Технологические решения напрямую отразились на бизнес-результатах: - **Шесть месяцев от старта проекта до выхода в оба магазина приложений** — с паритетом функций с первого дня: без запуска «сначала iOS» и без платформы, догоняющей другую. - **Время выхода на рынок сократилось примерно на 40%** по сравнению с двумя параллельными нативными разработками — эта цифра получается сама собой, когда за одну и ту же работу перестают платить дважды. - **Около 99% мобильного кода — общие для iOS и Android**; платформенная часть — несколько сотен строк нативной «прослойки» для биллинга, OAuth и push-уведомлений. - **Весь мобильный стек ведёт одна инженерная команда**, поэтому расходы на поддержку не выросли — а две нативные команды тянули бы каждая свой цикл обновлений. Приложение вышло в App Store и Google Play и хорошо удерживает пользователей — во многом благодаря плавной работе и данным в реальном времени. Подписка работает на интеграции встроенных покупок и приносит стабильный доход, а архитектура масштабируется вместе с аудиторией. ### Ключевые функции #### Push-уведомления Рыночные оповещения и обновления портфеля в реальном времени сообщают о заметных движениях цен, новостях рынка и персональной инвестиционной аналитике. Система уведомлений рассчитана на частые обновления, но экономит заряд батареи и учитывает настройки пользователя. #### Аутентификация и социальный вход Вход через OAuth от Apple и Google — безопасная аутентификация в одно касание. Регистрация становится проще, а за безопасность аккаунта отвечают доверенные провайдеры идентификации. #### Deep Linking Universal Links и App Links ведут из веб-контента, email-кампаний и социальных сетей прямо на нужные экраны приложения. Пользователи могут делиться друг с другом конкретными ETF, портфелями и рыночной аналитикой. #### Интерактивные графики Графики показывают рыночные данные на разных таймфреймах, с техническими индикаторами и инструментами сравнения. Они оптимизированы под мобильное взаимодействие: масштабирование щипком, управление жестами и отзывчивость даже на больших наборах данных. #### Поддержка тем Тёмная и светлая темы следуют предпочтениям пользователя и системным настройкам, так что приложением комфортно пользоваться при любом освещении. Дизайн-система сохраняет согласованность и читаемость в обеих темах. #### Управление подписками Премиум-функции продаются через встроенные покупки, а подписками пользователь управляет привычным способом — через App Store и Google Play. Система обрабатывает пробные периоды, тарифы подписки и восстановление покупок на разных устройствах. ### FAQ #### Что такое ExtraETF? ExtraETF — мобильное приложение для анализа ETF и фондового рынка, созданное для Isarvest GmbH: рыночные данные в реальном времени, интерактивные графики, отслеживание портфеля и премиум-функции по подписке на iOS и Android. Мобильную часть мы построили на Flutter, а слой данных реального времени — на WebSocket-сервисе на Go. #### Почему для ExtraETF выбрали Flutter? Одной команде нужно было выпускать одни и те же функции на iOS и Android в сжатые сроки, а приложению с финансовыми графиками — полностью распоряжаться своими пикселями: настоящий график — это кастомная отрисовка, а не стандартный UI-виджет. Flutter даёт и то и другое: единую кодовую базу на Dart и холст для прямой отрисовки. В готовом приложении около 99% мобильного кода общие для обеих платформ, а нативной «прослойки» для биллинга, OAuth и push — всего несколько сотен строк. #### Как ExtraETF работает с рыночными данными в реальном времени? WebSocket-сервис на Go транслирует живые цены веб- и мобильным клиентам, а горутины позволяют держать тысячи одновременных соединений при скромном расходе ресурсов. Обновления агрегируются ещё на сервере до стабильного темпа — примерно одно обновление на инструмент в секунду, ровно столько, сколько нужно человеку, следящему за ценой. Это сохраняет низкую задержку и разумную нагрузку на процессор и трафик клиентов по мере роста аудитории. #### Сколько времени заняла разработка ExtraETF? Шесть месяцев от старта до запуска в App Store и Google Play — с паритетом функций между платформами с первого дня. По сравнению с двумя параллельными нативными разработками время выхода на рынок сократилось примерно на 40%: бизнес-логика, графики и экраны писались один раз, а не дважды. #### Справляется ли Flutter с тяжёлыми финансовыми графиками? Да — ExtraETF по замыслу насыщен графиками: несколько таймфреймов, технические индикаторы, инструменты сравнения. Flutter сам отрисовывает каждый пиксель, поэтому код графиков работает одинаково на обеих платформах, а вынесение тяжёлых геометрических расчётов из фазы отрисовки сохраняет плавность взаимодействия в рамках бюджета кадра 16,6 мс даже на больших наборах данных. ExtraETF создан на Flutter и Go в рамках нашей услуги [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development). Инженерный разбор — как WebSocket-сервис на Go веером рассылает рыночные данные в реальном времени тысячам клиентов и как графики держат 60fps во Flutter — читайте в статье [Как мы построили финтех-приложение реального времени на Flutter и Go](https://ru.nerdy.pro/blog/extraetf-realtime-fintech-flutter-go). ExtraETF — референсный проект нашей практики [разработки финтех-приложений](https://ru.nerdy.pro/services/flutter-app-development/fintech). ## Formtastic https://ru.nerdy.pro/portfolio/formtastic ### Обзор проекта Formtastic — это сервис для создания форм и опросов, используемый в бизнес-сценариях с высокими требованиями к стабильности, скорости и гибкости интерфейса. По мере роста проекта стало ясно, что дальнейшее развитие требует не только обновления клиентских технологий, но и целенаправленной оптимизации серверной части. ![Экран авторизации](https://ru.nerdy.pro/projects/formtastic/images/01.webp) ![Главный экран](https://ru.nerdy.pro/projects/formtastic/images/02.webp) ![Редактор форм](https://ru.nerdy.pro/projects/formtastic/images/03.webp) ![Список шаблонов](https://ru.nerdy.pro/projects/formtastic/images/04.webp) ![Редактор шаблонов](https://ru.nerdy.pro/projects/formtastic/images/05.webp) ### Миграция с Django на Nuxt Изначально фронтенд Formtastic был реализован с использованием шаблонов Django. Это позволило быстро стартовать, но со временем такой подход стал ограничивать развитие продукта. Интерфейс становился всё сложнее, изменения требовали участия бэкенд-разработчиков, а сервер выполнял слишком много работы по рендерингу HTML, что негативно влияло на масштабируемость. В результате мы архитектурно разделили систему. Django остался основным бэкендом и API, в то время как весь пользовательский интерфейс был перенесён на отдельный фронтенд на Nuxt. Это обеспечило чёткое разделение ответственности и позволило интерфейсу развиваться независимо от серверной логики. После миграции скорость разработки UI заметно выросла. Команда начала быстрее выпускать новые экраны и улучшать пользовательский опыт, а нагрузка на сервер снизилась, так как он перестал заниматься рендерингом страниц. Nuxt с SSR и SSG также обеспечил значительный выигрыш в SEO и скорости загрузки, что положительно повлияло на маркетинг и публичные страницы. Такая форма миграции — фронтенд на серверных шаблонах или single-page приложение, переезжающие на Nuxt, — ровно то, о чём наша страница [разработки на Nuxt](https://ru.nerdy.pro/services/web-development/nuxt-development). Параллельно начала развиваться серверная часть. В областях с повышенной нагрузкой и строгими требованиями к производительности часть логики была извлечена из Django и реализована на Go в виде отдельных сервисов. Это снизило задержки, упростило горизонтальное масштабирование и устранило узкие места без полной переработки бэкенда. ### Миграция с нативных приложений на Flutter Мобильные клиенты Formtastic изначально разрабатывались как два независимых нативных приложения — для iOS и Android. По мере роста продукта этот подход становился всё менее эффективным: разработка замедлялась, функции выпускались несинхронно, а поддержка двух кодовых баз увеличивала общую стоимость владения. Переход на Flutter позволил нам объединить мобильную разработку в единую кодовую базу. Это значительно упростило поддержку и сделало выпуск новых функций предсказуемым. Обновления начали выходить одновременно на обеих платформах, а интерфейс и поведение приложения стали полностью согласованными для всех пользователей. С точки зрения бизнеса эффект был прямым: затраты на разработку и поддержку снизились, время выхода на рынок ускорилось, а команда получила возможность быстрее тестировать гипотезы и развивать продукт без пропорционального увеличения затрат. При этом Flutter позволил сохранить высокую производительность и качественный пользовательский опыт. Если конкретно, объединение изменило три показателя: - **Две кодовые базы стали одной.** Каждая функция, каждое исправление и каждый пограничный случай теперь пишутся один раз, а не дважды — работа из разряда «забыли добавить это в Android» исчезла полностью. - **Расхождение релизов сошло на нет.** Раньше релиз для iOS мог опережать релиз для Android на неделю и больше; теперь одна сборка уходит в оба магазина одновременно. - **Годовые затраты на платформенные обновления сократились примерно вдвое.** Один цикл обновления Flutter заменил два отдельных захода на обновления ОС, повышение требований SDK и приведение приложений в соответствие с политиками магазинов. #### iOS-приложение ![Список документов](https://ru.nerdy.pro/projects/formtastic/images/iphone_01.webp) ![Редактор форм](https://ru.nerdy.pro/projects/formtastic/images/iphone_02.webp) ![Список форм](https://ru.nerdy.pro/projects/formtastic/images/iphone_03.webp) ![Синхронизация документов](https://ru.nerdy.pro/projects/formtastic/images/iphone_04.webp) ![Заставка](https://ru.nerdy.pro/projects/formtastic/images/iphone_05.webp) #### iPad-приложение ![Экран авторизации](https://ru.nerdy.pro/projects/formtastic/images/ipad_01.webp) ![Главный экран](https://ru.nerdy.pro/projects/formtastic/images/ipad_02.webp) ![Редактор форм](https://ru.nerdy.pro/projects/formtastic/images/ipad_03.webp) ![Поиск форм](https://ru.nerdy.pro/projects/formtastic/images/ipad_04.webp) ![Диалог создания документа](https://ru.nerdy.pro/projects/formtastic/images/ipad_05.webp) > Долгое время Formtastic существовал в виде двух отдельных нативных приложений — одного для iOS, другого для Android. Каждую функцию приходилось делать дважды, релизы на двух платформах расходились по времени, а поддержка двух кодовых баз незаметно съедала наш роадмап. > > Команда Nerdy Production объединила всё в единую кодовую базу на Flutter, не нарушив работу продукта, на который полагаются наши клиенты. Теперь оба приложения получают одни и те же функции одновременно, опыт идентичен на iPhone и Android, а время вывода в продакшен резко сократилось. Мы поддерживаем одну кодовую базу вместо двух, и команда наконец тратит силы на новые функции, а не на синхронизацию двух приложений. *[Julian Garbotz](https://www.linkedin.com/in/julian-garbotz), Управляющий директор, Formtastic GmbH* ### Голосовой ввод для полей форм Длинные текстовые поля — заметки по осмотру, отчёты об инцидентах, открытые ответы в опросах — самая медленная часть заполнения формы с телефона. Мы добавили голосовой ввод на базе **[Whisper](https://ru.nerdy.pro/technologies/whisper)** и реализовали его по-разному для каждого клиента, а не подгоняли оба под одну архитектуру. В веб-приложении аудио записывается в браузере и отправляется на новый эндпоинт транскрипции на существующем бэкенде Django — это меньшее изменение, поскольку аутентификация и хранение для этого запроса и так уже на стороне Django. В приложениях на Flutter Whisper встроен прямо в бинарник в виде сборки на GGML, работающей через FFI, так что транскрипция идёт на устройстве: ничего не отправляется на сервер, нет обращений по сети, и всё продолжает работать вообще без сигнала — что полезно на объекте, где как раз и заполняется значительная часть форм Formtastic. В итоге получилась одна и та же функция, за которой стоят два разных компромисса. Веб-путь остаётся простым, потому что бэкенд, который можно было расширить, уже был. Мобильный путь расплачивается увеличенным размером приложения и вычислениями на устройстве — зато диктовка работает офлайн и аудио никуда не уходит. ### Итоги Formtastic — это пример продуманной технологической эволюции продукта. Перенос фронтенда с Django на Nuxt упростил разработку веб-версии и обеспечил выигрыш в скорости и SEO. Целенаправленное использование Go на сервере решило проблемы производительности без радикальных изменений платформы. Переход с нативных мобильных приложений на Flutter снизил затраты на владение и ускорил развитие продукта. В результате система стала более стабильной, гибкой и лучше подготовленной к масштабированию. ### FAQ #### Что такое Formtastic? Formtastic — немецкая платформа для создания цифровых форм и опросов, применяемая в бизнес-сценариях с высокими требованиями к стабильности, скорости и гибкости интерфейса. Она работает как веб-приложение плюс мобильные приложения для iOS, iPad и Android, а за ними стоит бэкенд на Django и Go. #### Почему Formtastic заменил два нативных приложения одним на Flutter? Основные затраты на продукт были уже не в разработке мобильных приложений, а в их поддержке: две кодовые базы, два релизных цикла и два прохода QA на каждую функцию, а релизы на платформах расходились по времени. Объединение в единую кодовую базу на Flutter означает, что каждая функция пишется один раз, оба магазина получают один и тот же релиз одновременно, а годовой объём работ по платформенным обновлениям сократился примерно вдвое. #### Нарушила ли миграция на Flutter работу существующих пользователей? Нет — объединение прошло без сбоев для продукта, на который полагаются клиенты. Приложения на Flutter заменили нативные с тем же набором функций и единообразным интерфейсом на обеих платформах, а обновления с этого момента стали выходить одновременно на iOS и Android, а не сначала на одной платформе. #### Как голосовой ввод работает без подключения к интернету? В мобильных приложениях Whisper встроен прямо в бинарник в виде сборки на GGML, работающей через FFI, поэтому транскрипция происходит целиком на устройстве: аудио никуда не загружается, обращений по сети нет, и диктовка продолжает работать вообще без сигнала — что полезно на объектах, где заполняется значительная часть форм Formtastic. Веб-приложение выбирает другой компромисс и отправляет аудио на эндпоинт транскрипции на существующем бэкенде Django. #### Почему веб-фронтенд перенесли с шаблонов Django на Nuxt? Шаблоны Django позволили быстро стартовать, но привязывали каждое изменение интерфейса к бэкенд-разработчику и тратили серверное время на рендеринг HTML. Выделение интерфейса в отдельный фронтенд на Nuxt дало UI возможность развиваться независимо, ускорило разработку экранов, снизило нагрузку на сервер и улучшило SEO и скорость загрузки публичных страниц за счёт серверного рендеринга. Объединение двух нативных приложений Formtastic в единую кодовую базу — именно та миграция, которую мы выполняем в рамках [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development), а работа веб-приложения на Nuxt рядом с этими приложениями, на том же бэкенде на Django и Go, — именно та работа, которую описывает наша страница [парных веб-приложений](https://ru.nerdy.pro/services/web-development/companion-web-app). ## Jepta https://ru.nerdy.pro/portfolio/jepta ### О проекте Jepta — гиперлокальная социальная сеть, построенная вокруг места, где вы живёте. Вместо глобального графа подписок она организует людей по близости: чаты, привязанные к адресу или дому, групповые чаты для тех, кто живёт по одному адресу, и каналы, которые ведут пекарня, мастерская или клуб в паре сотен метров от вас. Всё в приложении — предлагаемые чаты, каналы в ленте, посты, которые вы видите первыми, — ранжируется по расстоянию от текущей геопозиции. Компания Netgineers GmbH обратилась к нам, когда продукт был уже определён, а бэкенд — в работе. Требовалась мобильная часть: одна кодовая база на Flutter для iOS и Android — для продукта с необычно широкой для первого релиза поверхностью. ![Онбординг](https://ru.nerdy.pro/projects/jepta/images/01.webp) ![Запрос геопозиции](https://ru.nerdy.pro/projects/jepta/images/02.webp) ![Список чатов](https://ru.nerdy.pro/projects/jepta/images/03.webp) ![Лента канала](https://ru.nerdy.pro/projects/jepta/images/04.webp) ![Информация о канале](https://ru.nerdy.pro/projects/jepta/images/05.webp) ![Чат с каналом](https://ru.nerdy.pro/projects/jepta/images/06.webp) ![Новый пост](https://ru.nerdy.pro/projects/jepta/images/07.webp) ### Задача Две вещи делали этот проект сложнее обычной разработки. #### Масштаб поверхности экранов Jepta — это не одна функция с настройками вокруг неё. Личные чаты, групповые чаты, чаты по адресу, лента каналов, профили каналов с часами работы и галереями фотографий, переписка канала с пользователем, редактор постов с настройкой комментариев для каждого поста, вкладка активности, вкладка профиля, поиск, добавление контактов по QR-коду, работа с геопозицией и разрешениями — всё это в первом релизе и в шестимесячном окне. При таком количестве экраны перестают быть независимыми единицами работы: без общего скелета каждый новый тянет за собой свои особенности навигации, свои состояния загрузки и пустого экрана, свою копию одного и того же списка. #### Реальное время везде Чат — очевидное место, где появляются WebSocket-соединения, но в Jepta они им не ограничены. Пришедший пост в канале, изменившийся счётчик комментариев, бейдж непрочитанного на вкладке чатов, отметка о прочтении — всё это работает через одно и то же постоянное соединение, в том числе на экранах, на которые пользователь сейчас не смотрит. Решать это отдельно на каждом экране означало бы по соединению на экран и состояние, которое расходится с самим собой при первом же переключении вкладки. ### Наш подход Мы рассматривали обе проблемы как одну: сначала построить общие части, а экраны сделать тонкими. #### Один слой реального времени и много потребителей WebSocket-соединение живёт под интерфейсом как единый клиент, который сам управляет своим жизненным циклом: подключение, аутентификация, heartbeat, переподключение с backoff, повторная подписка. Экраны не открывают сокеты — они подписываются на типизированные потоки событий и получают обновления. Сообщения, отправленные при отсутствии связи, ставятся в очередь и досылаются при переподключении, а не теряются; статус доставки (отправлено, доставлено, прочитано) — часть модели сообщения, а не догадка на основе того, что успело прийти. Поскольку слой общий, сообщение в открытом чате и увеличение бейджа на вкладке, куда пользователь не смотрит, — это одно и то же событие, обработанное один раз. #### Скелет экрана вместо ручной сборки каждого При таком количестве поверхностей выигрыш давало их единообразие: один навигационный граф с типизированными маршрутами, один подход к Управление состоянием, применённый одинаково в каждой фиче, и общий набор состояний: список, загрузка, пустой экран, ошибка. Новые экраны в основном собирались из готового — именно это сделало объём выполнимым в срок и сохранило приложение цельным, а не похожим на двадцать экранов от разных рук. #### Геопозиция как полноценные входные данные Близость — не фильтр, прикрученный к ленте, а организующий принцип продукта, поэтому слой геопозиции должен был быть надёжным: сценарии выдачи разрешений, корректно работающие при отказе, закешированные координаты, чтобы лента не была пустой, пока определяется местоположение, и расстояние, показанное рядом с самим контентом («в 5 км от вас» в шапке канала), чтобы ранжирование читалось, а не выглядело загадкой. ### Результат Jepta вышла на iOS и Android из одной кодовой базы на Flutter в отведённые шесть месяцев и с полным набором функций: чаты, каналы, посты, комментарии и лента с учётом геопозиции — в первом релизе, а не по частям в нескольких. Дольше дедлайна прожила архитектура. Поскольку слой реального времени, навигационный граф и подход к состоянию были построены один раз и переиспользовались, добавление экрана после релиза осталось небольшим изменением, а не новой интеграцией с сокетом, роутером и кешем. Для продукта, чей роадмап — «больше поверхностей», именно эта разница накапливается. > Больше всего меня беспокоили шесть месяцев на приложение такого охвата. Jepta — это соседские чаты, каналы бизнеса, посты, комментарии и лента, которая перестраивается вокруг того места, где вы сейчас находитесь: длинный список экранов, фиксированная дата и трезвое ожидание, что половина уедет во второй релиз. > > Команда Nerdy Production сначала построила общий фундамент — один слой реального времени, одну модель навигации, — и после этого каждый новый экран стал дешёвым. Мы вышли на iOS и Android с полным набором функций, чат надёжно работает с первого дня, а дорабатывать приложение с тех пор — небольшая задача, а не проект. Мы получили тот продукт, который заказывали, и к той дате, к которой заказывали. *[Evgeny Syrtsov](https://www.linkedin.com/in/evgeny-syrtsov/), Генеральный директор, Netgineers GmbH* ### FAQ #### Что такое Jepta? Jepta — приложение для гиперлокального общения в немецких районах. Оно организует людей по близости, а не по глобальному графу подписок: чаты, привязанные к адресу или дому, групповые чаты для тех, кто живёт по одному адресу, и каналы, которые ведёт локальный бизнес — например, пекарня или мастерская. Чаты, каналы и посты, которые видит пользователь, ранжируются по расстоянию от его текущего местоположения. #### Как разрабатывать приложение на Flutter с большим количеством экранов? Сначала построить общие части, а экраны оставить тонкими. В Jepta это означало один навигационный граф с типизированными маршрутами, один подход к управлению состоянием, применённый одинаково в каждой фиче, и общий набор состояний: список, загрузка, пустой экран, ошибка. После этого новые экраны в основном собираются из готового, а не превращаются в новую интеграцию — именно это делает объём выполнимым в срок и не даёт приложению выглядеть как двадцать экранов от разных рук. #### Как работать с WebSocket-соединениями сразу на многих экранах во Flutter? Держать одно соединение под интерфейсом — как единый клиент, который сам управляет своим жизненным циклом: подключение, аутентификация, heartbeat, переподключение с backoff, повторная подписка. Экраны не открывают собственных сокетов, а подписываются на типизированные потоки событий и получают обновления. Тогда сообщение в открытом чате и растущий бейдж непрочитанного на вкладке, куда никто не смотрит, — одно и то же событие, обработанное один раз, вместо соединения на каждый экран и состояния, которое расходится с самим собой. #### Что происходит с сообщениями, отправленными при отсутствии связи? Они ставятся в очередь и досылаются при переподключении, а не теряются. Статус доставки — отправлено, доставлено, прочитано — хранится как часть модели сообщения, а не выводится из того, что успело прийти, поэтому интерфейс показывает реальный статус сообщения, а не догадку о нём. #### Сколько времени занимает разработка большого социального приложения на Flutter? Jepta вышла на iOS и Android из одной кодовой базы на Flutter за шесть месяцев: чаты, каналы, посты, комментарии и лента с учётом геопозиции — всё в первом релизе, а не по частям в нескольких. Срок выдержали за счёт того, что слой реального времени, навигационный граф и подход к состоянию были построены один раз и переиспользовались на всех экранах. #### Может ли приложение на Flutter ранжировать контент по геопозиции? Да, и в Jepta близость — организующий принцип продукта, а не фильтр поверх ленты. Это предъявляет требования к слою геопозиции: сценарии выдачи разрешений, корректно работающие при отказе, закешированные координаты, чтобы лента не была пустой, пока определяется местоположение, и расстояние, показанное рядом с тем контентом, к которому оно относится, — чтобы ранжирование читалось, а не выглядело загадкой. Jepta сделана в рамках нашей услуги [разработки приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development) и стала опорным проектом нашей практики [разработки социальных и комьюнити-приложений](https://ru.nerdy.pro/services/flutter-app-development/social) — тот же слой реального времени, только рассказанный со стороны сообщества. ## OneTwoDo https://ru.nerdy.pro/portfolio/onetwodo ### О проекте OneTwoDo — маркетплейс локальных услуг, где пользователи размещают и просматривают объявления о повседневных услугах — уборка, ремонт, покраска, сантехника, переезды, красота и другое — и напрямую связываются с исполнителями поблизости. Продукт разработала наша команда, от начала до конца: дизайн продукта, Flutter-клиент и всё, на чём приложение работает. ![Экран входа](https://ru.nerdy.pro/projects/onetwodo/images/01.webp) ![Лента объявлений](https://ru.nerdy.pro/projects/onetwodo/images/02.webp) ![Категории услуг](https://ru.nerdy.pro/projects/onetwodo/images/03.webp) ![Мастер создания поста — тип поста](https://ru.nerdy.pro/projects/onetwodo/images/04.webp) ![Мастер создания поста — локализованное описание](https://ru.nerdy.pro/projects/onetwodo/images/05.webp) ### Без команды бэкенда Двусторонний маркетплейс обычно подразумевает команду бэкенда раньше, чем что-либо ещё: аккаунты, сессии, базу данных для объявлений и профилей, хранилище файлов для фотографий и сервер, за которым всё это стоит, — ещё до того, как кнопка «Зарегистрироваться» сможет сделать хоть что-то реальное. Именно на разработку и поддержку этого слоя обычно уходит время и бюджет небольшой команды — ещё до того, как дело доходит до самого продукта. OneTwoDo вышел без единой строчки этого слоя. Firebase заменил собой весь бэкенд целиком — Authentication для аккаунтов и сброса пароля, Cloud Firestore для объявлений и профилей, Cloud Storage (объектное хранилище) для фотографий, Analytics и Crashlytics — там, где команда обычно строит собственные дашборды ради наблюдаемости. Ничего из этого не требовало развёртывания серверов, установки патчей или дежурств on-call — именно это позволило нашей команде одновременно вести дизайн продукта, Flutter-клиент и «бэкенд», уложившись в семь недель. ### Что это дало Экономия проявлялась не только на старте — она возникала каждый раз, когда фиче требовалась поддержка со стороны бэкенда. Мультивалютные цены в фиате и криптовалюте, локализованные описания для каждого объявления, лента объявлений с фильтрацией по языку и локации — обычно каждая из этих задач сначала становится тикетом для бэкенда и только потом для клиента. Здесь это была работа исключительно на стороне клиента поверх уже готового слоя данных — так мастер создания объявления (тип, описание, локация, цена, категория, фотографии) добирался от идеи до готового экрана за время, которое в другом проекте ушло бы на одну только спецификацию бэкенда, а это напрямую сокращает время выхода на рынок. Это же снизило совокупную стоимость владения продукта. Запуск, прошедший лучше ожиданий, не означает аврала с масштабированием базы данных или добавлением кеширующего слоя — Firebase берёт это на себя по умолчанию, а стоимость растёт вместе с использованием, а не остаётся фиксированным счётом за сервер, который платится независимо от того, десять ли у приложения пользователей или десять тысяч. ### Офлайн-режим демо И для ревью в сторах, и для обычных демонстраций нужен способ изучить продукт без живого бэкенда и реального аккаунта. В OneTwoDo встроен самодостаточный слой данных в памяти, который подменяет собой Firebase и отдаёт заранее заполненные объявления, профили и рабочую ленту — всё приложение можно изучить от и до, не обращаясь к сети и не запрашивая у ревьюера или потенциального клиента никаких учётных данных. ### Результаты С Firebase вместо той команды бэкенда, которая иначе понадобилась бы продукту, наша команда довела проект до рабочего кроссплатформенного маркетплейса — аутентификация по email/паролю со сбросом пароля, загрузка аватара со сжатием изображения на устройстве, полная локализация на английский и русский, раздел FAQ — за семь недель, от пустого репозитория до полностью готового к публикации приложения. Итог проекта в цифрах: - **Семь недель от пустого репозитория до готового к публикации приложения** — включая дизайн продукта, а не только код. - **Ноль собственных бэкенд-сервисов** — ничего не разрабатывалось, не разворачивалось и не требовало дежурств: аккаунты, данные, фотографии и аналитику нёс Firebase. - **Одна команда закрыла все три роли** — дизайн, Flutter-клиент и управляемый бэкенд, — тогда как маркетплейс обычно нанимает под каждую отдельных людей. - **Две локали и мультивалютные цены** (фиат и криптовалюта) вышли в те же семь недель — как клиентская работа поверх уже готового слоя данных. ### FAQ #### Что такое OneTwoDo? OneTwoDo — приложение-маркетплейс локальных услуг, созданное для Perform Connect Studios S.L.: пользователи размещают и просматривают объявления о повседневных услугах — уборка, ремонт, покраска, сантехника, переезды, красота и другое — и напрямую связываются с исполнителями поблизости. Мы сделали продукт от начала до конца: дизайн, Flutter-клиент для iOS и Android и всё, на чём приложение работает. #### Как двусторонний маркетплейс построили без команды бэкенда? Firebase заменил собой весь бэкенд-слой: Authentication для аккаунтов и сброса пароля, Cloud Firestore для объявлений и профилей, Cloud Storage для фотографий, Analytics и Crashlytics для операционной наблюдаемости. Ничего из этого не требовало развёртывания, патчей или дежурств on-call — именно это позволило одной команде одновременно вести дизайн продукта, Flutter-клиент и бэкенд. #### Сколько времени заняла разработка OneTwoDo? Семь недель от пустого репозитория до полностью готового к публикации приложения — включая дизайн продукта, аутентификацию по email/паролю со сбросом пароля, мультивалютные объявления, локализацию на английский и русский и загрузку фотографий со сжатием на устройстве. Такой календарь стал возможен именно благодаря отказу от собственного бэкенда. #### Какие компромиссы у маркетплейса, работающего только на Firebase? Выигрыш — скорость и стоимость: функции, которые обычно сначала становятся тикетом для бэкенда, превращаются в клиентскую работу поверх уже готового слоя данных, а счёт растёт вместе с использованием, а не как фиксированная плата за сервер. Честные ограничения — привязка к вендору и модель запросов Firestore, которая вознаграждает проектирование данных под то, как их читают экраны. Подходит ли такая архитектура конкретному маркетплейсу — вопрос скоупинга; экономной сборке для проверки гипотезы вроде OneTwoDo она подошла идеально. #### Как приложение работает в демо-режиме без аккаунта? В OneTwoDo встроен самодостаточный слой данных в памяти, который подменяет собой Firebase и отдаёт заранее заполненные объявления, профили и рабочую ленту. Всё приложение можно изучить от и до, не обращаясь к сети, — поэтому ревьюеры сторов и потенциальные клиенты проходят продукт целиком без учётных данных и живого бэкенда. OneTwoDo — это то, как [разработка MVP](https://ru.nerdy.pro/services/flutter-app-development/mvp) выглядит на практике: первый релиз, собранный под один вопрос и выпущенный сразу в оба стора. Он же — опорный проект нашей страницы [разработки приложений-маркетплейсов](https://ru.nerdy.pro/services/flutter-app-development/marketplace), которая рассказывает ту же историю с двусторонней стороны. ## YouMi https://ru.nerdy.pro/portfolio/youmi ### Обзор проекта YouMi — это онлайн-платформа психологического консультирования, которая связывает пользователей с сертифицированными психологами через мобильное приложение. Пользователь выбирает специалиста, планирует и переносит сеансы и переписывается с психологом в реальном времени. Когда YouMi обратилась к нам, их Flutter-приложение страдало от серьёзных технических проблем. Пользователи жаловались на частые обрывы связи во время сеансов, на то, что сообщения в чате не доходят, и на проблемы с навигацией, из-за которых приложением было мучительно пользоваться. Всё это подрывало доверие пользователей и мешало платформе расти. ![Экран выбора специалиста](https://ru.nerdy.pro/projects/youmi/images/01.webp) ![Планирование сеанса](https://ru.nerdy.pro/projects/youmi/images/02.webp) ![Функции сеанса](https://ru.nerdy.pro/projects/youmi/images/03.webp) ![Профиль пользователя](https://ru.nerdy.pro/projects/youmi/images/04.webp) ![Настройки](https://ru.nerdy.pro/projects/youmi/images/05.webp) ![Настройки уведомлений](https://ru.nerdy.pro/projects/youmi/images/06.webp) ### Технические сложности В основе нестабильности приложения лежали две проблемы: **Управление WebSocket-соединениями**: чат в реальном времени и сеансы опирались на WebSocket-соединения, с которыми приложение обращалось неправильно. Соединения обрывались без внятной логики переподключения, сообщения терялись при переключении между сетями, а поддерживать стабильную связь между пользователями и психологами приложению удавалось с трудом. Из-за этого чат был ненадёжен и мешал прямо во время консультаций. **Архитектура навигации**: система маршрутизации в приложении была реализована плохо — отсюда утечки памяти, рассогласованное управление состоянием и непредсказуемое поведение навигации. Пользователи сталкивались с падениями при переходе между экранами, с некорректной работой кнопки «Назад», а экраны зависали или показывали неверный контент. ### Решение и реализация Уложившись в жёсткие две недели, мы последовательно устранили обе ключевые проблемы: Мы полностью переписали работу с WebSocket, выстроив надёжное управление жизненным циклом соединения — с автоматическим переподключением, очередью сообщений на время обрывов сети и корректной обработкой ошибок. Теперь сообщения доходят надёжно, а сеансы переживают переключение между сетями и короткие обрывы связи. Заодно мы добавили отслеживание статуса сообщений: пользователи и психологи видят, когда сообщение отправлено, доставлено и прочитано. Систему навигации мы перестроили с нуля, опираясь на современные практики маршрутизации во Flutter. Мы выстроили корректное управление состоянием, устранили утечки памяти и сделали навигацию предсказуемой — такой, какой её ожидает пользователь. В итоге получился плавный, отзывчивый интерфейс, который одинаково ведёт себя во всех сценариях. ### Результаты За две недели приложение YouMi превратилось из нестабильного продукта в надёжную платформу. Чат стал предсказуемым, связь во время сеансов заметно улучшилась, а проблемы с навигацией исчезли полностью. Исправления позволили YouMi уверенно наращивать пользовательскую базу и заниматься развитием бизнеса, а не разгребанием технических кризисов. YouMi пришли к нам за спасением Flutter-приложения — посмотрите, как мы создаём и стабилизируем продакшен-приложения в рамках [разработки на Flutter](https://ru.nerdy.pro/services/flutter-app-development), или что мы вынесли из работы с [медицинскими и телемедицинскими приложениями](https://ru.nerdy.pro/services/flutter-app-development/healthcare). ## dxpdf https://ru.nerdy.pro/open-source/dxpdf ### Проблема Конвертация документов Word в PDF — одна из самых распространённых задач в бизнес-софте. Счета, договоры, отчёты, регуляторные формы — они начинаются как `.docx`-файлы и должны стать PDF для обмена, архивирования или печати. Каждое существующее решение требует серьёзных компромиссов: - **Microsoft Office / LibreOffice** — требует установки полного офисного пакета на каждый сервер. Headless-режим LibreOffice медленный, потребляет много памяти и выдаёт разный результат от версии к версии. Масштабирование означает запуск нескольких экземпляров, потребляющих гигабайты оперативной памяти. - **Облачные API** (Google Docs, Adobe, CloudConvert) — добавляют задержку, берут плату за каждую конвертацию и отправляют потенциально конфиденциальные документы на сторонние серверы. Неприемлемо для регулируемых отраслей или изолированных сред. - **Инструменты HTML-to-PDF** (wkhtmltopdf, Puppeteer) — требуют предварительного преобразования DOCX в HTML с потерей точности форматирования. Таблицы, колонтитулы и разрывы страниц редко переживают такую цепочку преобразований. Ни одно из этих решений не подходит, когда нужна быстрая и точная офлайн-конвертация в больших объёмах — особенно в автоматизированных пайплайнах, CI/CD-системах или встраиваемых приложениях, где установка LibreOffice невозможна. ### Как dxpdf решает эту задачу dxpdf — это автономный конвертер DOCX в PDF, написанный на Rust и использующий графическую библиотеку Google Skia. Он читает `.docx`-файлы напрямую, парсит OOXML-структуру и рендерит PDF с пиксельной точностью — всё в одном бинарнике без внешних зависимостей, кроме Skia. Модель **сначала измерить, потом разместить**, заимствованная у Flutter, следит за тем, чтобы перенос текста, размеры таблиц и разрывы страниц совпадали с тем, что делает Microsoft Word: ```text DOCX (ZIP) → Parse → Document Model → Resolve → Layout → Subset → Paint → PDF Twips/Emu/HalfPoints ←──── Pt throughout ────→ Skia ``` Через весь пайплайн проходят типобезопасные единицы измерения: единицы OOXML (`Twips`, `Emu`, `HalfPoints`) в разобранной модели хранятся как `i64` и потому переводятся туда-обратно без потерь, вёрстка работает в типографских пунктах (`Pt`), а голый `f32` появляется только на границе с рендерингом в Skia — так что смешение единиц становится ошибкой компиляции, а не документом с едва заметной ошибкой. Результат — конвертер, который превращает 3-страничный документ с таблицами и изображениями в PDF за **170 мс**, потребляя **55 МБ памяти**, а документ на 171 страницу и 14 МБ — за **420 мс**: достаточно быстро для запуска внутри обработчика запросов или пакетной обработки тысяч документов. ### Возможности Проверено по ISO 29500 (Office Open XML): **74 возможности реализованы полностью, 11 — частично, 12 пока не поддерживаются** — [полная матрица](https://github.com/nerdy-pro/dxpdf#ooxml-feature-coverage) перечисляет каждую позицию со статусом. Главное: - **Форматирование текста** — жирный, курсив, подчёркивание, выделение цветом, размер шрифта, гарнитура, цвет, межсимвольный интервал, масштабирование символов, надстрочный и подстрочный текст, заливка и границы символов - **Абзацы** — выравнивание (по левому краю, по центру, по правому краю, по ширине, распределённое), интервалы, отступы, табуляция (в том числе десятичная, с чертой и с абсолютной позицией), границы, заливка, «не отрывать от следующего», «не разрывать абзац», контроль висячих строк - **Таблицы** — ширина столбцов, отступы ячеек с 3-уровневым каскадированием, объединённые ячейки, высота строк, границы, заливка ячеек, стили таблиц с условным форматированием, вложенные и плавающие таблицы, разрыв строк между страницами - **Изображения** — встроенные (PNG, JPEG, GIF, BMP, WebP) и плавающие/привязанные с выравниванием, обтеканием, кадрированием и процентным позиционированием - **Фигуры и надписи** — фигуры DrawingML и VML, текстовые блоки фигур с внутренними полями, привязкой, автоподбором и произвольной геометрией - **Стили** — стили абзацев и символов с наследованием `basedOn`, настройки документа по умолчанию, шрифты темы - **Колонтитулы** — текст, изображения, номера страниц (коды полей PAGE/NUMPAGES), варианты для первой страницы и чётных/нечётных - **Списки** — многоуровневая нумерация: маркеры, десятичные, буквенные, римские, порядковые и прописью, а также нелатинские последовательности и графические маркеры - **Навигация** — кликабельные аннотации ссылок, закладки и перекрёстные ссылки как именованные назначения, оглавление PDF по уровням заголовков - **Разделы** — несколько размеров страниц и полей, разрывы разделов, многоколоночная вёрстка, книжная и альбомная ориентация - **Вёрстка** — автоматическая пагинация, сноски и концевые сноски, перенос слов, режимы межстрочного интервала, обтекание плавающих изображений - **Интернационализация** — переносы по UAX #14 (включая тайский, лаосский, кхмерский и бирманский), двунаправленный текст по UAX #9 с зеркалированием, шейпинг через HarfBuzz для письменностей с курсивным соединением, а также десятичные разделители, маски дат и числительные прописью по атрибуту `w:lang` - **Текст и эмодзи** — сегментация по графемным кластерам и полноцветные эмодзи, включая последовательности ZWJ, модификаторы, клавиши и флаги ### Три способа использования #### Инструмент командной строки Установите и запустите одной командой: ```shell cargo install dxpdf dxpdf input.docx -o output.pdf ``` #### Rust-библиотека Один вызов функции — байты на вход, байты на выход: ```rust let docx_bytes = std::fs::read("document.docx")?; let pdf_bytes = dxpdf::convert(&docx_bytes)?; std::fs::write("output.pdf", &pdf_bytes)?; ``` Для большего контроля можно просмотреть модель документа перед рендерингом: ```rust use dxpdf::{docx, model, render}; let document = docx::parse(&std::fs::read("document.docx")?)?; for block in &document.body { match block { model::Block::Paragraph(p) => { /* разбор абзаца */ } model::Block::Table(t) => { /* разбор таблицы */ } model::Block::SectionBreak(props) => { /* разбор свойств секции */ } } } let pdf_bytes = render::render(document, &dxpdf::RenderOptions::default())?; ``` #### Python-пакет Установите с PyPI и используйте в любом Python-приложении: ```shell pip install dxpdf ``` ```python import dxpdf # Байты на вход, байты на выход pdf_bytes = dxpdf.convert(open("input.docx", "rb").read()) # Файл в файл dxpdf.convert_file("input.docx", "output.pdf") ``` ### Производительность Бенчмарк на Apple M3 Max с [hyperfine](https://github.com/sharkdp/hyperfine) (30 запусков, 5 прогревочных) на версии 0.5.0, на фикстурах, закоммиченных в репозиторий: | Фикстура | Страниц | Вход | Время конвертации | Пик RSS | | --- | --- | --- | --- | --- | | Деловой документ на 3 страницы | 3 | 34 КБ | **170 мс** | 55 МБ | | Документ на 7 страниц | 7 | 10 КБ | **170 мс** | 52 МБ | | Документ на 9 страниц с изображениями | 9 | 1,3 МБ | **55 мс** | 42 МБ | | Отчёт на 171 страницу | 171 | 14 МБ | **420 мс** | 159 МБ | **Стоимость конвертации определяется тем, как разрешаются шрифты, а не размером документа.** Девятистраничная фикстура несёт в сорок раз больше данных, чем трёхстраничная, и конвертируется втрое быстрее, потому что её шрифты встроены или уже есть в системе: этот путь стоит около 4 мс, а откат к системному индексу метаданных — 120–185 мс, один раз. Для пакетной нагрузки важен не размер документов, а то, указывают ли они шрифты, которые на хосте уже есть. Корректность конвертации закреплена тестами на фикстурах, включая визуальные регрессионные тесты, сравнивающие отрендеренные PDF с эталонными документами, сгенерированными Word. ### Сценарии использования #### Автоматизированные пайплайны документов CI/CD-системы или пакетные процессоры, генерирующие договоры, счета или отчёты из `.docx`-шаблонов. dxpdf запускается как один бинарник — без установки LibreOffice, без Docker-образа с полным десктопным окружением, без платы за каждый документ. #### Регулируемые среды Приложения в здравоохранении, юриспруденции и финансах, где документы не могут покидать сеть. dxpdf работает полностью офлайн без внешних вызовов, что делает его подходящим для изолированных и локальных развёртываний. #### Встраиваемые системы и edge-вычисления IoT-устройства, киоски или лёгкие контейнеры, где установка офисного пакета на 500 МБ нецелесообразна. Потребление памяти dxpdf в десятки мегабайт и субсекундное время конвертации делают его пригодным для сред с ограниченными ресурсами. #### Python веб-приложения Бэкенды на Django, Flask или FastAPI, которым нужно конвертировать загруженные DOCX-файлы на лету. Python-биндинги оборачивают Rust-ядро через PyO3, обеспечивая нативную производительность без подпроцессов и внешних сервисов. ### О последнем релизе Свежая версия **0.5.0** — релиз про интернационализацию: переносы строк по UAX #14, двунаправленный текст по UAX #9, а также числа и даты из CLDR по языку самого документа. Мы разобрали его в тексте [dxpdf 0.5.0: как мы научили конвертер DOCX читать не только по-английски](https://ru.nerdy.pro/blog/dxpdf-0-5-0-release) — о решениях, которые за этим стоят, о замерах и о том, где конвертер всё ещё не справляется. #### Требуется ли dxpdf Microsoft Office или LibreOffice? Нет. dxpdf — это автономный конвертер, который читает DOCX-файлы напрямую и рендерит PDF с помощью графического движка Google Skia. Он не зависит ни от какого офисного пакета. #### Какие языки и платформы поддерживает dxpdf? dxpdf написан на Rust и доступен как CLI-инструмент (через cargo install), Rust-библиотека (через crates.io) и Python-пакет (через PyPI). Работает на macOS, Linux и Windows. #### Насколько точна конвертация? dxpdf использует заимствованный у Flutter пайплайн «сначала измерить, потом разместить», спроектированный для пиксельной точности. Соответствие проверено по ISO 29500: 74 возможности реализованы полностью, 11 — частично, 12 пока не поддерживаются. Визуальные регрессионные тесты сравнивают вывод с эталонами, сгенерированными Word. #### Насколько быстр dxpdf? На Apple M3 Max закоммиченные фикстуры конвертируются за 55–170 мс, а документ на 171 страницу и 14 МБ — примерно за 420 мс. Разрешение шрифтов важнее размера документа: встроенные или уже установленные шрифты разрешаются примерно за 4 мс, а откат к системному индексу метаданных стоит 120–185 мс, один раз. Это достаточно быстро для запуска внутри обработчиков веб-запросов. #### Можно ли использовать dxpdf в Python-приложении? Да. Установите через pip install dxpdf. Python-пакет оборачивает Rust-ядро через PyO3, обеспечивая нативную производительность. Используйте dxpdf.convert() для работы с байтами или dxpdf.convert_file() для конвертации файл-в-файл. #### Какие функции DOCX ещё не поддерживаются? Пока не поддерживаются: переупорядочивание индийских письменностей, подстановка шрифта для отдельных глифов, автоматический перенос по слогам, исправления и комментарии, SmartArt и диаграммы, границы страницы, изображения WMF и SVG, зеркалированные табуляции при w:bidi и счётные форматы нумерации вроде chineseCounting. Частично поддерживаются: малые прописные и скрытый текст разбираются, но не применяются, большинство стилей границ аппроксимируется сплошной линией, обтекание tight и through использует ограничивающий прямоугольник вместо полигона, а из EMF декодируется только одиночный встроенный растр. ## Flutter Adaptive Layout https://ru.nerdy.pro/open-source/flutter-adaptive-layout ### Проблема Приложению на Flutter, которое выходит на телефоны, планшеты и десктоп, недостаточно гибкой колонки. Экрану, который хорошо читается при ширине 390 логических пикселей, при 700 нужна центрированная карточка, а при 1200 — боковая панель. Flutter даёт измерения, но не даёт структуры, в которой их применять. Поэтому проверки расползаются по коду. Здесь `MediaQuery.of(context).size.width > 600`, там `LayoutBuilder`, тремя виджетами ниже — тернарный оператор внутри `Padding`. Каждая по отдельности выглядит уместно, но вместе они образуют правила вёрстки, которые негде прочитать целиком. Типичные проблемы: - Значения брейкпойнтов дублируются в десятках виджетов и расходятся по мере изменения дизайна - `LayoutBuilder` сообщает ограничения родителя, тогда как нужен размер устройства - Вёрстка переключается на планшетный вариант, как только телефон поворачивают в альбомную ориентацию - В дереве виджетов невозможно увидеть логику адаптивности, не прочитав каждый дочерний виджет ### Как flutter_adaptive_layout решает это `AdaptiveLayout` принимает по одному билдеру на размер экрана и `child`. Он измеряет устройство через `MediaQuery`, сопоставляет результат с `ScreenSize` и строит только подходящий вариант — при этом `child` создаётся один раз и передаётся в выбранный билдер. ```dart AdaptiveLayout( smallBuilder: (context, child) => child!, mediumBuilder: (context, child) => Center(child: child), largeBuilder: (context, child) => Row( children: [const Sidebar(), Expanded(child: child!)], ), child: const MyHomePage(), ) ``` Решение об адаптивности теперь живёт в одном виджете, на вершине поддерева, которым оно управляет, и записано как три варианта вёрстки, а не как цепочка условий. ![Пример приложения на маленьком экране — iPhone 14](https://ru.nerdy.pro/open-source/flutter-adaptive-layout/iphone_14.png) ![То же приложение на iPad Pro 12,9 дюйма: largeBuilder помещает контент в широкую карточку](https://ru.nerdy.pro/open-source/flutter-adaptive-layout/ipad_12_inch.png) ### Что умеет пакет - **Вёрстка по размеру экрана** — `smallBuilder`, `mediumBuilder` и `largeBuilder`, каждый необязателен: если билдера нет, рисуется `child`, поэтому можно описать только те размеры, которые важны - **Свои брейкпойнты** — по умолчанию 400 и 600 логических пикселей, переопределяются для отдельного виджета или для всего приложения - **`child` строится один раз** — при смене вёрстки контент оборачивается, а не пересобирается с нуля - **Устойчивость к повороту** — классификация читает `MediaQuery.of(context).size.shortestSide`, поэтому телефон остаётся маленьким экраном и в альбомной ориентации - **Подключаемая логика классификации** — реализуйте `ScreenSizeQualifier`, чтобы делить экраны по ширине, платформе, состоянию складного экрана или как принято в вашей дизайн-системе - **Без зависимостей** — чистый Flutter поверх `MediaQuery`, работает на iOS, Android, web, macOS, Windows и Linux ### Как определяется размер экрана `BreakpointsQualifier` по умолчанию сравнивает короткую сторону с двумя брейкпойнтами, причём включительно: значение, равное брейкпойнту, попадает в меньшую категорию. | Размер экрана | Условие | Диапазон по умолчанию | Типичное устройство | | --- | --- | --- | --- | | `ScreenSize.small` | `shortestSide <= smallBreakpoint` | 0–400 логических пикселей | Телефоны | | `ScreenSize.medium` | `shortestSide <= mediumBreakpoint` | 401–600 логических пикселей | Небольшие планшеты | | `ScreenSize.large` | больше `mediumBreakpoint` | 601+ логических пикселей | Большие планшеты, десктоп | Брейкпойнты разрешаются в фиксированном порядке — побеждает первое заданное значение: аргументы `BreakpointsQualifier`, затем ближайший `BreakpointsSetting` выше по дереву, затем встроенные 400 и 600. ### Сценарии использования #### От телефона к планшету Приложение, начинавшееся с телефонов, может сохранить все существующие экраны и добавить `largeBuilder` там, где широкая вёрстка действительно нужна. Экраны без билдера для нужного размера рисуют `child` без изменений, поэтому переход идёт экран за экраном, а не разом. #### Навигация «список — детали» Список, который на телефоне открывает детальный экран отдельным маршрутом, на планшете может показывать список и детали рядом. Оба варианта объявлены в одном виджете, так что разницу между ними видно прямо в диффе. #### Дизайн-системы со своими брейкпойнтами Оберните `MaterialApp` в `BreakpointsSetting` один раз — и каждый `AdaptiveLayout` в дереве возьмёт числа дизайн-системы. Позже поменять их — это правка одной строки, а не поиск по всему проекту. #### Окна на web и десктопе На web и десктопе измеряется размер окна, поэтому вёрстка реагирует на изменение его размера: тот же код, что обслуживает планшет, обслуживает и уменьшенное окно браузера. ### С чего начать Установите пакет: ```shell flutter pub add flutter_adaptive_layout ``` Требуется Dart `>=2.18.5 <4.0.0` и Flutter 1.17+. Импортируйте его и оберните виджет, вёрстка которого должна адаптироваться: ```dart import 'package:flutter_adaptive_layout/flutter_adaptive_layout.dart'; AdaptiveLayout( largeBuilder: (context, child) => Center( child: SizedBox(width: 600, child: child), ), child: const ArticleView(), // на маленьких и средних экранах — как есть ) ``` Чтобы классифицировать экраны иначе, реализуйте `ScreenSizeQualifier` и передайте его в `qualifier`: ```dart class WidthQualifier extends ScreenSizeQualifier { @override ScreenSize qualify(BuildContext context) { final width = MediaQuery.of(context).size.width; if (width < 600) return ScreenSize.small; if (width < 1024) return ScreenSize.medium; return ScreenSize.large; } } ``` `qualify` вызывается при каждой перестройке, поэтому он должен быть дешёвым и без побочных эффектов. Полноценное рабочее приложение лежит в [каталоге example](https://github.com/nerdy-pro/flutter-adaptive-layout/tree/main/example). #### Чем это отличается от LayoutBuilder? LayoutBuilder сообщает ограничения, переданные родительским виджетом, — они могут быть намного меньше экрана. AdaptiveLayout классифицирует устройство или окно через MediaQuery, поэтому виджет глубоко внутри дерева всё равно знает, что работает на планшете. LayoutBuilder нужен, чтобы вписаться в доступную область; AdaptiveLayout — чтобы выбрать вёрстку под устройство. #### Меняется ли вёрстка при повороте устройства? Нет. Классификация использует MediaQuery.of(context).size.shortestSide — эта величина одинакова в книжной и альбомной ориентации, поэтому телефон остаётся ScreenSize.small при повороте. Если вёрстка должна зависеть от ориентации, реализуйте свой ScreenSizeQualifier, который читает size.width. #### Обязательно ли задавать все три билдера? Нет. Любой билдер можно не указывать — для этого размера экрана будет использован child. Если нет ни подходящего билдера, ни child, будет выброшен UnimplementedError. #### Как изменить брейкпойнты по умолчанию? Либо передайте BreakpointsQualifier(smallBreakpoint: ..., mediumBreakpoint: ...) в конкретный AdaptiveLayout, либо оберните приложение в BreakpointsSetting, чтобы поменять их везде. Значения из конструктора важнее BreakpointsSetting, а он важнее значений по умолчанию — 400 и 600. #### Работает ли пакет на web и десктопе? Да. Он зависит только от MediaQuery, поэтому работает на всех платформах Flutter. На web и десктопе измеряется размер окна, так что вёрстка реагирует на его изменение. ## Flutter Future Progress Dialog https://ru.nerdy.pro/open-source/flutter-progress-dialog ### Проблема Большинству Flutter-приложений приходится выполнять задачи, занимающие несколько секунд — загрузка данных из API, отправка файла или обработка платежа. В это время пользователь видит застывший экран без каких-либо признаков того, что что-то происходит. Он нажимает кнопку повторно, вызывает дублирующие запросы или решает, что приложение зависло. Стандартное решение — вручную управлять состоянием загрузки: переключить флаг, показать спиннер, дождаться завершения future, скрыть спиннер, а затем обработать результат или ошибку. Эта логика дублируется на десятках экранов с мелкими различиями каждый раз. Типичные проблемы: - Повторяющийся шаблонный код управления состоянием загрузки на каждом экране - Повторные нажатия пользователей из-за отсутствия визуальной обратной связи - Закрытые диалоги, которые оставляют «осиротевшие» future, продолжающие работу в фоне - Непоследовательная обработка ошибок — одни экраны перехватывают исключения, другие падают молча ### Как flutter_future_progress_dialog решает эту задачу `flutter_future_progress_dialog` заменяет весь этот шаблонный код одним вызовом функции. Передайте асинхронную задачу — пакет покажет незакрываемый диалог прогресса, дождётся результата, закроет диалог и вернёт типобезопасный результат: `Success` со значением или `Failure` с ошибкой. ![Демо Flutter progress dialog на iPhone](https://ru.nerdy.pro/open-source/flutter-progress-dialog/flutter_progress_dialog.gif) Обработка ошибок встроена в тип результата, поэтому вы никогда не забудете перехватить исключение. ### Возможности - **Material-диалог** — `showProgressDialog` отображает стандартный круговой индикатор прогресса в стиле Material - **Cupertino-диалог** — `showCupertinoProgressDialog` показывает индикатор активности в стиле iOS - **Адаптивный диалог** — `showAdaptiveProgressDialog` автоматически выбирает стиль, соответствующий текущей платформе - **Свой интерфейс диалога** — передайте `builder`, чтобы заменить стандартный индикатор любым виджетом - **Типобезопасные результаты** — `ProgressDialogResult` — sealed-класс с вариантами `Success` и `Failure`, поддерживающий pattern matching и удобные методы `unwrap()` и `map()` - **Перехват ошибок** — `Failure` содержит объект ошибки и трассировку стека ### Сценарии использования #### Обработка платежей Экран оформления заказа вызывает API платёжного шлюза. Без диалога прогресса пользователи нажимают «Оплатить» повторно, когда ничего не происходит, вызывая двойные списания. С `showProgressDialog` диалог остаётся на экране и блокирует взаимодействие до ответа шлюза, а затем pattern matching результата позволяет перейти на экран подтверждения или ошибки. #### Загрузка файлов Сканер документов отправляет изображения на сервер. Загрузка может занять несколько секунд на медленном соединении. Если обернуть загрузку в `showAdaptiveProgressDialog`, пользователь сразу получает визуальную обратную связь, а диалог соответствует стилю платформы — Material на Android, Cupertino на iOS. #### Отправка форм Многошаговая форма отправляет данные на сервер на финальном этапе. Диалог прогресса предотвращает повторную отправку и даёт понятный результат `Success`/`Failure`, по которому легко решить, что показывать — экран успеха или сообщение об ошибке прямо в форме. #### Фоновая синхронизация данных CRM-приложение синхронизирует локальные изменения с удалённым сервером. Собственный builder может показать брендированную анимацию загрузки вместо стандартного спиннера, сохраняя единый стиль приложения. ### Начало работы Установите пакет: ```shell flutter pub add flutter_future_progress_dialog ``` Затем вызовите `showProgressDialog` с вашей асинхронной задачей: ```dart final result = await showProgressDialog( context: context, future: () => fetchData(), ); switch (result) { case Success(:final value): // Используйте значение case Failure(:final error): // Обработайте ошибку } ``` Доступны три функции диалога: `showProgressDialog` (Material), `showCupertinoProgressDialog` (Cupertino) и `showAdaptiveProgressDialog` (автоматический выбор платформы). Все принимают необязательный параметр `builder` для своего интерфейса. Полный рабочий пример доступен в [директории example](https://github.com/nerdy-pro/flutter-progress-dialog/tree/main/example). #### Может ли пользователь закрыть диалог вручную? Нет. Диалог по умолчанию незакрываемый, что предотвращает осиротевшие future и повторные отправки. Диалог закрывается автоматически после завершения асинхронной задачи. #### Что произойдёт, если future выбросит исключение? Диалог закроется, а результат вернётся как Failure, содержащий ошибку и трассировку стека. Ваш код может использовать pattern matching для обработки ошибок без блоков try-catch. #### Работает ли это и на Android, и на iOS? Да. Используйте showProgressDialog для стиля Material, showCupertinoProgressDialog для стиля iOS или showAdaptiveProgressDialog для автоматического соответствия платформе. #### Можно ли кастомизировать индикатор загрузки? Да. Передайте параметр builder в любую из трёх функций диалога, чтобы заменить стандартный индикатор прогресса на свой виджет — брендированную анимацию, текстовое сообщение или любой другой Flutter-виджет. #### Как работает тип результата? ProgressDialogResult — это sealed-класс с двумя вариантами: Success, содержащий возвращённое значение, и Failure, содержащий ошибку и трассировку стека. Можно использовать pattern matching в Dart или удобные методы isSuccess, isError, unwrap() и map(). ## Flutter Number Editing Controller https://ru.nerdy.pro/open-source/flutter-number-editing-controller ### Проблема Встроенный `TextEditingController` во Flutter работает с числами как с обычным текстом. Когда пользователь вводит `1000000` в поле цены, он видит именно это — нечитаемую последовательность цифр. Нет разделителей разрядов, нет форматирования дробной части, нет символа валюты. Разработчикам приходится писать собственную логику `TextInputFormatter`, вручную парсить строки и бороться с ошибками позиции курсора каждый раз, когда форматирование меняет длину текста. Типичные проблемы: - Пользователи ошибаются при чтении больших чисел без разделителей тысяч - Поля ввода валют игнорируют локальные правила (расположение символа, запятая вместо точки) - Хрупкий код парсинга ломается, когда в отформатированном тексте встречаются нецифровые символы - Курсор прыгает в неожиданные позиции после каждого нажатия клавиши ### Как number_editing_controller решает эту задачу `number_editing_controller` — это готовая замена `TextEditingController`. Назначьте его любому `TextField`, и он автоматически позаботится о форматировании, парсинге и управлении курсором. Пользователь видит корректно отформатированный текст — `$1,234.56` или `1.234,56 €` — а ваш код получает чистое числовое значение через `controller.number`. ![Демо форматирования валюты](https://ru.nerdy.pro/open-source/number-editing-controller/currency.gif) ### Возможности - **Форматирование целых чисел** — группировка разрядов с учётом локали (`1,000,000` в английской, `1.000.000` в немецкой) - **Форматирование дробных чисел** — настраиваемое минимальное и максимальное количество знаков после запятой - **Форматирование валют** — символ валюты размещается до или после числа по правилам локали - **Поддержка локалей** — используются ICU-шаблоны форматирования, поэтому `1234.56` в USD отображается как `$1,234.56` в английской и `1.234,56 $` в немецкой локали - **Отрицательные числа** — необязательный флаг `allowNegative` ограничивает ввод только положительными значениями - **Изменение параметров на лету** — смена локали, валюты или точности без пересоздания контроллера - **Внешний символ валюты** — отображение символа как декоратора `TextField` (prefix/suffix) вместо встроенного текста ### Сценарии использования #### Оформление заказа в e-commerce Платёжная форма принимает суммы в разных валютах. С `number_editing_controller` достаточно одного контроллера — при выборе другой валюты меняется только `currencyName`. Поле мгновенно переформатируется без перестроения виджета. #### Мультиязычные финансовые приложения Банковское приложение для пользователей из США, Германии и Японии должно учитывать правила каждой локали: `$1,234.56` vs `1.234,56 €` vs `¥1,500`. Контроллер обрабатывает всё это через один параметр `locale`. ![Демо форматирования целых чисел](https://ru.nerdy.pro/open-source/number-editing-controller/integer.gif) #### Складской учёт и поля количества Складские приложения работают с большими целочисленными количествами. Контроллер целых чисел превращает `1000000` в `1,000,000`, что помогает быстро заметить ошибки при вводе данных. #### Аналитические дашборды Контроллеры дробных чисел с настраиваемой точностью позволяют форматировать метрики — проценты с 2 знаками, научные измерения с 4 — сохраняя при этом сырое значение `num` для вычислений. ### Примеры форматирования по локалям | Локаль | Тип | Значение | Отображение | | --- | --- | --- | --- | | `en` | Валюта (USD) | `1234.56` | `$1,234.56` | | `de` | Валюта (EUR) | `1234.56` | `1.234,56 €` | | `fr` | Валюта (EUR) | `1234.56` | `1 234,56 €` | | `ja` | Валюта (JPY) | `1500` | `¥1,500` | | `ru` | Валюта (RUB) | `500` | `500 ₽` | | `en` | Целое число | `1234567` | `1,234,567` | | `de` | Дробное (макс. 2) | `1234.89` | `1.234,89` | ### Начало работы Установите пакет: ```shell flutter pub add number_editing_controller ``` Требуется Flutter 3.19+ и Dart 3.3+. Создайте контроллер и назначьте его `TextField`: ```dart final controller = NumberEditingTextController.currency( currencyName: 'USD', locale: 'en', ); TextField( controller: controller, keyboardType: TextInputType.numberWithOptions(decimal: true, signed: true), ) // Получить числовое значение в любой момент final amount = controller.number; // например, 1234.56 ``` Доступны три типа контроллеров: `.integer()`, `.decimal()` и `.currency()`. Все параметры — локаль, разделители, валюта, точность — можно менять в рантайме без пересоздания контроллера. Полный рабочий пример с выбором валюты, переключателем локали и внешним размещением символа валюты доступен в [директории example](https://github.com/nerdy-pro/flutter_number_editing_controller/tree/main/example). #### Работает ли это с Flutter web? Да, но в JavaScript все числа представлены как 64-битные числа с плавающей точкой, поэтому целые числа больше 2^53 - 1 (около 9 квадриллионов) теряют точность без предупреждения. На нативных платформах 64-битные целые поддерживаются полностью. #### Можно ли менять валюту или локаль после создания контроллера? Да. Все параметры форматирования изменяемы в рантайме. Смена локали, кода валюты, разделителей или точности немедленно переформатирует отображаемый текст без пересоздания контроллера. #### Поддерживается ли индийская система нумерации (лакх/крор)? Пока нет. Библиотека использует одинаковый размер группы, определяемый ICU-шаблоном локали (обычно группы по 3 цифры). Системы нумерации с переменной шириной группы, такие как индийская, не поддерживаются. #### Как отобразить символ валюты вне текстового поля? Установите showCurrencySymbol в false и используйте свойства resolvedCurrencySymbol и currencySymbolPosition для размещения символа как prefix или suffix декоратора TextField. #### Что происходит с очень длинными числами? Точность дробной части ограничена 20 знаками (лимит Dart toStringAsFixed). Крайне длинный ввод может замедлить форматирование, но типичные финансовые и бизнес-значения обрабатываются без проблем. ## Nerdy Pro Dev Container https://ru.nerdy.pro/open-source/nerdy-pro-dev-container ### Проблема Dev-контейнеры VS Code — самый чистый способ дать каждому участнику проекта одинаковое окружение: та же ОС, те же версии инструментов, тот же shell. Но за это приходится платить: при первом открытии проекта VS Code собирает образ контейнера с нуля. Установка базового слоя ОС, рантайма языка, shell и любых AI-инструментов поверх него может занять несколько минут — и это повторяется при каждом изменении `Dockerfile`, инвалидирующем кэш. Для команд, которые также используют Claude Code внутри контейнера, есть ещё один источник трения: чтобы аутентификация, история shell и кэш npm переживали пересборку контейнера, один и тот же шаблонный `devcontainer.json` приходится копировать в каждый репозиторий. ### Как это решается `nerdy-pro-dev-container` — это готовый мультиархитектурный Docker-образ, опубликованный в GitHub Container Registry. Вместо сборки образа dev-контейнера для каждого проекта репозиторий указывает в `devcontainer.json` на опубликованный образ, и VS Code скачивает его напрямую — без этапа сборки, без ожидания. Образ включает всё необходимое для немедленной работы над Node.js-проектом: - **Ubuntu 26.04** в качестве базового образа (официальный `devcontainers/base` от Microsoft) - **Node.js LTS**, установленный через NodeSource - **Claude Code**, установленный глобально через npm - **zsh с oh-my-zsh**, настроенный с плагинами `git` и `fzf` как shell по умолчанию - **Стандартные CLI-инструменты** — git, curl, wget, jq, gpg, клиент OpenSSH, fzf - **sudo без пароля** для пользователя `vscode` Образ собирается как для `linux/amd64`, так и для `linux/arm64`, поэтому один и тот же тег работает и на Intel/AMD, и на Apple Silicon. ![VS Code, открытый в Nerdy Dev Container, с Claude Code, работающим во встроенном терминале и боковой панели](https://ru.nerdy.pro/open-source/nerdy-pro-dev-container/screenshot.webp) ### Сохранение состояния между пересборками Контейнер, который пересобирается при каждом изменении `Dockerfile`, удобен, только если состояние не сбрасывается вместе с ним. Шаблон подключает четыре именованных Docker-тома, чтобы ничего не терялось между пересборками: - Конфигурация и данные сессий Claude Code (директория `.claude`, через `CLAUDE_CONFIG_DIR`) - История команд zsh (`HISTFILE` на постоянном томе) - Кэш npm-пакетов - `known_hosts` для SSH, чтобы доверие к git-хостам не приходилось устанавливать заново каждый раз Аутентификация git пробрасывает SSH-агент хоста внутрь контейнера, а `gitconfig` хоста копируется автоматически — коммиты, сделанные внутри контейнера, указывают правильного автора без какой-либо ручной настройки. ### Начало работы Скопируйте шаблон в `.devcontainer/devcontainer.json`: ```json { "name": "my-project", "image": "ghcr.io/nerdy-pro/nerdy-pro-dev-container:latest" } ``` Переоткройте проект в dev-контейнере — Node.js, zsh и Claude Code будут готовы без этапа сборки. #### Аутентификация Claude Code Claude Code внутри контейнера считывает переменную окружения `CLAUDE_CODE_OAUTH_TOKEN` вместо интерактивного входа. Сгенерируйте долгоживущий токен на хосте: ```shell claude setup-token ``` Сохраните его в менеджере учётных данных хоста — Keychain на macOS, keyring рабочего стола (`secret-tool`) на Linux или DPAPI на Windows — и пробросьте в контейнер через запись `remoteEnv` в `devcontainer.json`, чтобы его никогда не приходилось вводить внутри самого контейнера. #### Первая git-операция Первый push или fetch в свежем контейнере запросит в терминале проверку ключа хоста — это разовое подтверждение, которое устанавливает SSH-доверие к этому git-хосту, а дальше оно кэшируется на постоянном томе `known_hosts`. ### Версионирование и релизы Образы собираются и публикуются только из релизов GitHub, а не при каждом коммите — изменение `Dockerfile` в `main` никак не влияет на других, пока не выпущен релиз. Каждый релиз публикует четыре тега: | Тег | Поведение | | --- | --- | | `1.0.0` | Зафиксирован — никогда не меняется | | `1.0` | Обновляется при патч-релизах | | `1` | Обновляется при минорных и патч-релизах | | `latest` | Всегда последний стабильный релиз | Такой набор тегов позволяет проекту зафиксировать точную версию ради воспроизводимости или следовать за `latest`, чтобы обновляться автоматически. ### Лицензия Dockerfile, шаблон `devcontainer.json` и документация распространяются под лицензией MIT. Всё, что установлено поверх базового образа — Ubuntu, Node.js, zsh, fzf, Claude Code — сохраняет свою исходную лицензию. #### Что такое nerdy-pro-dev-container? Это готовый мультиархитектурный Docker-образ для VS Code Dev Containers, включающий Node.js LTS, zsh и Claude Code, опубликованный в GitHub Container Registry как ghcr.io/nerdy-pro/nerdy-pro-dev-container. Проект указывает на этот образ в devcontainer.json и полностью пропускает этап сборки. #### Нужно ли собирать образ самостоятельно? Нет. Образ опубликован в GitHub Container Registry и скачивается напрямую VS Code. Достаточно указать в devcontainer.json на ghcr.io/nerdy-pro/nerdy-pro-dev-container:latest — и этап сборки пропускается полностью. #### Что предустановлено в контейнере? Ubuntu 26.04, Node.js LTS, Claude Code, zsh с oh-my-zsh (плагины git и fzf) и стандартные CLI-инструменты, включая git, curl, wget, jq, gpg и клиент OpenSSH. #### Как аутентифицируется Claude Code внутри контейнера? Выполните claude setup-token на хосте, чтобы сгенерировать долгоживущий токен, сохраните его в менеджере учётных данных хоста и пробросьте в контейнер как переменную окружения CLAUDE_CODE_OAUTH_TOKEN через remoteEnv в devcontainer.json. #### Сохраняется ли состояние при пересборке контейнера? Да. Четыре Docker-тома сохраняют конфигурацию Claude Code, историю zsh, кэш npm и SSH known_hosts между пересборками, поэтому повторная аутентификация и установление доверия не требуются каждый раз. #### Какие архитектуры поддерживаются? Образ собирается как для linux/amd64, так и для linux/arm64, поэтому один и тот же тег работает и на Intel/AMD, и на Apple Silicon. #### Как устроено версионирование? Образы публикуются только из релизов GitHub. Каждый релиз создаёт четыре тега — точную версию вроде 1.0.0, минорную версию вроде 1.0, мажорную версию вроде 1 и latest — так что проект может зафиксировать конкретную версию или следовать за последним стабильным релизом. ## Orosu Server https://ru.nerdy.pro/open-source/orosu-server > От японского 降ろす (*órosu*) — «разгружать», «сгружать». ### Проблема CI-системы хорошо справляются со сборкой ПО и плохо — с его доставкой. Перенос артефакта сборки из пайплайна на продакшн-машину обычно сводится к одному из нескольких привычных, но хрупких паттернов: - **Приватные SSH-ключи**, скопированные в секреты CI и расползающиеся по всем пайплайнам и репозиториям, где нужен деплой - **SFTP/rsync-скрипты**, скопированные из одного проекта в другой, каждый со своими, чуть отличающимися багами - **Захардкоженные пути и права доступа**, которые ломаются при малейшем изменении структуры сервера - **Продакшн-машины, напрямую доступные** для CI-раннеров, расширяющие поверхность атаки с каждым новым пайплайном - **Разрастание секретов**, из-за которого ротация одного скомпрометированного ключа превращается в аудит всех репозиториев, которые могли на него ссылаться Это не специфично для какой-то одной CI/CD-платформы — это стандартная форма задачи «выполнить скрипт на удалённой машине из пайплайна», и риски накапливаются с каждым проектом, который её перенимает. ### Как Orosu решает эту задачу Orosu устанавливается один раз на целевой машине как `orosu-server` и превращает деплой из открытой SSH-сессии в ограниченный, аутентифицированный запрос: 1. **CI собирает** приложение — бинарник, образ контейнера, статические файлы — что бы ни производил пайплайн 2. **CI запускает задачу** через соединение WebSocket, при необходимости прикладывая файлы 3. **`orosu-server` аутентифицирует** запрос с помощью подписей Ed25519 — ни паролей, ни общих секретов при передаче 4. **`orosu-server` выполняет заранее заданный скрипт** — один из фиксированного набора, настроенного на сервере, а не произвольную команду от CI 5. **Скрипт выполняет деплой**, используя файлы и аргументы, приложенные к задаче Никакого прямого SSH, никакого жонглирования учётными данными в каждом пайплайне и никакой серверной поверхности за пределами конкретных скриптов, явно настроенных оператором. ### Установка `orosu-server` и сопутствующий CLI `orosu-keygen` поставляются как пакеты для Debian/Ubuntu из apt-репозитория: ```shell curl -fsSL https://packages.nerdy.pro/NerdyPro.gpg | sudo gpg --dearmor -o /usr/share/keyrings/nerdy-pro.gpg echo "deb [signed-by=/usr/share/keyrings/nerdy-pro.gpg] https://packages.nerdy.pro/ stable main" | sudo tee /etc/apt/sources.list.d/nerdy-pro.list sudo apt update sudo apt install orosu ``` GitHub Releases также публикует готовые бинарники `orosu-server` и `orosu-keygen` напрямую — для машин, на которые нельзя добавить apt-репозиторий. ### Быстрый старт **Сгенерируйте пару ключей клиента** с помощью `orosu-keygen`, находясь в `/etc/orosu`: ```shell orosu-keygen --name my-ci-client --private-key-output my-ci-client.key --public-key-output my-ci-client.pub ``` Публичный ключ идёт в конфиг сервера; приватный становится секретом CI. **Настройте сервер** в `/etc/orosu/orosu-server.toml` — адрес прослушивания и публичный ключ клиента: ```yaml listen: tcp: "127.0.0.1:8081" clients: - name: my-ci-client secret_file: /etc/orosu/my-ci-client.pub ``` Сервер, слушающий localhost, обычно стоит за обратным прокси, который терминирует TLS и пробрасывает апгрейды WebSocket — в README приведена конфигурация nginx для этого случая. **Определите скрипт**, который клиенту разрешено запускать: ```bash #!/bin/bash echo "Hello, $1!" ``` ```yaml clients: - name: my-ci-client secret_file: /etc/orosu/my-ci-client.pub scripts: - name: test-script command: - "bash" - "/etc/orosu/scripts/test.sh" ``` **Запустите его из CI** с помощью сопутствующего GitHub Action, передав адрес сервера и приватный ключ клиента через секреты: ```yaml - name: Remotely execute a script uses: orosu-ci/orosu@v0 with: address: ${{ secrets.OROSU_SERVER_URL }} script: test-script key: ${{ secrets.OROSU_CLIENT_KEY }} arguments: "from CI pipeline" ``` ### Сквозное шифрование WSS/:termTLS защищает соединение, но когда TLS терминируется на обратном прокси перед `orosu-server` — как в стандартной схеме выше — аргументы скрипта, загруженные файлы и потоковый вывод остаются на этом прокси открытым текстом. Опциональное рукопожатие сквозного шифрования (X25519 + HKDF-SHA256 + ChaCha20-Poly1305) закрывает эту брешь: ```shell orosu-keygen --kind server --private-key-output server.key --public-key-output server.pub ``` Публичный ключ сервера добавляется в его конфиг и передаётся в CI как входной параметр `server_key` экшена. Переход на него добровольный, и каждая сторона включает его независимо от другой: сервер с настроенным шифрованием по-прежнему обслуживает клиентов, которые не передают `server_key`, точно так же, как раньше, и скоординированного обновления не требуется. ### Безопасность Безопасность заложена в проект с самого начала, а не добавлена постфактум: - **Криптографическая аутентификация** — каждая задача подписывается ключом Ed25519 клиента; в канале нет ни пароля, ни общего секрета, который мог бы перехватить атакующий. - **Закрытый набор действий** — CI может запускать только те скрипты, которые оператор явно задал на сервере. Пути от задачи CI к произвольной команде оболочки не существует. - **Защищённая обработка вложений** — имена записей в zip проверяются на обход путей, а количество записей и суммарный размер после распаковки ограничены, поэтому специально сформированное или чрезмерно большое вложение не может выйти за пределы каталога извлечения или исчерпать место на диске. - **Эшелонированная защита** — опциональное рукопожатие сквозного шифрования проверяет обмен X25519 на атаки low-order-point, дополняя конфиденциальность, которую оно и так обеспечивает. - **Безопасные права доступа по умолчанию** — `orosu-keygen` записывает файлы приватных ключей с правами `0600` независимо от текущего umask. Эти механизмы вошли в релиз 0.7.0 как обновление, не требующее дополнительных действий — без изменений конфига, CLI или протокола. Полную историю релизов см. в [`CHANGELOG.md`](https://github.com/orosu-ci/server/blob/main/CHANGELOG.md). ### Лицензия Orosu распространяется под лицензией Apache-2.0. #### Что заменяет orosu-server? Разрозненные шаги деплоя по SSH/SCP в CI-пайплайнах — приватные ключи в секретах, скопированные rsync-скрипты и напрямую доступные продакшн-серверы. CI запускает аутентифицированную задачу по WebSocket вместо открытия SSH-сессии. #### Как работает аутентификация? У каждого CI-клиента есть пара ключей Ed25519. Публичный ключ регистрируется в конфиге сервера; приватный хранится как секрет CI и используется для подписи запросов на выполнение задач. Сервер выполняет только те скрипты, которые явно настроены для этого клиента. #### Может ли CI выполнять произвольные команды на сервере? Нет. orosu-server выполняет только заранее заданные скрипты, настроенные в orosu-server.toml. Задача CI выбирает скрипт по имени и передаёт аргументы и вложения — она не может передать произвольную команду. #### Шифруется ли соединение? По умолчанию соединение защищено WSS/TLS, обычно терминируемым на обратном прокси перед orosu-server. Можно включить опциональный слой сквозного шифрования (X25519 + ChaCha20-Poly1305), чтобы аргументы скрипта, файлы и вывод оставались конфиденциальными даже для этого прокси. #### Как устанавливается orosu-server? Через apt-репозиторий (packages.nerdy.pro) для Debian/Ubuntu, либо в виде готовых бинарников, прикреплённых к GitHub Releases, для orosu-server и CLI orosu-keygen. #### Какие меры безопасности реализованы в orosu-server? Подписанные Ed25519 запросы, закрытый набор скриптов, заданных на сервере и доступных для запуска из CI, защита от обхода путей и ограничения размера при извлечении вложений, защита от атаки low-order-point в опциональном рукопожатии сквозного шифрования и безопасные права доступа по умолчанию для сгенерированных ключей. ## Как выбрать компанию по разработке на Flutter: чек-лист, который мы применили бы к себе https://ru.nerdy.pro/blog/how-to-choose-a-flutter-development-company **Коротко.** Каждый сайт агентства говорит одни и те же три вещи — senior-команда, отлаженный процесс, качество прежде всего, — поэтому слова как фильтр бесполезны. Работает проверка: выпущенные приложения, которые можно установить; инженерное мышление, которое можно прочитать; цены, опубликованные до продающего звонка; и рекомендатели, которым вы реально звоните. Этот пост — чек-лист, которым мы пользовались бы сами, окажись мы на стороне покупателя, и да — мы Flutter-агентство, поэтому в конце прогоняем его по себе и показываем, где проверить наши утверждения. --- ### Почему обычный путь поиска вводит в заблуждение Поищите «best Flutter development company» — и найдёте листиклы и каталоги. Полезно и то и другое, и то и другое требует расшифровки. Многие листиклы — pay-to-play или партнёрские: попадание в них — маркетинг, а не заслуга. Каталоги вроде Clutch полезнее, потому что верифицированные отзывы клиентов трудно подделывать массово, но читайте отзывы, а не бейджи: один подробный отзыв о реальном проекте расскажет больше, чем дюжина пятизвёздочных оценок по два предложения. Ни то ни другое не заменяет двадцати минут прямой проверки, описанной ниже. Каждый критерий здесь можно проверить, не вставая из-за рабочего стола, ещё до того, как вы забронируете звонок. ### Шесть вещей, которые нужно проверять, а не спрашивать #### 1. Выпущенные Flutter-приложения, которые можно подержать в руках Не скриншоты — страницы в сторах. Откройте портфолио, найдите ссылки на App Store и Google Play, установите два приложения и попользуйтесь ими десять минут. Полистайте быстро, поверните экран, уйдите в офлайн, вернитесь. Команда, которая выпускает отполированный продакшн на Flutter, не сможет этого скрыть, а команда, которая не выпускала, не сможет это подделать. Относитесь с подозрением к портфолио, где одни мокапы и ни одной ссылки; отнеситесь с пониманием к работе под NDA, но ожидайте хотя бы *каких-то* названных, устанавливаемых доказательств. #### 2. Инженерное мышление на публике Агентство, которое по-настоящему боролось с лентой реального времени или платёжным флоу, может написать об этом три конкретные страницы; то, которое не боролось, пишет «мы поставляем масштабируемые инновационные решения». Читайте блог. Ищите конкретику: названные компромиссы, цифры, вещи, которые пошли не так. Open source — тот же сигнал, только сильнее: пакеты на pub.dev с реальными пользователями означают, что код команды выдерживает публичную проверку, — и вы можете прочитать его сами, прежде чем платить за новый. #### 3. Кто на самом деле будет делать ваше приложение Люди на продающем звонке и люди в вашем репозитории — часто разные люди. Попросите имена и профили инженеров, назначенных на *ваш* проект, проверьте, что на странице команды — реальные люди с проверяемыми историями, и расценивайте отказ как ответ. Сеньорность во Flutter значит больше, чем подсказывает молодость пула: фреймворк лёгок на старте и беспощаден на продакшн-краях — скоупинг ребилдов, платформенные каналы, ревью в сторах, — где у senior-инженера шрамы, а у джуниора туториалы. #### 4. Цены и сроки, которые они готовы зафиксировать письменно Агентство, которое публикует вилки цен, решило, что его цифры выдержат сравнение; то, которое называет цену только после discovery-звонка, оставляет себе пространство подогнать смету под клиента. Та же логика с календарём: вопрос «Сколько занимает MVP?» заслуживает конкретной вилки с условиями, а не «зависит от обстоятельств». Зависит всегда — профессионалы говорят вам, *от чего именно*, и их условия показывают, как они думают. #### 5. Как выглядит неделя работы с ними Демо по расписанию, канал, где вы видите прогресс ежедневно, и честные отчёты, когда что-то сдвигается, — а каждому рекомендателю задайте прежде всего один вопрос: **«Когда что-то пошло не так, как вы об этом узнали?»** Если клиент обнаружил проблемы сам — уходите. Если агентство первым забило тревогу и сразу с планом — это тот вендор, который вам нужен, и никакая презентация с коммерческим предложением этого не покажет. #### 6. Что происходит после запуска Запуск — середина стоимости приложения, а не её конец: [у поддержки своя экономика](https://ru.nerdy.pro/blog/flutter-app-maintenance-cost). Убедитесь, что послерелизное предложение реально существует: мониторинг, обновления под меняющиеся политики сторов, выделенный канал реагирования. И проверьте владение кодом в черновике контракта, а не в разговоре: ваш репозиторий с первого дня, ваши сторы, ваши аккаунты. Любое трение в этом вопросе — репетиция переговоров об освобождении заложника. ### Пять красных флагов, на которых разговор заканчивается 1. **Сертификации как продающий аргумент.** «HIPAA-сертифицированные разработчики» — это маркер: [такой сертификации у HIPAA не существует](https://ru.nerdy.pro/services/flutter-app-development/healthcare), и агентство, которое начинает разговор с бейджей, делает ставку на то, что вы этого не знаете. Команды, *грамотные* в комплаенсе, вместо этого описывают, как они строят продукты. 2. **«Да» на всё.** Никаких возражений по объёму, срокам или реализуемости на этапе продажи — значит, возражения придут после предоплаты, в форме доплат за изменения объёма. 3. **Смета без вопросов.** Тот, кто оценивает ваше приложение, не разобрав объём, интеграции и невидимые 40% — флоу авторизации, удаление аккаунта, публикацию в сторы, — называет число, придуманное, чтобы начать отношения, а не закончить проект. 4. **Ни одного инженера в комнате.** Если вы не можете получить технического человека на предпродажный разговор, вы покупаете у продающей прослойки, которая будет транслировать ваш продукт через очередь тикетов. 5. **Портфолио без дат и ссылок на сторы.** Недатированной работе может быть десять лет; работа без ссылок может быть чьей угодно. ### Десять вопросов для звонка с шорт-листом 1. Какое из ваших выпущенных Flutter-приложений больше всего похоже на наше — и можем ли мы поговорить с этим клиентом? 2. Кто именно будет работать над нашим проектом и чем ещё эти люди заняты? 3. Что бы вы вырезали из нашего объёма и почему? 4. Какова ваша вилка оценки для нашего MVP и что двигает её вверх или вниз? 5. Как вы делаете платёжный флоу / функциональность реального времени / офлайн-синхронизацию вроде нашей? (Выберите свою самую сложную фичу; слушайте конкретику.) 6. Как выглядит наша обычная неделя — демо, каналы, отчётность? 7. Расскажите о проекте, который пошёл не так, и о том, что вы сделали. 8. Чего вы *не* делаете и к кому отправили бы нас вместо себя? 9. Сколько стоит послерелизная поддержка и что она покрывает? 10. Когда мы увидим репозиторий и на чьё имя он записан? (Правильный ответ: на ваше, с первого дня.) Вопрос 8 — тихий убийца. Студия, у которой нет честного ответа на «чего вы не делаете», никогда не задумывалась, в чём она на самом деле хороша. ### Как сравнивать сметы, не обманывая себя Сметы на «одно и то же приложение» вполне обоснованно расходятся в 3–5 раз — в основном потому, что они не на одно и то же приложение. Прежде чем сравнивать числа, нормализуйте объём: какие фичи, какие платформы, чей бэкенд, чей дизайн, какое тестирование, какая работа по публикации в сторы, какой послерелизный период. Структурированный разбор того, [что на самом деле определяет стоимость Flutter-разработки](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026), делает эту нормализацию посильной. Затем отбрасывайте самую дешёвую заявку, если не можете объяснить, *почему* она дешёвая, — обычные ответы: джуниоры, офшорная экономия, испаряющаяся в накладных расходах на коммуникацию, или объём, тихо исключающий невидимые 40%. Дешёвая инженерия, которую придётся перестраивать, — [самая дорогая](https://ru.nerdy.pro/blog/mobile-cost-reduction-case-studies); платить дважды — стандартный итог покупки только по цене. География, раз уж зашла речь: nearshore- и offshore-команды могут быть превосходными — результат предсказывает не точка на карте, а пересечение часовых поясов с вашим рабочим днём и сеньорность людей, которые реально коммитят код. Настаивайте на пересечении от трёх часов и на senior-инженерах, названных поимённо, — и вопрос локации в основном решится сам. ### Тот же чек-лист — на нас самих Мы — Nerdy Production, Flutter-агентство, и было бы странно опубликовать гайд по отбору и исключить из него себя. Итак, по пунктам: наши выпущенные работы — в [портфолио](https://ru.nerdy.pro/portfolio) со ссылками на сторы там, где позволяют NDA, — установите [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) или [Jepta](https://ru.nerdy.pro/portfolio/jepta) и оцените плавность скролла сами. Инженерное мышление — в [этом блоге](https://ru.nerdy.pro/blog) и в [open-source-пакетах](https://ru.nerdy.pro/open-source) на pub.dev, которые можно прочитать уже сегодня вечером. Люди — на [странице команды](https://ru.nerdy.pro/team), и человек на вашем звонке — инженер, обычно тот самый, который будет проектировать архитектуру вашего проекта. Цены и сроки [опубликованы](https://ru.nerdy.pro/services/flutter-app-development) — вместе с условиями, которые их двигают. За рекомендациями обращайтесь к нам — мы свяжем вас с клиентами, названными поимённо, — теми же, чьи цитаты есть на этом сайте. А вот публичный ответ на вопрос 8: мы не делаем чисто нативные сборки, мы [отговариваем клиентов от миграций](https://ru.nerdy.pro/services/flutter-app-development/flutter-migration), которые их продукту не нужны, а когда [Kotlin Multiplatform](https://ru.nerdy.pro/blog/flutter-vs-kotlin-multiplatform-2026) или [Expo](https://ru.nerdy.pro/blog/flutter-vs-expo-2026) подходит вашей команде лучше, мы говорим это прямо в самих сравнительных постах. Прогоните этот чек-лист по нам *и* по нашим конкурентам. Если кто-то обойдёт нас по нему — нанимайте их. ### Часто задаваемые вопросы #### Сколько стоит нанять компанию по разработке на Flutter? Опубликованные тарифы агентств за полную разработку обычно начинаются примерно с пятнадцати тысяч долларов за бюджетный MVP и доходят до девяноста тысяч и выше за enterprise-работу — наши открыты на страницах услуг, от MVP до e-commerce-тарифов, именно для того, чтобы их можно было сравнивать. Почасовые модели для выделенных senior-инженеров обычно попадают в диапазон пятьдесят–сто долларов в час в зависимости от географии и сеньорности. Считайте любую смету, названную без вопросов об объёме, маркетинговым числом и нормализуйте объём перед сравнением заявок — сметы на то, что звучит как одно и то же приложение, вполне обоснованно расходятся в разы, потому что различаются объёмы. #### Нанять Flutter-агентство или фрилансера? Сильный фрилансер — правильный выбор для небольшой, хорошо определённой разработки с одним ключевым навыком в центре, и неправильный — в тот момент, когда продукту нужны дизайн, бэкенд и мобильная работа, движущиеся параллельно, со страховкой непрерывности. Настоящий продукт агентства — избыточность и процесс: кто-то ревьюит код, кто-то может перехватить проект на середине, и доставка не зависит от календаря или здоровья одного человека. Честное сравнение стоимости — не дневные ставки, а то, во что обойдётся вашему запуску трёхнедельный простой на четвёртом месяце. #### Как проверить, что портфолио Flutter-агентства настоящее? Установите приложения. Настоящие записи в портфолио ведут на страницы в App Store и Google Play; откройте два приложения, попользуйтесь ими десять минут и заодно посмотрите отзывы в сторе и историю обновлений — страница, заброшенная с 2023 года, рассказывает собственную историю. По работе под NDA спросите, что конкретно агентство построило, а что унаследовало. Сверьте имя агентства с отзывами клиентов, где есть реальные имена и должности, и попросите поговорить с одним из этих клиентов напрямую; уверенная в себе студия воспринимает такую просьбу как рутину, а не как посягательство. #### Дешёвая офшорная Flutter-команда — это ложная экономия? Иногда — и сценарий провала достаточно предсказуем, чтобы его проверять заранее. Плохой код пишет не локация — его пишут джуниорские команды, слабые процессы ревью и нулевое пересечение часовых поясов, где бы они ни сидели. Отличные команды существуют в любой географии и любом ценовом диапазоне; проверять нужно тот же список, что и везде: названные поимённо senior-инженеры на вашем проекте, выпущенные работы, которые можно установить, и минимум три часа пересечения с вашим рабочим днём. Дешёвое становится дорогим на переделке — код, который придётся переписать до продакшн-качества, стоит дороже, чем построенный правильно с первого раза. #### Фиксированная цена или time and materials для Flutter-проекта? Фиксированная цена подходит хорошо очерченной первой разработке: агентство несёт риск оценки, вы получаете зафиксированное число, а привычка письменно фиксировать список вырезанного полезна обеим сторонам — так мы ведём MVP-проекты. Time and materials подходит продолжающейся эволюции после запуска или по-настоящему исследовательской работе, где фиксировать объём было бы фикцией. Отказываться нужно от схемы «фиксированная цена при размытом объёме» — она превращает каждый разговор в переговоры о доплатах. Компетентное агентство само скажет, какая модель подходит вашей ситуации, вместо того чтобы по умолчанию предлагать удобную себе. #### Можно ли сменить Flutter-агентство посреди проекта? Да, и это случается чаще, чем индустрия признаёт: унаследованные кодовые базы — заметная доля входящих проектов серьёзных агентств. Механика зависит от двух вещей, которые нужно закрепить в первый же день исходного контракта: репозиторий на ваше имя и аккаунты сторов под вашим контролем. Компетентный преемник начнёт с платного аудита существующего кода, а не с обещаний, и отчёт аудита заодно станет вашей переговорной позицией. Иногда вердикт — две недели исправлений, а не переписывание; в любом случае перехват должен начинаться с доказательств. --- *Составляете шорт-лист агентств прямо сейчас? [Забронируйте 30-минутный звонок](https://ru.nerdy.pro/contact) и возьмите с собой этот чек-лист — мы ответим на все десять вопросов, включая те, что нам не льстят.* ## Flutter vs Expo в 2026 году: фреймворк против платформы — честное сравнение https://ru.nerdy.pro/blog/flutter-vs-expo-2026 **Коротко.** В 2026 году сравнивать Flutter с «React Native», не произнося слова Expo, — значит сравнивать с workflow, с которого почти никто уже не начинает: новые RN-проекты — это Expo-проекты. Но это делает сравнение несимметричным, причём интересным образом: **Flutter — это фреймворк; Expo — фреймворк плюс коммерческая платформа** — сервис сборки, сервис обновлений, тулинг публикации. Выбрать Expo — значит выбрать удобства этой платформы вместе с её зависимостями. Выбрать Flutter — значит выбрать самодостаточный тулчейн и собирать сервисы самостоятельно. Оба варианта превосходны. Этот пост о том, какая *форма* подходит вашей команде. За сравнением на уровне фреймворков — рендеринг, точность UI, экосистемы — сначала загляните в пост [Flutter vs React Native в 2026 году](https://ru.nerdy.pro/blog/flutter-vs-react-native-2026); всё сказанное там применимо и здесь. Этот пост — о том, что Expo добавляет сверху и сколько это стоит. --- ### Наша позиция — сразу и открыто Мы — Flutter-first студия, и [портфолио](https://ru.nerdy.pro/portfolio) — наши доказательства, но мы выпускали и работу на React Native, включая современный Expo. Мы также держим собственную инфраструктуру доставки — с пайплайнами CI/CD, которыми владеем от начала до конца, — и это накладывает отпечаток на то, как мы взвешиваем managed-сервисы против самостоятельно управляемых. Там, где ниже по тексту этот перекос имеет значение, мы прямо его помечаем. --- ### Чем Expo на самом деле является в 2026 году Стоит проговорить, потому что «Expo» — это три разные вещи, которые часто смешивают: 1. **Слой фреймворка** — Expo SDK (56 на середину 2026-го, поверх React Native 0.85), курируемый набор нативных модулей, Expo Router для файловой навигации и config-плагины, которые генерируют нативные проекты, так что Xcode или Android Studio открывать почти не приходится. Legacy-архитектура исчезла полностью; Новая архитектура — просто то, как RN теперь работает. 2. **Опыт разработки** — dev-сборки и система prebuild. Старое возражение «Expo Go не умеет настоящие нативные модули» — уже история; dev-сборка включает любой нужный вам нативный код. 3. **Коммерческая платформа** — EAS: облачные сборки, публикация в сторы и обновления по воздуху как платные managed-сервисы. Слои 1 и 2 — open source и бесплатны. Слой 3 — продукт со страницей цен, и именно здесь сравнение с Flutter становится по-настоящему интересным. --- ### Пять осей сравнения, которые реально имеют значение #### 1. Скорость от первого дня до страницы в сторе Expo — лучший онбординг-трамплин в мобильной разработке, и точка: одна команда в терминале — и приложение запущено, облачные сборки без локальной установки Xcode, публикация с подсказками на каждом шаге. Первый день с Flutter тоже хорош — `flutter create`, `flutter doctor`, запуск, — но доставку в сторы собираете вы сами: подпись кода, provisioning, CI, тулинг публикации. **Честный вердикт:** для соло-разработчика или веб-команды, выпускающей своё первое мобильное приложение, трамплин Expo ощутимо мягче. Для команды с мобильным опытом разрыв схлопывается почти до нуля уже в первый спринт — [фаза публикации в сторы](https://ru.nerdy.pro/blog/how-long-to-build-a-flutter-app) в любом случае определяется циклами ревью и ассетами страницы приложения, а не тулингом сборки. #### 2. Обновления по воздуху Флагманская возможность Expo: EAS Update доставляет изменения уровня JavaScript в установленные приложения без ревью стора — фиксы за часы, поэтапные раскатки, откаты. В рамках политики сторов (JS-бандлы явно разрешены; нативный код по-прежнему проходит ревью) это реальное операционное преимущество. У Flutter встроенного эквивалента нет. Code push для Flutter есть у сторонних вендоров (серьёзный вариант — Shorebird, основанный выходцами из руководства Flutter), но это отдельное вендорское решение, а не часть фреймворка. **Честный вердикт:** если хотфикс без ревью — жёсткое операционное требование, это самый сильный отдельный аргумент Expo, и мы говорим это как студия, которой было бы приятнее сказать обратное. Две отрезвляющие оговорки: ревью в сторах в 2026 году обычно измеряется часами, а не днями, которые когда-то делали OTA почти обязательным; и команды регулярно переоценивают, как часто будут этим пользоваться, — дисциплинированный релизный поезд с хорошим мониторингом крэшей нуждается в нём редко. #### 3. Вопрос зависимости Это и есть разница форм. Expo-проект по умолчанию опирается на EAS в сборках, обновлениях и публикации — managed-сервисы с оплатой по потреблению, которые становятся частью вашего пути доставки, и на масштабе команды это вполне реальная статья расходов в несколько сотен долларов в месяц (их бесплатный тариф для небольших приложений действительно рабочий). Всё, что делает Expo, можно захостить самостоятельно — сборки локально, обновления на своём сервере, — но тогда вы сами обслуживаете ту машинерию, от которой платформа и должна была вас избавить. Во Flutter-проекте платформы в контуре нет: тулчейн локален и бесплатен, а доставка работает на том CI, за который вы уже платите. Цена в том, что этот пайплайн собираете и поддерживаете *вы* — это реальное инженерное время, и для команды без опыта работы с CI это не погрешность округления. **Честный вердикт:** команды с существующей DevOps-экспертизой получают независимость Flutter почти бесплатно. Командам без неё часто выгоднее заплатить EAS, чтобы проблема исчезла, — честный размен денег на операционную поверхность, а наш перекос в сторону собственной инфраструктуры — ровно это: наш перекос, оценённый по меркам студии, которая выпускает приложения еженедельно. #### 4. Границы нативного кода Config-плагины и prebuild позволяют Expo-команде уйти удивительно далеко, не прикасаясь к нативным проектам, — а когда кастомная нативная работа всё же нужна, вы пишете модуль и двигаетесь дальше; стены, которую люди помнят по 2021 году, больше нет. Граница Flutter устроена иначе: платформенные каналы в настоящие iOS/Android-проекты, которые ваши с первого дня, всегда лежат в репозитории — и их всегда можно открыть. **Честный вердикт:** почти паритет по возможностям; разница — в философии. Expo прячет нативные проекты, пока вы сами не решите в них войти; Flutter вручает их вам с самого начала. Команды с нативными инженерами обычно предпочитают прозрачность Flutter; команды без них — то, что абстракция Expo держится дольше. #### 5. Куда тянет гравитация каждого стека - **Гравитация Expo — веб-экосистема React**: один язык с вашим веб-приложением, Expo Router публикует маршруты в web, React-инженеры контрибьютят с первого дня. Если ваша организация имеет форму React, Expo накапливает преимущества, с которыми Flutter сравниться не может. - **Гравитация Flutter — широта поверхностей и владение рендерингом**: одни и те же пиксели на iOS, Android, [web там, где он уместен, desktop и embedded](https://ru.nerdy.pro/blog/flutter-vs-native-2026), плюс история про насыщенный анимацией UI с собственной дизайн-системой — та самая, что сделала его правильным выбором для [Arcana](https://ru.nerdy.pro/portfolio/arcana) и [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf). **Честный вердикт:** без изменений относительно [нашего сравнения с RN](https://ru.nerdy.pro/blog/flutter-vs-react-native-2026), потому что в основе тот же вопрос: решают состав команды и дорожная карта поверхностей, а не фичи тулинга. --- ### Выбирайте Expo, если… - Ваша команда — React/TypeScript, и вы хотите, чтобы она была продуктивна в мобильной разработке уже на этой неделе. - Хотфикс без ревью стора — операционное требование, которым вы действительно будете пользоваться. - Вы предпочтёте платить платформе, а не нанимать людей под пайплайн доставки, — легитимный размен, особенно при команде меньше пяти инженеров. - Web плюс mobile из одной React-кодовой базы — реальная форма вашего продукта. ### Выбирайте Flutter, если… - Вы владеете своей дизайн-системой и хотите идентичный рендеринг на каждой поверхности — или ваш UI насыщен анимацией и кастомной отрисовкой на канвасе. - Вы хотите тулчейн без коммерческой платформы в пути доставки — всё локально, всё ваше. - В вашей дорожной карте есть desktop или embedded, где Expo не конкурирует. - Вы нанимаете выделенных мобильных инженеров, а не расширяете веб-команду, — расчёт, на котором построена [вся наша практика](https://ru.nerdy.pro/services/flutter-app-development). --- ### Короткий фреймворк принятия решения **Expo — правильный дефолт для React-организаций и для маленьких команд, которые хотят, чтобы инфраструктура доставки была заботой кого-то другого. Flutter — правильный дефолт для команд, которые владеют своей дизайн-системой, своим пайплайном и дорожной картой шире двух сторов.** Если ни одна из этих формулировок явно не описывает вас — решайте по сравнению на уровне фреймворков (модель UI, экосистема, найм) в [посте про RN](https://ru.nerdy.pro/blog/flutter-vs-react-native-2026), потому что удобства платформы — меньшая половина решения на пять лет. --- ### Часто задаваемые вопросы #### Готов ли Expo к продакшну в 2026 году — или это инструмент для прототипов? Полностью готов, а репутация инструмента для прототипов устарела на годы. Expo — способ по умолчанию, которым сегодня строятся новые React Native-приложения: SDK плотно следует за React Native, dev-сборки включают любой кастомный нативный код, а старые ограничения Expo Go давно перестали быть сутью разговора. Настоящий вопрос 2026 года не в том, может ли Expo выпускать серьёзные приложения — может, — а в том, хотите ли вы его коммерческую платформу, EAS, в своём пути доставки; это бизнес-решение в той же мере, что и техническое. #### Есть ли у Flutter обновления по воздуху, как у Expo? Встроенных — нет. Expo доставляет обновления уровня JavaScript в установленные приложения через EAS Update, в рамках политики сторов, и для хотфиксов это реальное операционное преимущество. Эквивалент для Flutter предлагают сторонние игроки — надёжный вариант здесь Shorebird, созданный бывшими руководителями команды Flutter, — и это отдельное вендорское решение, а не first-party-возможность. Стоит честно оценить масштаб, прежде чем это определит выбор фреймворка: ревью в сторах в 2026 году обычно проходит за часы, а команды с дисциплинированным релизным поездом прибегают к OTA-обновлениям куда реже, чем ожидали. #### Expo бесплатен? Фреймворк и тулинг — open source и бесплатны, включая локальные сборки на собственном железе и даже self-hosted-обновления. Денег стоит EAS — облачная платформа, которой большинство Expo-команд реально пользуется: облачные сборки, публикация в сторы, обновления по воздуху. У неё есть рабочий бесплатный тариф и платные планы с оплатой по потреблению, которые команда с несколькими приложениями должна закладывать как реальную ежемесячную статью расходов. Поэтому сравнение с Flutter — не «бесплатное против платного», а оплата платформы против собственного инженерного времени на сборку той же машинерии доставки. #### Умеет ли Expo всё, что умеет bare React Native? В 2026 году — фактически да. Config-плагины и система prebuild генерируют нативные проекты, dev-сборки несут произвольные нативные модули, а «eject» как страшная дверь в один конец — устаревшее понятие: забрать нативные проекты под свой контроль можно в любой момент. Практическая разница с Flutter — философская: Expo прячет iOS- и Android-проекты, пока вы сами не решите в них войти, а Flutter кладёт их в ваш репозиторий с первого дня. Команды с нативной экспертизой часто предпочитают прозрачность Flutter; командам без неё выгодно, что абстракция Expo держится дольше. #### Мы — веб-команда на React. Стоит ли нам вообще рассматривать Flutter? Рассмотреть стоит, но честный дефолт для вас — Expo. Ваши инженеры сохраняют язык, паттерны управления состоянием и большую часть тулинга, а Expo Router может публиковать общие маршруты в web — преимущества, которые Flutter структурно не может предложить React-организации. Случаи, когда Flutter всё же выигрывает для вас: продукт с сильно анимированным или канвас-ориентированным UI, дизайн-система, которая должна рендериться попиксельно одинаково везде, или дорожная карта, дотягивающаяся до desktop- или embedded-поверхностей. Если ничего из этого не про вас, выбрать Flutter значило бы выбрать наше предпочтение вместо вашего преимущества. #### Что дешевле построить и эксплуатировать: Flutter или Expo? Стоимость разработки при сравнимом объёме сходится в пределах примерно десяти процентов — выбор фреймворка двигает бюджеты куда меньше, чем объём и интеграции, что совпадает с цифрами в нашем гайде по стоимости. Расходы на эксплуатацию различаются формой, а не размером: Expo-команда обычно платит EAS подписку и сборы по потреблению в обмен на то, что не эксплуатирует инфраструктуру сборок и обновлений, а Flutter-команда платит инженерным временем на CI, который полностью контролирует. При команде меньше пяти инженеров без DevOps-опыта размен Expo обычно выгоднее; с существующим пайплайном независимость Flutter почти бесплатна. --- *Взвешиваете Expo против Flutter для реального продукта? [Забронируйте 30-минутный звонок](https://ru.nerdy.pro/contact) — мы честно скажем, какая форма подходит вашей команде, даже если ответ — Expo.* ## Flutter vs Kotlin Multiplatform в 2026 году: две разные ставки — честное сравнение https://ru.nerdy.pro/blog/flutter-vs-kotlin-multiplatform-2026 **Коротко.** Flutter и Kotlin Multiplatform — не две реализации одной идеи. Flutter — одна ставка: один движок рендеринга, один UI, все платформы. KMP — другая ставка: общая логика и нативный UI каждой платформы — если только вы не добавите сверху Compose Multiplatform, и тогда это становится ставкой «в форме Flutter», но с более молодой экосистемой. Какая из них правильная, куда сильнее зависит от навыков вашей команды и амбиций продукта по части UI, чем от любых бенчмарков. Мы разрабатываем на Flutter — и ниже конкретно скажем, когда сами отправили бы вас в сторону KMP. Если нужен только ответ — переходите сразу к [фреймворку принятия решения](#%D0%BA%D0%BE%D1%80%D0%BE%D1%82%D0%BA%D0%B8%D0%B9-%D1%84%D1%80%D0%B5%D0%B9%D0%BC%D0%B2%D0%BE%D1%80%D0%BA-%D0%BF%D1%80%D0%B8%D0%BD%D1%8F%D1%82%D0%B8%D1%8F-%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F). Сравниваете Flutter с React Native? Это [отдельный пост](https://ru.nerdy.pro/blog/flutter-vs-react-native-2026). С полностью нативной разработкой на Swift/Kotlin? [Тоже отдельный пост](https://ru.nerdy.pro/blog/flutter-vs-native-2026). --- ### Наша позиция — сразу и открыто Мы — Flutter-first агентство; [работы публичны](https://ru.nerdy.pro/portfolio). Мы оценивали KMP для клиентов — основатель, который пишет этот текст, годами выпускал продакшн-код на Kotlin и следит за развитием KMP и SwiftUI, — но мы **не выпускали продакшн-приложение на KMP**, и было бы нечестно делать вид, что это не так. Поэтому этот пост аккуратен в двух вещах: там, где мы сравниваем developer experience, мы говорим о том, что показали наши оценочные проекты, а не намекаем на продакшн-пробег; а там, где KMP действительно лучший ответ, мы говорим это прямо — потому что сравнение, написанное так, чтобы всегда заканчиваться выводом «наймите нас», для вас бесполезно. --- ### Что реально изменилось к 2026 году KMP давно перестал быть экспериментальным вариантом, и сравнение, которое обращается с ним как с экспериментом, устарело. - **Сам KMP стабилен и одобрен с обеих сторон.** JetBrains объявила Kotlin Multiplatform стабильным в конце 2023 года, а Google на I/O 2024 официально рекомендовала его для общей бизнес-логики на Android. Netflix, McDonald's и Cash App используют общий KMP-код в продакшне в серьёзном масштабе. - **Compose Multiplatform достиг стабильности на iOS в 2025 году.** Слой общего UI от JetBrains теперь стабилен на Android, iOS и desktop; web-таргет всё ещё в бете. Это важно, потому что меняет само значение слова «KMP»: не только общая логика под нативными UI, но и общий UI тоже. - **Активный фронтир сейчас — сторона Swift.** Прямой экспорт Kotlin в Swift — без прослойки из Objective-C bridging headers — вышел в экспериментальном статусе и стоит в планах JetBrains на стабилизацию. Пока он не стабилен повсюду, на границе Kotlin и iOS остаётся трение, которое замечают нативные Swift-разработчики: экспортированные API ощущаются переведёнными, а не спроектированными. - **Flutter тем временем не стоял на месте.** Рендеринг на Impeller — устоявшийся дефолт, а десятилетие экосистемы фреймворка — pub.dev, тулинг, пул найма — это ровно то, с чем приходится конкурировать более молодому стеку. --- ### Сравнение, которое большинство постов проводит неправильно «Flutter vs KMP» — на самом деле **два разных сравнения**, и когда их сваливают в одну кучу, команды и приходят к неверным выводам. **Сравнение A: Flutter против KMP с нативными UI.** Бизнес-логика — сеть, хранение данных, доменные правила, вью-модели — выносится в общий Kotlin-код, а интерфейс строится дважды: SwiftUI на iOS, Jetpack Compose на Android. Типичная доля общего кода — 40–70% кодовой базы. Взамен каждое приложение неотличимо от полностью нативного, потому что UI и *есть* полностью нативный. **Сравнение B: Flutter против Compose Multiplatform.** Общим становится и UI. CMP на iOS рисует собственные пиксели через рендеринг семейства Skia — ровно та архитектурная ставка, которую Flutter сделал в 2017-м. В этот момент вы выбираете между двумя реализациями *одной и той же* идеи, где преимущества Flutter — зрелость, глубина экосистемы и широта платформ, а преимущество CMP в том, что язык и половина тулчейна — те самые, в которых уже живёт ваша Android-команда. Постоянно спрашивайте себя, в каком из двух сравнений вы на самом деле находитесь. Ответ определяет почти всё, что ниже. --- ### Шесть осей сравнения, которые реально имеют значение #### 1. Стратегия UI — настоящая развилка - **KMP с нативными UI** даёт то, чего Flutter архитектурно дать не может: каждый экран собран из собственных компонентов платформы, с платформенно-точным поведением в каждой детали физики скролла, контекстного меню и атрибутов accessibility. Если «неотличимо от нативного» — жёсткое требование, а в enterprise-продажах в банкинге или медицине оно иногда прописано в контракте, — это честный способ его выполнить, сохранив общую логику под интерфейсом. - **Flutter** даёт противоположную гарантию: одни и те же пиксели везде, дизайн-система, которой вы владеете целиком, и одна реализация каждого экрана. Для насыщенного брендом и анимацией кастомного UI — конца спектра, где живёт [Arcana](https://ru.nerdy.pro/portfolio/arcana), — построить один раз лучше, чем строить дважды, и по стоимости, и по консистентности. - **CMP** на этой оси стоит рядом с Flutter, а не с нативной разработкой. **Честный вердикт:** с этой оси и нужно начинать решение. Всё остальное — уточнения. #### 2. Состав команды — где на самом деле принимается большинство решений KMP с нативными UI по-прежнему требует людей, которые пишут SwiftUI, и людей, которые пишут Compose. Вы убрали дублирующуюся *логику*, но не потребность в двух наборах платформенных навыков. Это правильный размен для организаций, у которых уже есть нативные команды, — а именно они сегодня и выпускают KMP в масштабе. Flutter переворачивает уравнение: одна команда, один язык, обе платформы — поэтому студия из шести человек вроде нашей может тянуть [семь продакшн-приложений](https://ru.nerdy.pro/portfolio). Если вы нанимаете с нуля и Kotlin-экспертизы у вас нет, Flutter — это меньшая организация, которую придётся построить. **Честный вердикт:** есть Android/Kotlin-команда → KMP заслуживает роли вашей гипотезы по умолчанию. Мобильной команды ещё нет → с Flutter организация получится меньше. #### 3. Экосистема и шов на стороне iOS У экосистемы Flutter десятилетие глубины: пакеты на pub.dev для большинства значимых SDK, зрелый тулинг, огромный корпус продакшн-историй (мы [написали свою долю](https://ru.nerdy.pro/blog/extraetf-realtime-fintech-flutter-go)). Библиотечная экосистема KMP реальна и растёт — kotlinx, Ktor, SQLDelight, Koin надёжны, — но тоньше в длинном хвосте, а шов на стороне iOS — то место, где это чувствуется в оценочных проектах: пока прямой экспорт в Swift не стабилен повсюду, общие API, потребляемые из Swift, могут ощущаться как переведённый Kotlin, а не как нативный Swift, — и у ваших iOS-инженеров будет об этом своё мнение. **Честный вердикт:** сегодня — однозначно Flutter, с честной сноской: это самый быстро движущийся фронт KMP. #### 4. Постепенное внедрение — тихая суперсила KMP Flutter нельзя осмысленно внедрять в существующее нативное приложение по 10% за раз; add-to-app существует и работает (мы [строим на нём миграции](https://ru.nerdy.pro/services/flutter-app-development/flutter-migration)), но это путь к *замене* UI, а не к вечному сосуществованию. KMP спроектирован для обратного: выделите один модуль — сеть, синхронизацию, правила ценообразования, — сделайте его общим, выпустите, повторите. Без переписывания, без большого решения, обратимо на каждом шаге. **Честный вердикт:** для здорового существующего нативного приложения, которое хочет перестать писать логику дважды, KMP — правильный инструмент, а Flutter — неправильный. То же самое мы говорим на нашей [странице миграций](https://ru.nerdy.pro/services/flutter-app-development/flutter-migration): стабильные нативные приложения с довольными командами не нужно переписывать ни во что. #### 5. Производительность Скучный ответ, которого мы честно придерживаемся: для подавляющего большинства приложений все три конфигурации достаточно быстры, и производительность решают архитектура и дисциплина ребилдов, а не фреймворк. Impeller у Flutter и нативные UI у KMP рендерят на скорости платформы; канвас-рендеринг CMP на iOS — тот же подход, который Flutter годами закалял, только в исполнении команды, которая находится ближе к началу той же дороги. Если ваш продукт живёт на границе возможностей рендеринга — графики в реальном времени, тяжёлая анимация, — зрелость Flutter там доказана; именно таким было решение по [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf). #### 6. Долгосрочные риски Риск Flutter — концентрация: он процветает, пока этого хочет Google, что смягчают открытый код и огромная установленная база. Риск KMP меньше и другой формы: Kotlin — официальный язык Android, весь бизнес JetBrains — инструменты для разработчиков, и Google со своей стороны поддерживает историю с общим кодом. Но конкретно CMP молод, и ставить общий UI на него — значит ставить на его дорожную карту. Ни один из рисков не оправдывает страха; оба оправдывают написание бизнес-логики за чистыми границами модулей — это и есть настоящая страховка, и она не зависит от фреймворка: тот же аргумент — в [FAQ нашей основной страницы услуг](https://ru.nerdy.pro/services/flutter-app-development). --- ### Выбирайте Kotlin Multiplatform, если… - У вас есть существующее здоровое нативное приложение — особенно Android-first с Kotlin-командой — и боль в том, что логика пишется дважды, а не в UI. - Платформенно-точный нативный UI — жёсткое требование, и вы можете укомплектовать команду под SwiftUI + Compose. - Вы хотите постепенного, обратимого внедрения внутри приложений, которые уже выпускаете. - Ваша организация уже думает на Kotlin: серверный Kotlin, senior-инженеры на Android, тулинг JetBrains повсюду. ### Выбирайте Flutter, если… - Вы строите новый продукт и хотите, чтобы одна команда выпускала в оба стора, — экономика, на которой работает [всё наше портфолио](https://ru.nerdy.pro/portfolio). - Вы владеете своей дизайн-системой и хотите попиксельно одинаковый вид везде, включая [web и desktop там, где они оправданны](https://ru.nerdy.pro/blog/flutter-vs-native-2026). - Вам нужны более глубокая экосистема и больший пул найма под сам фреймворк уже сегодня, а не после следующей вехи чьей-то дорожной карты. - Альтернатива, которую вы на самом деле рассматриваете, — CMP; в этом случае вы так или иначе выбираете архитектуру Flutter, и зрелость на стороне оригинала. --- ### Короткий фреймворк принятия решения **Существующее нативное приложение, Kotlin-навыки в команде, UI остаётся нативным: KMP — и вам не нужно агентство вроде нашего, чтобы это услышать. Новый продукт, одна команда, кастомная дизайн-система, оба стора на один бюджет: Flutter. Тянет к Compose Multiplatform на новом продукте: поймите, что вы делаете ставку Flutter более молодым стеком, и принимайте решение по зрелости экосистемы, а не по симпатии к языку.** Если всё ещё сомневаетесь, решающий фактор — команда, которую вы реально будете содержать через два года. --- ### Часто задаваемые вопросы #### Готов ли Kotlin Multiplatform к продакшну в 2026 году? Да, без оговорок — для общей бизнес-логики: KMP стабилен с конца 2023 года, Google рекомендует его для общей логики на Android, а компании вроде Netflix, McDonald's и Cash App используют его в продакшне в масштабе. Общий UI — половина с нюансами: Compose Multiplatform стабилен на Android, iOS и desktop с 2025 года, но его iOS-экосистема молода на фоне флаттеровской, а web-таргет всё ещё в бете. «Готов к продакшну» и «закалён в боях» — разные утверждения, и право на второе для общего UI на iOS ещё только зарабатывается. #### Заменяет ли Kotlin Multiplatform Flutter? Нет — в классической форме он отвечает на другой вопрос. KMP делает общей бизнес-логику, а каждая платформа сохраняет нативный UI, так что вы по-прежнему строите два интерфейса и держите два набора UI-навыков; Flutter делает общим всё, включая пиксели, поэтому одна команда выпускает в оба стора. Конфигурации сходятся, только если вы берёте Compose Multiplatform для общего UI — и в этот момент вы выбрали архитектурную ставку Flutter (канвас-рендеринг, одно дерево виджетов), реализованную более молодым стеком. Разные ставки подходят разным командам; ни одна не заменяет другую. #### Можно ли сделать UI-код общим в Kotlin Multiplatform? Да — через Compose Multiplatform, который стабилен на Android, iOS и desktop; web всё ещё в бете. На iOS он рисует собственные пиксели через рендеринг семейства Skia, а не использует UI-компоненты Apple — та же архитектура, что у Flutter, поэтому команде, выбирающей CMP для нового приложения, стоит сравнивать его с Flutter напрямую. Размен такой: знакомый Kotlin и переиспользование навыков Jetpack Compose с Android — против десятилетия экосистемы, тулинга и продакшн-закалки Flutter ровно на этом подходе к рендерингу. #### У нас уже есть Android-приложение на Kotlin. KMP или Flutter? Почти наверняка KMP — и Flutter-агентство, утверждающее обратное, продаёт, а не советует. Ваш Kotlin-код, навыки команды и существующая архитектура переносятся напрямую: выделяйте общие модули постепенно, добавьте iOS-приложение, переиспользующее логику, и держите каждый шаг обратимым. Flutter означал бы переписывание работающего кода и переобучение работающей команды — цена, которая окупается, только если вам к тому же нужно то, что уникально предлагает Flutter: например, одна собственная дизайн-система на всех платформах или полный уход от платформенных команд. #### Compose Multiplatform лучше Flutter для нового приложения? Для большинства новых продуктов в 2026 году Flutter остаётся более безопасной версией той же ставки. Оба рисуют собственный UI на каждой платформе; Flutter обкатывает эту архитектуру в продакшне с 2018 года — с экосистемой пакетов, devtools и пулом найма в качестве доказательств, — а CMP достиг стабильности на iOS в 2025-м и эту глубину ещё набирает. Аргумент за CMP против Flutter — организационный, и он реален: сильная Kotlin-команда, ежедневно пишущая Jetpack Compose, сохраняет свой язык, IDE и мышечную память. Взвешивайте непрерывность команды против зрелости экосистемы — вот в чём настоящее решение. #### Разрабатываете ли вы приложения на Kotlin Multiplatform? В продакшне — нет, и мы говорим это прямо, а не блефуем: наши выпущенные работы — Flutter, плюс нативная и бэкенд-инженерия вокруг него. Мы серьёзно оценивали KMP — в том числе для клиентов, чья ситуация указывала в его сторону, — и когда оценка говорит, что ваш ответ — KMP, мы сообщаем это и помогаем определить, что выносить в общий код первым, потому что честное «нет» строит больше доверия, чем натянутое «да». Если хотите, чтобы это сравнение прогнали по вашему конкретному продукту и команде, — именно для такого разговора и существует наш discovery-звонок. --- *Выбираете между Flutter и KMP для реального продукта? [Забронируйте 30-минутный звонок](https://ru.nerdy.pro/contact) — мы дадим честную рекомендацию, включая вариант «используйте KMP, и мы вам для этого не нужны».* ## Пять состояний, которых не может быть, — и ваш код разрешает все пять https://ru.nerdy.pro/blog/impossible-states-dart-sealed-classes Вот задача, которую вы писали раз двадцать: экран со списком товаров. Загрузить с сервера, обработать ошибку, отрисовать список. Мы отдали её агенту — Claude Opus 5 — и получили вот это. ```dart class ProductsState { final List? items; final bool isLoading; final Exception? error; } ``` Выглядит как то, что вы написали бы сами. В этом и суть: модели обучены ровно на том, что мы все годами писали руками. Теперь посчитаем. Три поля: bool, nullable-список, nullable-ошибка. У каждого по два значимых состояния. Два на два на два — **восемь комбинаций.** Сколько из них что-то значат? | `isLoading` | `items` | `error` | Что это значит | | --- | --- | --- | --- | | `true` | `null` | `null` | Загружаем | | `false` | `null` | есть | Упало | | `false` | есть | `null` | Загрузили | | `true` | есть | `null` | ? | | `true` | `null` | есть | ? | | `true` | есть | есть | ? | | `false` | есть | есть | ? | | `false` | `null` | `null` | ? | Три. Остальные пять — не ошибки компиляции. Это валидный код. Он собирается, проходит ревью, уезжает в прод и лежит там. `isLoading` — `true`, и рядом лежит ошибка: что рисуем? Или всё пустое: не грузим, списка нет, ошибки нет. Мы ещё не начинали — или сходили и вернулись ни с чем? Код не знает. Он гадает. Или гадает следующий, кто откроет файл. ### Коротко - **Невозможное состояние — то, которое ваш тип разрешает, а предметная область запрещает.** Это то же самое, что сломанный инвариант, только с другой стороны. - **Цена — не баги в рантайме.** Цена — договорённость, которую никто не записал, ветки, которые никто не рассматривал, и налог, который вы платите при каждом чтении, а не один раз за инцидент. - **Инвариант есть всегда.** Вопрос только в том, держит ли его человек — комментарием, страницей в вики, `assert`, который не работает в релизе, — или держать нечего, потому что сломать его не компилируется. - **`enum Status` плюс nullable-поля делает хуже**, а не лучше: двенадцать комбинаций вместо восьми, и всё те же три осмысленных. - **Sealed-класс с non-nullable полями оставляет только осмысленные состояния** и делает остальные несобираемыми. Ни одного булева поля, ни одного nullable, ни одного `!`. - **Проверка на полноту — вот ради чего всё.** Добавляете четвёртое состояние — и компилятор перечисляет все места, где оно не обработано. Это обратная связь, на которую агент умеет реагировать, а не стена, в которую он упирается. - **Одна ветка `_ =>` выключает всё это молча.** Это единственное правило ревью, которое стоит унести из статьи. - **Алгебраические типы чинят не всё.** Они убирают невозможные комбинации состояний, но не невозможные значения, и ничего не делают на границе системы. ### Невозможные состояния и их более старое имя Сначала словарь, потому что одну вещь называют двумя словами. **Невозможное состояние — то, которое ваш тип разрешает, а предметная область запрещает.** Экран не может одновременно грузиться и показывать ошибку. Тип это позволяет. Это дыра. Более старое и более точное имя — **инвариант**: правило, которое должно выполняться всегда. *Если грузим, данных ещё нет. Если есть ошибка, у неё есть сообщение.* Невозможное состояние — это просто сломанный инвариант. Один и тот же факт с двух сторон: инвариант — как должно быть, невозможное состояние — что получилось, когда не так. Запомните это слово. Именно на нём всё разворачивается. #### Почему это проблема и почему обычное объяснение неверно Обычно говорят так: невозможные состояния — это баги, которые не падают, а тихо рисуют не тот экран. Звучит убедительно, и если бы дело было в этом, ответ был бы дешёвым: тесты, QA, алерт на пустой экран. Поймали, починили, поехали дальше. Но проблема не в этом. Она вообще не про рантайм. Цены здесь три, и все три уже лежат в вашем коде **прямо сейчас**, даже если приложение работает идеально. **Раз: договорённость, которую никто не записал.** Посмотрите на класс агента ещё раз. Где написано, что при `isLoading == true` нельзя читать `items`? Нигде. Где написано, что `error` и `items` никогда не заполнены одновременно? Нигде. Эти правила существуют, на них опираются, но их нет ни в типе, ни в комментарии. Они живут в голове того, кто написал класс, а все остальные восстанавливают их из кода, читая `if`-ы и додумывая замысел. Пять минут на файл — каждому, кто его откроет. **Два: количество веток.** Восемь комбинаций — это восемь случаев, с каждым из которых кто-то должен что-то сделать: обработать или сознательно исключить. Обработаны три. Остальные пять не обработаны и не исключены — их **никогда не рассматривали.** Разница важная: исключённый случай — это решение, нерассмотренный — дыра. И счёт растёт не сложением, а умножением. | Полей, которые ходят вместе | Комбинаций | Осмысленных | | --- | --- | --- | | 3 | 8 | 3 | | 4 | 16 | 3–4 | | 5 | 32 | 3–4 | | 6 | 64 | 3–4 | **Три: вы платите при каждом чтении, а не один раз за баг.** Это главное. Баг чинится один раз. Договорённость, которой нет в типе, каждый раз выводится заново — на ревью, при добавлении фичи, при разборе инцидента. Это не разовая цена, это налог. Отсюда же берётся привычный костыль. Дыры затыкают условиями: тут проверим, что не грузим, там — что ошибка не null. Каждый такой `if` — заплатка на дыре, которую вы сами прорезали, объявив три независимых поля. Дыр пять, заплаток обычно две — те, которые уже выстрелили. И заметьте попутно: ничего из этого **не падает.** Ни краш-лога, ни стектрейса, ничего в трекере ошибок. Так что «мониторинг поймает» тут не работает. Но это следствие, а не суть. Что изменилось за пару лет — **объём.** Раньше такой класс появлялся раз в неделю, и его писал человек, потративший хотя бы тридцать секунд на размышление о полях. Теперь он появляется за двенадцать секунд, и его никто не читает — ни агент, ни вы. А ревьюер — это человек, который смотрит на три поля, а не держит в голове восемь комбинаций. #### Кто держит инвариант Итак, вопрос никогда не в том, есть ли у вас инвариант. Он есть всегда. Вопрос в том, **кто за него отвечает.** Обычно человек. Комментарий над классом. Страница в вики, которую не открывали с позапрошлого года. Договорённость, принятая на ревью, которую помнят двое из пяти. В лучшем случае `assert` в конструкторе. > **assert в Dart — это не инвариант** > > Инструкции `assert` вырезаются из релизной сборки. Не «пропускаются» — условие вообще не вычисляется, как и аргументы assert, поэтому дорогая проверка внутри assert ничего не стоит в проде. Практическое следствие — ровно то, о чём забывают: инвариант, который вы защитили ассертом, для ваших пользователей не существует. Он работал во время разработки, когда вы и так смотрели, и его нет ровно там, где вы не смотрите. Assert — инструмент отладки, а не ограничение. Всё это — инвариант, который кто-то **держит**: руками, вниманием, памятью. Альтернатива — устроить так, чтобы держать было нечего, потому что нарушение невыразимо в типе. Не «мы проверяем, что так не бывает», а «так не компилируется». И вот почему это перестало быть академическим различием. Инвариант, который держит человек, работает ровно до тех пор, пока код пишется на человеческой скорости. Агент не читал ваш комментарий, не открывал вики и не был на вашем ревью. **Договорённости не масштабируются под скорость генерации. Компиляторы масштабируются.** ### Алгебраические типы данных за один проход Одна оговорка перед теорией, чтобы снять половину возражений. Пример здесь намеренно самый очевидный из возможных. Загрузка, ошибка, успех — самая заезженная иллюстрация алгебраических типов данных в интернете, она есть в каждом туториале. У идеи есть знаменитый лозунг — *make illegal states unrepresentable*, «сделай недопустимые состояния непредставимыми», — который обычно приписывают докладам Ярона Мински про эффективный ML, и ему больше десяти лет. Тут нет никакого открытия. Интересно другое: откуда этот класс взялся в начале статьи и что делать, когда такой код приезжает пачками. Теперь определение. Без теории категорий. **Произведение — это «и».** Обычный класс с полями: строка **и** число **и** булево. Количество возможных значений — произведение количеств. Отсюда и взялись восемь: это не риторика, это арифметика. Каждое добавленное поле **умножает.** **Сумма — это «или».** Значение — либо одно, либо другое, третьего не дано. Самая простая сумма, которую все знают, — `enum`. Sealed-класс — та же сумма, только каждый случай может нести свои данные. Здесь количества **складываются**: загрузка — одно значение, ошибка — сколько есть ошибок, успех — сколько есть списков. Ничего не умножается просто так. Вся идея одним предложением: **вы не можете сконструировать значение, изображающее состояние, которое никогда не было валидным.** #### «А почему не просто enum?» Очевидное возражение: sealed для этого не нужен, добавьте `enum Status` с тремя значениями и не усложняйте. Инстинкт разумный, но не работает. ```dart enum Status { loading, error, success } class ProductsState { final Status status; final List? items; final Exception? error; } ``` Считаем снова: три значения статуса на два состояния списка на два состояния ошибки. **Двенадцать комбинаций.** Было восемь. Осмысленных по-прежнему три. Арифметически стало **хуже** — девять мусорных комбинаций вместо пяти. И исходная проблема не тронута. `status` — `success`, `items` — `null`: компилируется прекрасно. А в UI вы всё так же пишете `items!`, клянясь компилятору, что данные точно есть. Обещание — не гарантия. Это отключённая проверка с приятным синтаксисом. Разница в том, что несёт сумма. `enum` — сумма **без данных**: он говорит, в каком вы состоянии, но данные лежат рядом и привязаны к состоянию только вашей дисциплиной. Sealed-класс — сумма **с данными**: список существует ровно там, где он что-то значит, и больше нигде. Отсюда правило: если состояния не несут данных — берите `enum`, он короче. Как только хотя бы у одного состояния появляется полезная нагрузка, `enum` перестаёт помогать, и **каждый `!` в вашем UI — это чек об оплате.** Идея старая. Языку ML, откуда она родом, пятьдесят лет. В Haskell, OCaml и F# это есть уже десятилетия. В мейнстриме она сейчас есть практически везде: Dart получил это в третьей версии, в 2023 году. Изменилась не идея, а аргумент в её пользу. Раньше АТД продавали через элегантность, а эстетические аргументы проигрывают спринтам. Теперь аргумент такой: **код пишется быстрее, чем читается**, единственный ревьюер, поспевающий за генерацией, — компилятор, и всё, что вы не выразили в типе, вы выразили в виде надежды. ### Переписываем Три шага, минуты две работы. **Шаг один: выписать состояния словами, до всякого кода.** Экран грузится. Экран упал. У экрана есть список. Всё, три. Если на этом шаге у вас получилось семь, вы почти наверняка выписываете комбинации флагов, а не состояния. Состояние — это то, что называется одним словом и показывается пальцем на макете. **Шаг два: каждое состояние становится отдельным классом.** ```dart sealed class ProductsState { const ProductsState(); } final class Loading extends ProductsState { const Loading(); } final class Failed extends ProductsState { const Failed(this.error); final Exception error; } final class Loaded extends ProductsState { const Loaded(this.items); final List items; } ``` Смотрите, что произошло. `Failed` держит ошибку, и она **не nullable** — состояния «упали, но ошибки нет» больше не существует, его физически нельзя сконструировать. `Loaded` держит список, тоже не nullable. `Loading` не держит **ничего**, потому что во время загрузки данных нет, а поле под них — это прямое приглашение к невозможному состоянию. Обратите внимание на то, чего здесь нет. **Ни одного булева поля. Ни одного nullable.** Это и есть весь рефакторинг, остальное — синтаксис. Одна деталь про Dart: `sealed` ограничивает наследование **той же библиотекой**, а библиотека на практике — это один файл, если вы намеренно не разбиваете её через `part`. Это не занудство, это механизм: чтобы проверить полноту, компилятор должен знать полный список случаев, а гарантировать это он может только внутри библиотеки. Отсюда и соглашение: сколько бы случаев ни было, они живут в одном файле. Сначала выглядит странно, потом оказывается удобным — всё пространство состояний экрана видно одним взглядом. **Шаг три: UI.** ```dart Widget build(BuildContext context) { return switch (state) { Loading() => const AppSpinner(), Failed(:final error) => ErrorView(error), Loaded(:final items) => ItemList(items), }; } ``` Ни одного `if`. Ни одной проверки на null. Данные разбираются прямо в паттерне — `:final items` — и приходят уже типизированными. Внутри ветки `Loaded` список **существует**, а не «возможно, существует». Сравните с тем, что было бы на флагах: цепочка проверок, порядок которых важен, а причину порядка никто уже не помнит. #### Как это делать на проекте, где сорок экранов Не переписывайте всё. Правило: **новый экран — сразу sealed; старый — когда вы и так в нём что-то чините.** Экран, который никто не трогал два года, не трогайте. Невозможные состояния там, скорее всего, есть, но они либо уже выстрелили, либо не выстрелят никогда. Для тех, что придётся трогать, приоритет определяется одним: **из скольких мест это состояние читают.** Один `switch` в одном виджете — переписывать почти не окупается, там и так всё видно. Состояние, которое читают из шести мест, — UI, аналитика, логирование, обработчик пуша, — вот где проверка на полноту себя оправдывает, потому что это ровно те пять из шести, про которые забывают. Если экран уже идёт через один стрим состояния, а не через [пирамиду флагов](https://ru.nerdy.pro/blog/ai-agents-struggle-with-flutter), начинать дешевле всего оттуда. ### Тот же тип на четырёх других языках Не потому, что нужен второй язык, а чтобы было видно: это не диалект Dart и не мода Flutter-сообщества. Тот же тип, три состояния, данные привязаны к состоянию. **Kotlin.** ```kotlin sealed interface ProductsState data object Loading : ProductsState data class Failed( val error: AppError, ) : ProductsState data class Loaded( val items: List, ) : ProductsState ``` `when` в роли выражения обязан покрыть все ветки — ровно как наш `switch`. **Rust.** Интереснее, потому что это встроенный `enum`: ```rust enum ProductsState { Loading, Failed(AppError), Loaded(Vec), } ``` В Rust `enum` **и есть** сумма с данными с самого начала, и никакой дополнительной обвязки не требуется. Пропустите случай в `match` — не скомпилируется. **Swift**, то же самое через associated values: ```swift enum ProductsState { case loading case failed(AppError) case loaded([Product]) } ``` **И экзотика — Idris** с синтаксисом семейства ML, от которого произошло всё перечисленное выше: ```idris data ProductsState = Loading | Failed AppError | Loaded (List Product) ``` Посмотрите на вертикальную черту. Она читается как «или». **Тип-сумма буквально записан символом «или»** — вот откуда взялось название; это сумма задолго до того, как её кто-то назвал sealed-классом. Idris умеет ещё кое-что, чего не умеет ни один из предыдущих четырёх: ```idris import Data.Vect data ProductsState : Type where Loading : ProductsState Failed : AppError -> ProductsState Loaded : Vect (S n) Product -> ProductsState ``` `Vect (S n)` в типе означает список, **гарантированно содержащий хотя бы один элемент.** Не «мы проверили», не «мы договорились» — вы просто не можете сконструировать `Loaded` с пустым списком. Запомните этот пример, он вернётся в конце. Одна идея, пять языков, проверка на полноту везде. Если вы не на Dart, всё дальнейшее работает у вас так же. ### Проверка на полноту — ради чего всё и затевалось Через месяц приходит требование: когда список вернулся пустым, показать заглушку с кнопкой. Это не ошибка и не успех с данными. Это четвёртое состояние. ```dart final class Empty extends ProductsState { const Empty(); } ``` Одна строка. Больше ничего не менялось. И проект не собирается: ```text The type 'ProductsState' is not exhaustively matched by the switch cases since it doesn't match 'Empty()'. ``` Компилятор перечисляет **все** места, где новое состояние не обработано. Не одно из них — все. Теперь о том, почему этому место в статье про код, написанный ИИ. «Добавь пустое состояние на экран списка» — задача ровно того размера, который отдают агенту не глядя. Агент добавит класс, обновит тот `switch`, который был у него в контексте, и **не обновит два других** — в аналитике и за кнопкой обновления. Их не было в промпте, значит, для него их не существует. На флагах это уезжает в прод. Диффом не поймать: в диффе только то, что агент изменил. Тесты зелёные, потому что у состояния, которого месяц назад не было, тестов нет по определению. Вы узнаете об этом от пользователя через шесть недель, в формулировке «иногда просто пустой экран». На sealed-классах это никуда не уезжает. Оно не собирается. И вот что отсюда стоит унести. **Проверка на полноту — это не защита от ИИ. Это обратная связь для него.** Агент плох в этом потому, что у него нет доступа к тому списку «и вот тут ещё не забудь», который держите в голове вы. Компилятор выдаёт этот список в машиночитаемом виде, с файлами и номерами строк. Агент упирается в красное и чинит все три — а это он умеет хорошо, потому что ему сказали, где именно. Вы не мешаете ему работать, вы даёте ему то, чего не хватало. Тот же механизм помогает человеку раз в неделю, а агенту — раз в двадцать минут. #### Одна строка, которая всё это выключает ```dart return switch (state) { Loading() => const AppSpinner(), _ => ItemList(state.items), }; ``` Одно подчёркивание — и проверки на полноту нет. Молча, без предупреждения. Ветка «всё остальное» по определению обрабатывает всё, значит, компилятору нечего проверять. Добавьте пятое состояние, шестое, десятое — соберётся и молча провалится сюда. Если вы уносите из статьи ровно одно правило ревью, унесите это: > **В `switch` по sealed-типу не должно быть ветки по умолчанию.** Это та единственная строка, которую действительно нужно проверять глазами, — но её ловит линтер, так что и глазами не нужно. ### Где алгебраические типы не помогают Четыре места, где они не работают или работают против вас. Без этого раздела всё остальное — проповедь. **Сумма не лечит произведение.** Вы разбили экран на три состояния — хорошо. Но `Loaded` — по-прежнему обычный класс с полями, и если их двенадцать, а половина nullable, вы просто перенесли болото этажом ниже. АТД — про то, **какие состояния существуют**, а не про то, что лежит внутри состояния. **Состояния, которые пересекаются, — самая частая ошибка при переходе.** Прилетает требование pull-to-refresh: список уже на экране, и вы тянете свежий. Это `Loading` или `Loaded`? Наивный ответ — случай `Refreshing` с `items` внутри. Через неделю у вас `Refreshing`, `RefreshingAfterError` и `FailedButHasCache` — тот же комбинаторный взрыв, только теперь в классах, что заметно хуже флагов, потому что многословнее. Правильный ответ людей расстраивает: ```dart final class Loaded extends ProductsState { const Loaded(this.items, {this.isRefreshing = false}); final List items; final bool isRefreshing; } ``` Да, булево поле вернулось, и это **нормально.** Теперь оно живёт внутри состояния, где что-то значит: «у нас есть список, и мы его обновляем» — реальная ситуация, которую нужно уметь выразить. А «обновляем без списка» больше не существует, потому что его некуда положить. > **Суммы — для взаимоисключающего, произведения — для сосуществующего** > > Это правило решает, поможет рефакторинг или навредит. Две вещи, которые никогда не истинны одновременно, просят сумму — отдельные случаи sealed-типа. Две вещи, которые регулярно истинны одновременно, просят произведение — поля рядом внутри одного случая. Перепутать их — главный способ оказаться в худшем положении, чем в начале: взаимоисключающие состояния, смоделированные параллельными булевыми полями, дают ту самую задачу про восемь комбинаций, а сосуществующие факты, смоделированные отдельными случаями, дают класс на каждую комбинацию. **Типы держат не всякий инвариант, и это важнее остального.** Sealed-класс прекрасно выражает **структурный** инвариант: какие состояния существуют и какие данные с каким состоянием ходят. И ничего не говорит про инварианты **на значениях**: этот список отсортирован, дата начала раньше даты конца, сумма позиций сходится с итогом, эта строка — валидный email. Всё это по-прежнему держит человек, и никакой `sealed` тут не помогает. Тот самый пример на Idris — и есть контрпример. `Vect (S n)`, непустота в типе, — это инвариант на значении, затащенный в систему типов. Называется это зависимыми типами, и так же можно было бы выразить «отсортирован» и «начало раньше конца». Цена — язык, на котором вы не пишете и, будем честны, не будете. На практике используют другой приём: приватный конструктор с валидацией, чтобы тип нельзя было создать в невалидной форме. В ту же корзину — **границы системы.** JSON из сети не типизирован. АТД начинаются *после* парсинга, и sealed-класс не спасёт вас, когда бэкенд пришлёт `"succes"` с одной «s», — спасёт явная десериализация с ключами, прописанными руками. Фраза, которую стоит запомнить: **АТД убирают невозможные комбинации состояний, но не невозможные значения.** **И оверинжиниринг.** Sealed-класс с двумя случаями без данных — это `enum`, и `enum` читается быстрее. Sealed-класс с одним случаем — это класс. Честное булево поле остаётся честным булевым полем: `isSelected` у чекбокса — ровно два состояния, оба валидные, и АТД тут ничего не улучшают. Проверка простая: посчитайте комбинации и вычеркните бессмысленные. **Вычёркивать нечего — не трогайте.** Наконец, честная цена: **это многословно.** Четыре состояния на Dart — примерно тридцать строк против четырёх полей. Аргумент «дольше писать» не переживает 2026 год: это ровно та работа, которую агент делает за секунды и без ошибок. А вот «дольше читать» никуда не делось, и это та цена, которую вы платите. ### Найдите свои за полчаса #### Ревью класса состояния Десять проверок в трёх группах. Всё неотмеченное — решение, которое ещё никто не принял. ##### Сам тип - Взаимоисключающие ситуации — отдельные случаи sealed-типа, а не параллельные булевы поля - Каждое поле внутри случая non-nullable, либо null означает что-то конкретное и это записано - Данные лежат в том состоянии, которому принадлежат, а не рядом со статусным enum - Сосуществующие факты — поля внутри одного случая, а не случай на каждую комбинацию ##### Каждый switch по нему - Нет ветки по умолчанию и нет паттерна с подчёркиванием по sealed-типу - Нет принудительного снятия null через восклицательный знак на поле, которое состояние и так гарантирует - Покрыты все читатели состояния, включая аналитику, логирование и фоновые обработчики ##### Чего типы за вас не сделают - Инварианты на значениях — отсортирован, непуст, в диапазоне, корректной формы — проверяются в конструкторе, а не предполагаются - Парсинг на границе системы валидирует явно, а не доверяет форме данных - Ассерты считаются инструментом отладки, потому что в релизной сборке они не работают ### Коротко о главном Невозможное состояние — то, которое ваш тип разрешает, а предметная область запрещает; это то же самое, что сломанный инвариант. И дорого оно не потому, что что-то ломается в рантайме, а потому, что каждую договорённость, которой нет в типе, кто-то выводит заново при каждом чтении. Инвариант есть всегда. Вопрос только в том, держит ли его человек — комментарием, страницей в вики и ассертом, который не работает в релизе, — или держать нечего, потому что нарушение не компилируется. Три поля, которые ходят вместе, восемь комбинаций, три осмысленных. Sealed-класс с non-nullable полями оставляет ровно эти три и делает остальные пять несобираемыми. Проверка на полноту — вот в чём отдача: она превращает «кто-то забыл обновить обработку» в «проект не собирается», и работает одинаково независимо от того, кто забыл. А ветка по умолчанию выключает всё это молча. И более крупный сдвиг. Раньше типы были способом объяснить свой замысел коллеге, который откроет файл через полгода. Теперь это единственный способ объяснить его чему-то, что пишет код быстрее, чем вы читаете. Ремесло никуда не делось — оно поднялось на уровень выше. Раньше вы писали реализацию. Теперь вы пишете границы, внутри которых реализация обязана остаться, а заполнить их может кто угодно. В том числе машина. ### Частые вопросы #### Что такое невозможное состояние в программировании? Состояние, которое ваш тип разрешает, а предметная область запрещает. У класса состояния экрана с булевым флагом загрузки, nullable-списком и nullable-ошибкой восемь комбинаций, но только три из них описывают ситуацию, в которой продукт реально бывает. Остальные пять компилируются, проходят ревью и уезжают в прод. Более точное имя тому же самому — сломанный инвариант: инвариант — это правило, которое должно выполняться всегда, а невозможное состояние — это то, что получается, когда тип его не обеспечивает. #### Что такое алгебраические типы данных? Способ описать, сколько значений может принимать тип. Тип-произведение — это «и»: обычный класс с полями, количество значений которого равно произведению количеств по полям, поэтому каждое добавленное поле умножает. Тип-сумма — это «или»: значение относится к одному случаю либо к другому, третьего не дано, и количества складываются, а не умножаются. Enum — это сумма без данных, sealed-класс — сумма, где каждый случай несёт свои данные. Смысл сумм в том, что вы не можете сконструировать значение, изображающее состояние, которое никогда не было валидным. #### Почему не обойтись enum со статусом? Потому что арифметика становится хуже. Enum с тремя значениями рядом с nullable-списком и nullable-ошибкой даёт двенадцать комбинаций вместо восьми, и осмысленных среди них по-прежнему три. Исходная проблема не тронута: статус может быть success, а списка при этом нет вовсе, это компилируется, и в UI поле приходится подпирать восклицательным знаком. Enum говорит, в каком вы состоянии, но оставляет данные рядом, привязанными к состоянию только договорённостью. Sealed-класс привязывает данные к состоянию, поэтому список существует ровно там, где он что-то значит. #### Обязательно ли держать sealed-классы в Dart в одном файле? В одной библиотеке, а это на практике один файл, если вы намеренно не разбиваете её через part. Это механизм, а не стилевое правило: чтобы проверить полноту разбора, компилятору нужен полный список подтипов, а гарантировать полноту он может только внутри библиотеки. Отсюда соглашение держать все случаи состояния в одном файле, и оно оказывается удобным: всё пространство состояний экрана видно одним взглядом. #### Почему ветка по умолчанию ломает проверку на полноту? Потому что ветка всё остальное по определению обрабатывает любой случай, и компилятору нечего проверять. Как только в switch по sealed-типу появляется ветка по умолчанию или подчёркивание, добавление пятого или десятого состояния компилируется чисто и молча проваливается в эту ветку вместо того, чтобы уронить сборку. Это самое полезное правило ревью по теме: в switch по sealed-типу не должно быть ветки по умолчанию, и это ловится линтером. #### Помогают ли sealed-классы с кодом, который пишет ИИ? Да, но не как барьер. Проверка на полноту — это обратная связь, а не защита. Агент, которого попросили добавить новое состояние, обновит тот switch, что был у него в контексте, и пропустит те, которых не было в промпте. На флагах это уезжает молча, потому что дифф показывает только изменённое, а у нового состояния по определению нет тестов. На sealed-типе сборка падает, и компилятор называет каждое необработанное место с файлом и номером строки — ровно тот вход, с которым агент работает хорошо. #### Где алгебраические типы не помогают? В четырёх местах. Они не чинят случай, который сам по себе — раздутое произведение с дюжиной nullable-полей. Они вредят там, где состояния реально пересекаются, например при обновлении списка, который уже на экране: там нужно булево поле внутри загруженного состояния, а не новый случай. Они ничего не говорят про инварианты на значениях вроде отсортированности, непустоты или корректного email, для которых нужен валидирующий конструктор, и начинаются только после парсинга, так что на границе системы, где бэкенд присылает статус с опечаткой, тоже бесполезны. И они превращаются в оверинжиниринг там, где хватило бы enum или честного булева поля. Если нужен более широкий список того, что делают с Flutter-кодом агенты без присмотра, — это пункт номер три в нашем [разборе из семи пунктов](https://ru.nerdy.pro/blog/ai-agents-struggle-with-flutter), а процесс, которым мы это предотвращаем, описан в статье [как мы строим с ИИ](https://ru.nerdy.pro/blog/building-with-ai). Эта статья написана в пару к нашему видеовыпуску на ту же тему; предыдущий был [ваш SSL pinning, скорее всего, не работает](https://ru.nerdy.pro/blog/ssl-certificate-pinning-flutter). Прогоните упражнение и расскажите, сколько строк вычеркнули и на каком экране. Наша ставка: большинство придёт ровно к той же тройке состояний, что и мы. --- *Илья Никсан — основатель и ведущий разработчик [Nerdy Production](https://ru.nerdy.pro/), Flutter-first агентства, которое создаёт и поддерживает приложения в финтехе, медицине и ритейле. Мы также делаем [аудит кода, написанного ИИ](https://ru.nerdy.pro/services/ai-code-audit) для команд, чья кодовая база выросла быстрее, чем её успевали ревьюить.* ## Ваш SSL pinning, скорее всего, не работает https://ru.nerdy.pro/blog/ssl-certificate-pinning-flutter Собственная документация Google по Android говорит, что certificate pinning **«не рекомендуется для Android-приложений»**. Apple говорит: **«в большинстве случаев pinning не нужен, и его следует избегать»**. А вот инженер Chromium отвечает на вопрос о том, как правильно настроить pinning. Начинает он так: **«Хотя pinning в целом никогда не следует поощрять, поскольку он активно вредит безопасности и стабильности интернета в целом, если вы пиннитесь к приватному CA, Network Security Config в Android будет предпочтительным подходом»**. Человек буквально объясняет, как это сделать, и всё равно не может удержаться. При этом pinning есть примерно в половине мобильных приложений, которые мы смотрим, включая те, что попадают к нам на аудит. Хорошая новость: если вы на Flutter, он, скорее всего, не работает. Плохая новость — та же самая фраза. ### Коротко - **Pinning — узкий механизм против угрозы, которая сильно сжалась**, и при этом радиус поражения у него — вся ваша пользовательская база. Certificate Transparency, CAA-записи и изменение модели доверия в Android 7 забрали у него большую часть исходной работы. - **Он действительно нужен финтеху, медицине и госуслугам** — то есть приложениям, которые обязаны проходить проверку по MAS-L2, профилю OWASP для софта с чувствительными данными. Всем остальным Google, Apple, Cloudflare и половина OWASP говорят «не надо». - **Сроки жизни сертификатов схлопываются**: 200 дней с марта 2026-го, 100 — в 2027-м, 47 — в 2029-м. Если вы пиннитесь на лист и не можете ротировать пины без релиза, это восемь принудительных релизов в год. - **Пиннить нужно ключ (SPKI), а не сертификат**, и всегда с резервным пином на офлайновый ключ, который принадлежит вам. - **Никогда не выкатывайте сразу в hard-fail** — и телеметрию отказов придётся написать самим, потому что ни на одной платформе её нет. - **На Flutter `` в `network_security_config.xml` ничего не делает с трафиком Dart.** У Dart свой TLS-стек. Баг открыт с января 2022 года. - **Тест на полчаса** в конце статьи показывает, какой из этих вариантов — про ваше приложение. Наивная версия этого теста вам врёт. ### От чего pinning защищает на самом деле Коротко о базе, чтобы говорить на одном языке. Обычно приложение доверяет любому сертификату, подписанному любым удостоверяющим центром из системного хранилища, а корневых сертификатов там около полутора сотен. Pinning сужает этот список до одного: **вот этот ключ и больше никакой.** Сначала про название. Все говорят «SSL pinning», мы в том числе — в заголовке этой статьи. **SSL мёртв больше десяти лет**, везде TLS. Но ищут люди именно это, поэтому название остаётся. Просто держите в голове: если кто-то в 2026 году говорит «SSL» и имеет это в виду буквально, это кое-что говорит о свежести его источников. Теперь честная модель угроз. От двух вещей pinning защищает по-настоящему: **Скомпрометированный или недобросовестный удостоверяющий центр.** Это его настоящая работа. Учебный пример — DigiNotar, 2011 год: атакующие взломали нидерландский CA, выпустили поддельные сертификаты на домены Google и перехватывали иранских пользователей. Поймал это именно pinning — в Chrome был предзагруженный пин для google.com. **Корпоративный перехват.** DLP-прокси; антивирусы, которые инспектируют TLS; MDM, который ставит корневой сертификат на уровне системы. Pinning ломает их все. В этом и смысл — но это же означает, что вы сломаете легальный прокси корпоративного заказчика вместе с прокси атакующего. А теперь то, чего нет в статьях 2015 года, потому что тогда этого просто не существовало. #### Угроза «плохой CA» сильно сжалась **Certificate Transparency** публикует каждый публично доверенный сертификат в открытые логи, в которые можно только дописывать. Механизм появился в 2013 году, но обязательным на практике стал в 2018-м: Chrome начал требовать его в версии 68, а Apple проверяет CT **на уровне ОС** — то есть внутри приложений, а не только в Safari. Это не теория. В сентябре 2015 года CT впервые поймал ошибочно выпущенный сертификат в реальном мире — неавторизованный сертификат на google.com, который Symantec выпустил при внутреннем тестировании. Без злого умысла, срок жизни — один день. Этот же случай стал первой трещиной в истории, которая закончилась в 2018 году полной потерей доверия к Symantec во всех браузерах. Свежий случай всплыл в сентябре 2025 года: хорватский CA Fina выпустил двенадцать неавторизованных сертификатов на 1.1.1.1 Cloudflare — с февраля 2024-го по август 2025-го. Полтора года этого никто не замечал; показали их в итоге логи CT. И обратите внимание: Fina вообще нет в мобильных хранилищах доверия, так что на телефоне эти сертификаты и так бы не прошли проверку. Лог поймал их независимо от того, кто доверяет этому CA. Вот в этом и ценность. К этому добавились CAA-записи, то есть DNS-записи, которые перечисляют, какие CA вправе выпускать сертификаты для вашего домена, и обязательная валидация домена из нескольких точек сети. #### Часть работы платформа уже сделала за вас Начиная с Android 7 — точнее, с `targetSdk` 24 — приложения **по умолчанию не доверяют сертификатам, установленным пользователем**. Так по умолчанию с 2016 года. То есть сценарий «жертва поставила себе вредоносный корневой сертификат» — уже не то, от чего вас спасает pinning. Операционная система сделала это раньше. | `targetSdk` | Системные CA | Пользовательские CA | Открытый HTTP | | --- | --- | --- | --- | | ≤ 23 | доверие | **доверие** | разрешён | | 24–27 | доверие | нет доверия | разрешён | | ≥ 28 | доверие | нет доверия | **запрещён по умолчанию** | Таблица привязана к `targetSdk`, а не к версии Android на устройстве. Поэтому старое приложение на новом телефоне по-прежнему доверяет всему, что пользователь себе поставил. #### От чего pinning не защищает вообще Формулировка OWASP прямая: если атакующий контролирует устройство, он просто отключает вашу логику pinning. На рутованном Android или на iPhone с джейлбрейком это одна команда в `objection` — а для Flutter есть отдельный инструментарий, до которого мы дойдём, потому что он интересный. Итого: **pinning защищает реальных пользователей от перехвата во враждебной сети. Он не защищает ваш API от владельца устройства.** Если вы используете pinning, чтобы спрятать ключи или помешать реверс-инжинирингу, вы уже проиграли. Для этого существует серверная аттестация — Play Integrity, App Attest, — а не клиентские фокусы. > **OWASP сам себе противоречит, и это важно, когда вам приносят отчёт** > > MASVS действительно требует pinning — но только для приложения, которое обязано проходить проверку по MAS-L2, профилю OWASP «защита в глубину» для софта с чувствительными данными. Это не базовое требование. А руководство по тестированию говорит прямым текстом: если приложение не реализует pinning, это не следует сообщать как уязвимость. При этом OWASP Pinning Cheat Sheet пишет: «Первый вопрос должен быть — а нужно ли мне пиннить? Ответ на него — вероятно, никогда». Эта формулировка появилась в марте 2023 года, раньше её там не было. Так что когда в отчёте пентеста «отсутствие SSL pinning» стоит как находка, это обычно автоматический чек-лист, а не вывод о вашей модели угроз. ### Нужен ли вам certificate pinning? Короткий чек-лист. Если хотя бы один пункт — про вас, **не пиннитесь.** - Вы не контролируете оба конца: и сервер, и приложение - Вы не можете обновить набор пинов безопасно и быстро - Обновление пинов требует релиза приложения - Вы не можете узнать пару ключей до того, как она уйдёт в прод - Это не нативное мобильное приложение Кому он правда нужен: финансы, медицина, госуслуги — всё, где данные чувствительные, а модель угроз предполагает враждебную сеть. Это и есть случай MAS-L2. Всем остальным — скорее нет. #### Почему это стало срочным именно в 2026-м В апреле 2025 года CA/Browser Forum проголосовал за поэтапное сокращение срока жизни TLS-сертификатов. Бюллетень внесла Apple. График такой: | С какого момента | Максимальный срок | Ротаций в год | | --- | --- | --- | | до марта 2026 | 398 дней | ~1 | | **15 марта 2026** | **200 дней** | ~2 | | 15 марта 2027 | 100 дней | ~4 | | 15 марта 2029 | 47 дней | ~8 | Ступень в 200 дней действует прямо сейчас. Сорок семь дней — это примерно **восемь ротаций сертификата в год**. Если вы пиннитесь на лист и не можете обновлять пины без релиза, это восемь принудительных обновлений приложения в год: с ревью в сторе и с пользователями, которые не обновляются. И экосистема борется с pinning намеренно. Ещё в 2024 году Let's Encrypt начал **выбирать выпускающий промежуточный сертификат случайно** — специально, чтобы сломать привычку пиннить промежуточные. В ноябре 2025 года эта же политика перешла в новую иерархию из шести промежуточных, и в анонсе написано прямо: **«как и раньше, при каждом выпуске промежуточный сертификат будет выбираться случайно, чтобы не поощрять pinning промежуточных ключей»**. Крупнейший публичный CA в интернете два года целенаправленно ломает такой pinning. Это не злой умысел, а выстраданный опыт. Мир браузеров прошёл через это первым. **HPKP** — pinning через HTTP-заголовок, спецификация 2015 года, написанная инженерами Google, — провалился полностью. Chrome объявил его устаревшим в версии 67 и убрал в версии 72, в январе 2019-го. Google назвал причины: очень низкое внедрение, риск отказа в обслуживании и враждебный pinning. Знаменитая жертва — Smashing Magazine, октябрь 2016 года: четыре дня недоступности для большей части читателей. Они перешли на сертификат с новым ключом, отдали заголовок только с новым пином, и все, у кого в кеше лежал старый набор, просто перестали попадать на сайт. Откатиться было нельзя: срок старого сертификата уже истёк. Из мобильных случаев — Barclays, ноябрь 2016 года. По сообщениям, приложение пиннило устаревший промежуточный сертификат, цепочка изменилась, и платежи встали — накануне «чёрной пятницы». Чтобы починить это в срок, Symantec выпустил сертификат под старым промежуточным и вынужден был нарушить требование CA/Browser Forum к серийным номерам. Мораль в обоих случаях одна: **pinning ломается не тогда, когда вас атакуют. Он ломается в обычный вторник, когда кто-то продлил сертификат.** ### Пять правил, если вы всё-таки пиннитесь #### Правило 1. Пиннить ключ, а не сертификат Технически это SPKI — SubjectPublicKeyInfo, структура внутри сертификата, в которой лежит открытый ключ. Пиннится его SHA-256-хеш в base64. Почему это ключевой момент: **сертификат меняется при каждом продлении, ключ — нет.** Пиннится ключ — и продление с тем же ключом вообще не касается вашего пина. Пиннится отпечаток всего сертификата — и он ломается каждый раз: дважды в год при 200 днях и восемь раз при 47. Однострочник для рабочего хоста: ```bash openssl s_client -connect api.example.com:443 -servername api.example.com /dev/null \ | openssl x509 -pubkey -noout \ | openssl pkey -pubin -outform der \ | openssl dgst -sha256 -binary \ | openssl enc -base64 ``` И тот же хеш из приватного ключа, который вы уже сгенерировали, но ещё не сертифицировали, — так считается резервный пин: ```bash openssl pkey -in backup.key -pubout -outform der \ | openssl dgst -sha256 -binary \ | openssl enc -base64 ``` Хорошая новость: обе платформы нативно поддерживают **только** SPKI, и Android, и iOS. Так что если вы пиннитесь на весь сертификат, вы либо написали это руками, либо выбрали библиотеку, которая делает это за вас, — и почти наверняка без всяких оснований. #### Правило 2. Резервные пины обязательны Как минимум один ключ, **полностью находящийся под вашим контролем.** Это формулировка Google, и «полностью под вашим контролем» — несущая часть. Не «второй сертификат от того же CA». Пара ключей, которую вы сгенерировали заранее и держите офлайн; сертификат на неё можно получить в любой момент. Её пин едет в приложение рядом с рабочим и просто лежит там до нужного часа. Именно отсутствие резервного пина превратило один плохой день Smashing Magazine в четыре дня простоя. Старый ключ у них на самом деле был — его сохранил хостер. В заголовке они отдали только отпечаток нового. ```xml api.example.com YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg= sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis= ``` Эквивалент для iOS — декларативный и проверяемый самой ОС: ```xml NSAppTransportSecurity NSPinnedDomains api.example.com NSIncludesSubdomains NSPinnedLeafIdentities SPKI-SHA256-BASE64YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg= SPKI-SHA256-BASE64sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis= ``` В этом примере закреплены ключи листа. В документации Apple используется `NSPinnedCAIdentities` — та же структура на уровень выше по цепочке, и как раз об этом правило 3. #### Правило 3. Выберите уровень в цепочке — и знайте, чем за него платите Консенсуса здесь нет, поэтому вот обе позиции. **Apple рекомендует пиннить CA, а не сервер** — тогда серверные сертификаты можно ротировать без релиза приложения. **OWASP рекомендует обратное**: пиннить лист, но всегда с резервом. | Уровень | Цена ротации | Чему вы доверяете | Вердикт в 2026-м | | --- | --- | --- | --- | | Лист | Бесплатно при продлении с тем же ключом, иначе релиз | Ровно одному ключу | Рабочий вариант **только** с резервными пинами | | Промежуточный | Непредсказуемая | Всему, что выпустит этот промежуточный | **Снят с рассмотрения** на публичных CA | | Публичный корень | Редкая | Всему, что этот CA когда-либо выпустит | Настолько широко, что почти бессмысленно | | **Свой приватный корень** | Как решите сами | Своей собственной PKI | Единственный однозначно хороший случай | Pinning промежуточного для публично доверенных сертификатов, по сути, снят с рассмотрения с 2024 года: у крупнейшего публичного CA теперь шесть промежуточных, и предсказать, какой достанется вашему сертификату, нельзя. Если вы на публичном CA, остаётся лист с резервными пинами или корень. Конфигурация, в которой pinning по-прежнему однозначно хорошая идея, — **свой приватный CA**, который вы контролируете от начала до конца. Пиннить свой корень разумно, предсказуемо, и он не сломается потому, что кто-то другой передумал. Собственно, ровно об этом и говорил инженер Chromium в цитате из начала статьи. #### Правило 4. Никогда не выкатывайте сразу в hard-fail Порядок такой: сначала **режим только отчётов** — пин проверяется, соединение не блокируется, промахи уходят в телеметрию. Неделю смотрим. Потом hard-fail для одного процента пользователей. Потом поэтапная раскатка на всех. И вот неприятная часть: **встроенной отчётности об отказах нет ни на одной платформе.** Ни в Network Security Config на Android, ни в App Transport Security на iOS. Единственный механизм, который это когда-либо умел, — `report-uri` из HPKP, и он мёртв. Так что телеметрию вы пишете сами: ловите исключение, собираете событие — домен, какой ключ получили, какие ожидали, версия приложения — и отправляете **по каналу, который не пиннится**, иначе отчёт о вашей же аварии наружу не уйдёт. И ещё одно: научитесь отличать аварию от атаки. **Если падают все сразу — вы сломали свою собственную ротацию.** Разрозненные отказы — это корпоративный прокси, антивирус или настоящая попытка перехвата. #### Правило 5. Сделайте выключатель до того, как он понадобится На Android он встроенный — атрибут `expiration` у набора пинов. После этой даты pinning просто прекращается: пины не проверяются, трафик идёт как будто ничего и не было. Выключатель по таймеру. Но у него есть цена, и Google сам об этом пишет: **«установка срока истечения для пинов может позволить атакующим обойти ваши закреплённые сертификаты»**. То есть это одновременно и ваша страховка от «окирпичивания» приложения, **и** способ обойти ваш pinning, просто подождав. А на другой странице той же документации Google рекомендует **«достаточно короткий срок истечения»**. Оба утверждения официальные и оба актуальные. И вот наша любимая деталь во всей этой истории. **В каноническом примере документации Google до сих пор стоит `expiration="2018-01-01"`.** Если вы его скопировали — а его копируют все, — ваш pinning полностью выключен уже восемь лет. Страницу обновляли в июне 2026-го. Дата осталась. На iOS аналогичного механизма нет вообще. Выключить pinning там — значит выпустить релиз. Если вы делаете выключатель через remote config или фиче-флаги, помните про проблему курицы и яйца: канал доставки нельзя защищать теми же пинами, иначе вы никогда не восстановитесь. Правильный ответ — **подписывать набор пинов ключом, публичная половина которого зашита в приложение.** Тогда неважно, по какому каналу он пришёл. И добавьте монотонный счётчик версий, чтобы никто не подсунул повторно старый, но корректно подписанный набор. ### Flutter: где certificate pinning рассыпается Всё сказанное выше касается всех. Дальше — то, ради чего статья и написана: мы делаем приложения на Flutter, и всё это мы проверяли руками. **Главное, и это противоречит интуиции большинства: Dart не использует сетевой стек платформы.** `HttpClient` из `dart:io` не ходит ни через `OkHttp` и `HttpURLConnection` на Android, ни через `NSURLSession` на iOS. Он открывает сокет и сам проводит TLS-хендшейк — через копию BoringSSL, встроенную в движок Flutter. Практическое следствие: **ваш `` в `network_security_config.xml` ничего не делает с трафиком Dart.** Молча. Ни ошибки, ни предупреждения — запрос просто уходит. Это не наше открытие. Это [issue #96722 во Flutter](https://github.com/flutter/flutter/issues/96722), заведённый в январе 2022 года и до сих пор открытый. Участник команды Flutter воспроизвёл проблему и зафиксировал вывод: на iOS запрос корректно отменяется, на Android проходит, а в контрольном нативном приложении для Android с тем же конфигом падает, как и должен. На iOS поведение другое: по сообщениям, там работает, потому что Dart на платформах Apple передаёт решение о доверии системному `SecTrust`. Нигде это не задокументировано. И, честно говоря, это худший из исходов. **Pinning, который работает на одной платформе и молча не делает ничего на другой, опаснее pinning, который не работает нигде** — потому что вы его проверили. На айфоне. Дальше три ловушки. Мы наступили на все три в продакшене. **Dart не даёт доступа к SPKI.** Класс `X509Certificate` отдаёт `der`, `pem`, `sha1`, subject, issuer и даты действия. Открытого ключа среди них нет. Поэтому практически каждый туториал пиннит SHA-256 всего сертификата — что, по правилу 1, ломается при каждом продлении. Сделать правильно — значит разбирать ASN.1 руками. **В README самого dio показаны два способа сделать это неправильно.** Вот первый, в сокращении: ```dart // Пример из README dio. Три проблемы в девяти строках. dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { final client = HttpClient(context: SecurityContext(withTrustedRoots: false)); client.badCertificateCallback = (cert, host, port) => true; return client; }, validateCertificate: (cert, host, port) => cert != null && fingerprint == sha256.convert(cert.der).toString(), ); ``` `withTrustedRoots: false` вместе с безусловным `badCertificateCallback` отключает построение цепочки, проверку срока действия и проверку имени хоста. Сравнивается хеш от `cert.der` — то есть от всего сертификата, а это, по правилу 1, ломается при каждом продлении. И `validateCertificate` вызывается позже, чем кажется, — об этом ниже. Второй вариант из README ещё хуже: там пин проверяется прямо внутри `badCertificateCallback`, сравнением `cert.pem` с сохранённым PEM. А этот колбэк по определению срабатывает **только если валидация уже провалилась.** Если атакующий предъявит сертификат, корректно подписанный любым доверенным CA, колбэк не вызовется вообще и сравнивать будет нечего. Как механизм pinning он не работает в принципе. **Правильный хук вызывается слишком поздно.** У dio он есть — `validateCertificate`, который срабатывает при успешной валидации. Но выполняется он **после того, как запрос уже ушёл, а ответ уже пришёл.** В сети оказались не только тело запроса и заголовок `Authorization`: вы уже приняли статус и заголовки ответа. Единственное, что спасает эта проверка, — тело ответа. Запрос уже утёк. > **И теперь про пакеты** > > Самый популярный пакет для pinning на pub.dev — около ста шестидесяти лайков и почти пятьдесят тысяч загрузок в месяц на момент проверки в августе 2026-го — через платформенный канал делает **отдельный нативный запрос** к вашему хосту, проверяет отпечаток на нём и, если совпало, отправляет настоящий запрос по **другому соединению**, где pinning не проверяется вообще. Одно соединение проверено, данные идут по другому. Плюс каждый запрос теперь — два запроса. Мы его не называем, потому что дело не в одном мейнтейнере: прочитайте исходники того пакета для pinning, который используете вы, и выясните, какое соединение он на самом деле проверяет. **Что делать на самом деле.** На платформах Apple есть чистый ответ: перевести HTTP-слой на `cupertino_http`. Это настоящий `NSURLSession`, а значит, pinning становится декларативным — `NSPinnedDomains` в `Info.plist`, ровно как в правиле 2. Проверяется операционной системой, работает по SPKI, сторонний код не нужен. На Android эквивалента нет. У Cronet есть собственный API для pinning, но пакет `cronet_http` **его не экспортирует**. JNI-биндинг лежит прямо в пакете. Зато пакет экспортирует `enablePublicKeyPinningBypassForLocalTrustAnchors` — переключатель, который pinning обходит. Не сам pinning. И есть один открытый вопрос, на который в интернете никто не отвечает внятно, — мы его так и обозначаем как открытый: применяется ли Network Security Config из Android к трафику, идущему через Cronet. Источники противоречат друг другу, официальной документации нет ни в одну сторону. Проверьте сами, прежде чем опираться на любой из ответов. Два предупреждения напоследок, специфичных для Flutter. **Сторонние SDK** — аналитика, крашлитика, платежи — делают сетевые вызовы через свои нативные клиенты, и ваш pinning их не касается. Из-за этого же разделения [отложенный диплинкинг на Flutter](https://ru.nerdy.pro/blog/deferred-deep-linking-app-clips-install-referrer) приходится делать отдельно под каждую платформу, а не одной реализацией. И **если вы перейдёте на `native_dio_adapter`, чтобы получить нативный сетевой стек, pinning на dio, скопированный из статьи в блоге, молча выключится**, потому что `SecurityContext` и оба колбэка перестанут вызываться. ### Как проверить свой pinning за полчаса Сделайте это правильно, потому что наивная версия теста вам врёт. Наивная версия — поднять прокси, поставить его сертификат на телефон, посмотреть, сломается ли приложение. Сломается. И это не значит ничего: как разобрано выше, начиная с Android 7 приложения и так не доверяют сертификатам, установленным пользователем. Вы сделаете вывод, что вас защищает pinning, тогда как всё это время вас защищала операционная система. #### Проверка pinning за 30 минут Прогоняется по одному разу на каждую платформу. Всё, что осталось неотмеченным, — находка. ##### Подготовка - Release-сборка, а не debug — блок debug-overrides обесценивает весь тест - Сертификат прокси установлен в СИСТЕМНОЕ хранилище доверия, а не в пользовательское - Flutter: подтверждено, что трафик доходит до прокси через VPN-перехват или iptables, потому что Dart игнорирует системные настройки прокси - Пройден реальный сценарий с авторизацией, а не только первый экран ##### На что смотреть - Трафик вашего API не читается — если читается, работающего pinning нет - Трафик сторонних SDK и ваш ведут себя одинаково, либо вы знаете, почему нет - То, что вы видите, — отказ pinning, а не отказ хранилища доверия - Одинаковый результат на Android и на iOS ##### Затем проверьте сам конфиг - Пины — это хеши SPKI, а не отпечатки сертификатов - Есть хотя бы один резервный пин на офлайновый ключ, принадлежащий вам - Android: дата expiration у набора пинов в будущем и выбрана осознанно - Есть выключатель, который не требует релиза в сторе - Телеметрия отказов уходит по каналу, который сам не пиннится ### Если совсем коротко Pinning — узкий механизм против угрозы, которая стала гораздо менее вероятной, и радиус поражения у него — вся ваша пользовательская база. Он действительно нужен финансам, медицине и госуслугам. Всем остальным Google, Apple, Cloudflare и половина OWASP говорят «не надо», и у них есть причины — те же самые, что убили HPKP и что сейчас режут срок жизни сертификатов до 47 дней. Если делаете: ключ, а не сертификат. Резервные пины на офлайновый ключ. Наблюдение до hard-fail. Свой выключатель. И ясное понимание, чем вы платите за свой уровень в цепочке. А если вы на Flutter — сначала выясните, работает ли он вообще. Три самых вероятных ответа: pinning выключен датой 2018 года, скопированной из документации Google; pinning объявлен в конфиге, который Dart никогда не читает; или pinning проверяет соединение, по которому ваши данные не идут. Версия из статьи в блоге говорит: «включите pinning, защитите пользователей». Инженерная реальность — выяснить, к чему вы на самом деле привязаны, проверить, что это срабатывает на обеих платформах, и положить запасной ключ в сейф. Про это никто не снимает виральных видео. ### Источники Всё выше проверяемо, поэтому вот откуда что взято. - Google, «Security with HTTPS and SSL» — pinning «не рекомендуется для Android-приложений»; резервные пины и «как минимум один ключ, полностью находящийся под вашим контролем»: [developer.android.com/privacy-and-security/security-ssl](https://developer.android.com/privacy-and-security/security-ssl) - Google, «Network security configuration» — атрибут `expiration`, предупреждение про обход, «поддерживается только `SHA-256`» и пример с `2018-01-01`: [developer.android.com/privacy-and-security/security-config](https://developer.android.com/privacy-and-security/security-config) - Apple, «Identity Pinning: How to configure server certificates for your app» — «в большинстве случаев pinning не нужен, и его следует избегать», плюс рекомендация пиннить CA, а не лист: [developer.apple.com/news](https://developer.apple.com/news/?id=g9ejcf8y) - Райан Сливи в `net-dev@chromium.org`, март 2021 года — цитата про приватный CA целиком: [groups.google.com](https://groups.google.com/a/chromium.org/g/net-dev/c/q_oSyjtMhts) - OWASP Pinning Cheat Sheet — «ответ на него — вероятно, никогда» и список причин не пиннить: [cheatsheetseries.owasp.org](https://cheatsheetseries.owasp.org/cheatsheets/Pinning_Cheat_Sheet.html) - OWASP MASTG, раздел про сетевое взаимодействие — «если приложение не реализует pinning, это не следует сообщать как уязвимость; но если приложение обязано проходить проверку по MAS-L2, pinning должен быть реализован»: [github.com/OWASP/mastg](https://github.com/OWASP/mastg/blob/master/Document/0x04f-Testing-Network-Communication.md) - Бюллетень CA/Browser Forum SC-081v3 — график 398 → 200 → 100 → 47, внесён Apple, принят 11 апреля 2025 года: [cabforum.org](https://cabforum.org/2025/04/11/ballot-sc081v3-introduce-schedule-of-reducing-validity-and-data-reuse-periods/) - Let's Encrypt, «Generation Y» — шесть промежуточных, выбираются случайно, «чтобы не поощрять pinning промежуточных ключей»: [letsencrypt.org](https://letsencrypt.org/2025/11/24/gen-y-hierarchy/) - Cloudflare — двенадцать неавторизованных сертификатов на 1.1.1.1 и отсутствие Fina в мобильных корневых хранилищах: [blog.cloudflare.com](https://blog.cloudflare.com/unauthorized-issuance-of-certificates-for-1-1-1-1) - Flutter issue #96722 — открыт с января 2022 года: [github.com/flutter/flutter](https://github.com/flutter/flutter/issues/96722) - dio, `IOHttpClientAdapter` — где на самом деле вызывается `validateCertificate`, прямо в исходниках: [github.com/cfug/dio](https://github.com/cfug/dio/blob/main/dio/lib/src/adapters/io_adapter.dart) - Dart `X509Certificate` — полный список того, что класс отдаёт, и открытого ключа там нет: [api.dart.dev](https://api.dart.dev/stable/dart-io/X509Certificate-class.html) - `cronet_http`, `CronetEngine.build` — список параметров, где обход есть, а pinning отсутствует: [pub.dev](https://pub.dev/documentation/cronet_http/latest/cronet_http/CronetEngine/build.html) #### Нужен ли certificate pinning моему мобильному приложению? Скорее нет, если вы не в финансах, медицине или госуслугах и ваша модель угроз не предполагает прямо враждебную сеть и недоверенное устройство. Google называет pinning не рекомендованным для Android-приложений, Apple пишет, что в большинстве случаев он не нужен и его следует избегать, а OWASP Pinning Cheat Sheet на вопрос нужно ли пиннить отвечает: вероятно, никогда. Не пиннитесь, если не можете обновить набор пинов без релиза, не знаете пару ключей до прода или не контролируете и сервер, и приложение. #### Отсутствие SSL pinning — это уязвимость? Само по себе нет. Руководство OWASP по тестированию мобильных приложений прямо говорит, что отсутствие pinning не следует сообщать как уязвимость, а MASVS требует его только от приложений, которые обязаны проходить проверку по MAS-L2 — профилю для софта с чувствительными данными. Если в отчёте пентеста отсутствие SSL pinning стоит как находка, это обычно результат автоматического чек-листа, а не суждение о вашей модели угроз. #### Что пиннить: сертификат, открытый ключ или CA? Открытый ключ, в виде SHA-256-хеша SPKI. Сертификат меняется при каждом продлении, ключ — нет, поэтому продление с тем же ключом вообще не затрагивает пин. И Android, и iOS нативно поддерживают только pinning по SPKI. Про уровень в цепочке: промежуточный сертификат для публичных CA снят с рассмотрения с 2024 года, когда крупнейший публичный CA начал выбирать промежуточные случайно; публичный корень настолько широк, что почти бессмысленен; лист работает только с резервными пинами. Свой приватный корень — единственный однозначно хороший случай. #### Работает ли certificate pinning во Flutter? Не так, как его настраивает большинство. Dart не использует сетевой стек платформы: dart:io проводит TLS через BoringSSL, встроенный в движок Flutter, поэтому pin-set из network_security_config.xml для трафика Dart на Android не читается. Это issue 96722 во Flutter, заведённый в январе 2022 года и до сих пор открытый. На iOS, по сообщениям, работает, потому что Dart передаёт решение о доверии в SecTrust, — то есть типичный исход это pinning, который держит на одной платформе и молча ничего не делает на другой. #### Почему badCertificateCallback — неправильное место для pinning в Dart? Потому что он вызывается только после того, как валидация уже провалилась. Если атакующий предъявит сертификат, корректно подписанный любым доверенным CA, колбэк не сработает и отпечаток не будет сравнён вообще. Часто копируемый пример, где он идёт вместе с SecurityContext и withTrustedRoots false, дополнительно отключает построение цепочки, проверку срока действия и проверку имени хоста. Хук validateCertificate в dio ближе к правильному, но выполняется после отправки запроса и получения ответа, поэтому защищает только тело ответа. #### Как срок жизни сертификатов в 47 дней влияет на pinning? Он делает pinning листа с обновлением через релиз практически неподъёмным. Бюллетень CA/Browser Forum, принятый в апреле 2025 года, снижает максимальный срок жизни TLS-сертификатов с 398 дней до 200 в марте 2026-го, 100 в марте 2027-го и 47 в марте 2029-го. Сорок семь дней — это около восьми ротаций в год. Если набор пинов меняется только вместе с релизом в сторе, это восемь принудительных релизов в год, каждый с ревью и с пользователями, которые не обновляются. Pinning ключа вместо сертификата убирает большую часть этой цены, потому что продление с тем же ключом пин не меняет. #### Как проверить, что мой pinning действительно работает? Поставьте сертификат прокси в системное хранилище доверия, а не в пользовательское, — через эмулятор с записываемым системным разделом или Magisk, — и прочитайте перехваченный трафик release-сборки. Наивный тест с пользовательским сертификатом не доказывает ничего, потому что приложения Android не доверяют пользовательским сертификатам начиная с targetSdk 24. На Flutter отдельно убедитесь, что трафик вообще доходит до прокси: Dart игнорирует системные настройки прокси. Затем прогоните тот же тест на второй платформе и сравните. Если нужен фон про то, за что вообще отвечает удостоверяющий центр, мы разбирали это в статье [HTTPS без боли](https://ru.nerdy.pro/blog/https-free-ssl-certificate), а криптографию под ним — в [как работает шифрование](https://ru.nerdy.pro/blog/encryption-explained). Эта статья написана в пару к нашему видеовыпуску на ту же тему; предыдущий был [все ошиблись со сроками](https://ru.nerdy.pro/blog/everyone-got-the-timeline-wrong). А если при прогоне теста вы нашли что-то неожиданное — особенно отказ ровно на одной платформе, — нам правда интересно об этом услышать. Наша ставка: у половины Flutter-приложений pinning не работает как минимум на одной платформе. --- *Илья Никсан — основатель и ведущий разработчик [Nerdy Production](https://ru.nerdy.pro/), Flutter-first агентства, которое создаёт и поддерживает приложения в финтехе, медицине и ритейле. Мы также занимаемся [разработкой приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development) для команд, которые предпочли бы не выяснять всё это в продакшене.* ## Все ошиблись со сроками: AI, джуниоры и квантовые компьютеры https://ru.nerdy.pro/blog/everyone-got-the-timeline-wrong **Коротко.** Два года назад самые громкие голоса в индустрии обещали, что к этому моменту инженеры-программисты станут исторической диковинкой. Промпт на входе — продукт на выходе. На дворе август 2026 года, и занятость разработчиков не рухнула. Но та же волна, которая не смогла убрать разработчиков, убрала кое-что другое, и вот об этой половине почти никто толком не рассказывает: она удалила рутинную работу, на которой новички превращались в сеньоров. Занятость разработчиков 22–25 лет упала примерно на 20% с конца 2022 года, а у более старших инженеров в тех же профессиях осталась на месте или выросла. Квантовые вычисления сейчас проходят ровно тот же цикл с отставанием на круг: есть по-настоящему историческая веха, которую за пределами отрасли не обсуждает никто, и есть фальшивая, которая повсюду. ### Два утверждения, и оба верны Если вы получили диплом по компьютерным наукам в этом году, вы конкурируете с сотнями других кандидатов за вакансию, в названии которой написано *junior*, а в требованиях тихо просят три года опыта. И при этом с профессией в целом всё в порядке. Оба утверждения верны одновременно. Большинство высказываний на эту тему выбирает одно и делает вид, что второго не существует, — поэтому дискуссия и оказалась такой бесполезной. Интересный вопрос не в том, забрал ли AI работу (очевидно, не забрал), а в том, почему удар пришёлся так точно по тем, кто только пытается войти в профессию. ### Почему AI не забрал работу у разработчиков Начнём с того, в чём хайп прав, — потому что вокруг AI он возник не на пустом месте. Помощь модели больше не экзотика, а режим по умолчанию, и заметная доля нового кода пишется с моделью в контуре. Так почему рабочие места не исчезли? #### Написание кода никогда не было узким местом Подумайте, чем вы на самом деле заняты за рабочую неделю. Сколько времени вы набираете новый код? Процентов двадцать, в удачную неделю. Всё остальное — понять, что вообще нужно построить, разобраться в системе, которую кто-то написал пять лет назад, спорить о компромиссах и быть тем, кто *отвечает*, когда всё падает в три часа ночи. В BCG это хорошо сформулировали в анализе 2026 года: AI может радикально ускорить генерацию кода и тестов, но не может отвечать за результат от начала до конца. И работу нельзя аккуратно разрезать на «модель пишет код» и «инженер принимает решения», потому что в реальной разработке это одно и то же действие. Решение выражается *в виде* кода. #### Налог на проверку Вот это контринтуитивная часть, и именно на неё мы бы указали в первую очередь. METR — Model Evaluation and Threat Research, некоммерческая организация, которая исследует, на что эти модели действительно способны, — провела контролируемый эксперимент: опытные разработчики open-source-проектов работали в *своих собственных* кодовых базах, половину задач с AI-инструментами, половину без. С AI они работали на 19% медленнее. Тревожит здесь не само число. Тревожит то, что они были уверены, что стали быстрее. Они оценили ускорение, которого не было. Так устроена вся проблема целиком. Время, сэкономленное на генерации, возвращается в виде ревью, отладки и поддержки. Оно не исчезает — оно просто перемещается туда, где его труднее заметить. Мы проводим много времени в кодовых базах, где этот счёт уже предъявили к оплате; из этого и состоит большая часть находок [аудита AI-кода](https://ru.nerdy.pro/services/ai-code-audit). > **Скорость, которую вы чувствуете, — не та, которую можно измерить** > > Результат METR неудобен именно потому, что разработчики были опытными, работали в знакомом коде и искренне утверждали обратное тому, что показал секундомер. Ощущаемая и реальная скорость разошлись. Любая команда, которая строит свою AI-стратегию на том, насколько быстро работа *ощущается*, снимает показания с прибора, который заведомо сломан. #### Дешевле разрабатывать — значит, разрабатывать больше Бэклог любой компании — это кладбище того, на что ни у кого не хватило времени. Сделайте разработку дешевле, и вы не уволите разработчиков — вы наконец начнёте расчищать это кладбище. Так было при каждой революции в инструментах со времён компилятора, и нет особых причин считать, что на этот раз будет иначе. #### Многие «увольнения из-за AI» не были увольнениями из-за AI Компании сокращают штат по финансовым причинам и ссылаются на AI, потому что «мы возглавляем AI-трансформацию» звучит для акционеров лучше, чем «мы перенабрали людей в 2021-м, а потом выросли процентные ставки». Для этого уже есть термин — AI washing. Выдают их развороты: организации громко сокращали штат под AI-нарратив, а потом тихо возобновляли найм новичков, когда баланс приходил в норму. Итак: AI не убрал разработчиков. Он убрал *слой задач*. Запомните эту фразу — потому что именно этот слой был тренировочной площадкой. ### Что происходит с джуниорами Вот число, которое имеет значение. Стэнфордская Digital Economy Lab, работая с зарплатными данными ADP — Automatic Data Processing, одного из крупнейших операторов расчёта зарплат в США, — обнаружила на данных по миллионам работников, что занятость разработчиков в возрасте 22–25 лет упала примерно на 20% с конца 2022 года. Теперь посмотрите строкой ниже в том же наборе данных. По *всем* профессиям, затронутым AI, занятость снизилась на 0,2% год к году. У более старших работников в тех же профессиях — без изменений или рост. Ущерб не размазан по профессии. Он сконцентрирован почти целиком на тех, кто пытается в неё войти. #### Причина не в том, что AI умный Традиционный вход в индустрию был замаскированным ученичеством. Вас нанимали, и год вы делали работу, которую не хотел делать никто из старших: чинили мелкие баги, писали тесты, обновляли документацию, собирали скучный CRUD-экран. Вы не окупались. Все знали, что вы не окупаетесь. И это было нормально, потому что через двенадцать месяцев вы уже знали кодовую базу и становились полезны. AI съел ровно эту работу. Не интересную. *Тренировочную*. И теперь перед компаниями стоит вопрос, на который они не ответили: как вводить джуниора в работу, если рутины, которую можно ему передать, не осталось? #### И ситуацию усугубляют три обстоятельства **Вакансии врут.** Число объявлений с пометкой «начальный уровень» выросло, а реальный найм на начальные позиции упал: компания публикует джуниорскую вакансию и закрывает её человеком с пятью годами опыта. **Вы конкурируете не с джуниорами.** Увольнения 2024–2025 годов выбросили на рынок много мидлов, и многие из них согласятся на позицию с меткой junior. **Содержать джуниора стало дороже.** Сеньоры говорят, что заметно больше времени уходит на ревью, когда джуниоры слишком полагаются на AI-ассистентов. Стоимость джуниора выросла ровно в тот момент, когда его очевидная польза упала. Вот это — самое жестокое. #### Что с этим делать Перестаньте оптимизироваться под то, что автоматизировали. Вас не наймут в 2026 году за умение сделать React-компонент — модель делает это бесплатно. Аргумент для найма — это работа, которую модель заведомо не умеет: - Прочитать большую незнакомую кодовую базу и объяснить, *почему* она устроена именно так - Разобраться с проблемой в проде, когда логи вводят в заблуждение - Распознать, что вывод AI уверенно неверен, — сегодня именно это и отличает сильного кандидата, и именно эта способность у джуниоров слабее всего - Отвечать за результат, а не за тикет И трезво выбирайте, *куда* целиться. Открытые двери — не те, о которых пишут у вас в ленте. Энтерпрайз, медтех, финтех, госсектор, оборонный сектор, средние SaaS-компании, малый бизнес — места со старым кодом, требованиями комплаенса и реальной болью. Кандидаты со стажировкой, по их собственным отчётам, заметно чаще получают офферы, и этот разрыв стоит больше, чем любой фреймворк, который вы успеете выучить за месяц. Трудно — не то же самое, что закрыто. Но рынок нужно читать таким, какой он есть, а большинство тех, кто сейчас рассылает отклики, всё ещё откликается на рынок 2019 года. ### Квантовые вычисления: чего ждать на самом деле Та же машина хайпа уже заходит на второй круг, так что прививку стоит получить заранее. #### Результат, который действительно важен Чип Google Willow продемонстрировал коррекцию ошибок ниже порога. Простыми словами: десятилетиями каждый новый кубит вносил больше ошибок, чем удавалось исправить, поэтому масштабирование делало только хуже. Willow показал обратное — добавляете физические кубиты, и частота логических ошибок *падает*, экспоненциально. Один этот результат переводит квантовые вычисления из физики в инженерию. А инженерные задачи решаются. IBM публично идёт по дорожной карте к отказоустойчивой машине Starling, обещанной к 2029 году; текущий процессор Nighthawk — 120 кубитов, схемы с тысячами запутывающих вентилей и цель продемонстрировать подтверждённое квантовое преимущество до конца этого года, причём с открытым трекером, чтобы независимые исследователи могли проверять заявления на прочность. У Microsoft и Quantinuum логические кубиты работают с более низкой частотой ошибок, чем само железо, на котором они держатся. Реальный прогресс, а не пустые обещания. #### Чего это не значит **Это не значит, что квантовый компьютер заменит ваш ноутбук.** Квантовые машины — не «более быстрые компьютеры». Это специализированный ускоритель, который обыгрывает классическое железо на узком классе задач: симуляция квантовых систем (то есть химия и материаловедение) плюс отдельные задачи оптимизации и сэмплирования. Ваше веб-приложение никогда на нём не запустится. Для REST API квантового преимущества не существует. **Это не значит, что шифрование сломают в следующем году.** Чтобы взломать RSA-2048 — Rivest–Shamir–Adleman, алгоритм с открытым ключом и длиной ключа 2048 бит, на котором держится большая часть интернета, — нужны тысячи *логических* кубитов, что при сегодняшних частотах ошибок означает миллионы физических. В текущих системах — сотни физических. Такой разрыв не закрывают за один удачный квартал. Экспертный опрос Global Risk Institute оценивает вероятность появления криптографически значимой машины в течение десяти лет примерно в 17–22%. #### Единственная часть, которая касается вашей работы «Собери сейчас, расшифруй потом». Противник может записать зашифрованный трафик сегодня и расшифровать его в 2035 году. Поэтому для всего, у чего долгий срок конфиденциальности — медицинские карты, гостайна, юридические архивы, — дедлайн миграции *сейчас*, а не тогда, когда появится машина. Механику самой угрозы — алгоритм Шора, что он делает с RSA и ECC и почему спецслужбы уже сохраняют трафик, который пока не могут прочитать, — мы разбирали в [большом разборе шифрования](https://ru.nerdy.pro/blog/encryption-explained). NIST, Национальный институт стандартов и технологий США, утвердил стандарты постквантовой криптографии, федеральные агентства США ориентируются на срок около 2030 года, а практическая работа неблагодарна и скучна: провести инвентаризацию того, где у вас живёт криптография, и перейти на гибридный обмен ключами. Если вы всё равно разбираетесь, где в вашем хозяйстве терминируется TLS, — это та же самая работа. Вот и всё, что квантовые вычисления требуют от обычного разработчика в 2026 году. Учить Qiskit, чтобы сохранить работу, не нужно. **И когда прочтёте следующий заголовок, смотрите на правильную метрику.** Число физических кубитов — это маркетинг. Число логических кубитов, частота ошибок и глубина схемы — это инженерия. Считайте любое заявление о «квантовом преимуществе» предварительным, пока специалисты по классическим алгоритмам не потратят полгода на попытки побить его на ноутбуке: исторически многим это удаётся. ### Что насчёт AI на квантовых компьютерах? Это самое далёкое от реальности во всём обсуждении, и стрелка направлена в другую сторону. **Проблема ввода.** Чтобы выполнить квантовый алгоритм на обычных данных, эти данные сначала нужно загрузить в квантовое состояние, а для произвольных данных загрузка может стоить столько же, сколько само вычисление. Обучение языковой модели — это по большей части перекладывание данных. Квантовые вычисления хуже всего справляются именно с тем, что составляет основную часть работы. **Обучение не масштабируется.** Есть явление под названием «бесплодные плато»: по мере добавления кубитов в глубокую параметризованную схему градиенты убывают экспоненциально. Ландшафт функции потерь становится плоским, и спускаться некуда. Это не сноска — это центральная открытая проблема области. **Деквантизация.** Начиная с результата Эвин Тан 2018 года — она была студенткой и убила знаменитое квантовое ускорение, написав классический алгоритм, который его повторил, — исследователи раз за разом обнаруживают одно и то же. Дайте классическому алгоритму тот же доступ к данным, который квантовая версия молча предполагает, и преимущество испаряется. Деквантизация не убила квантовое машинное обучение. Она очертила его границы. Внутри этих границ есть реальные, но узкие применения: данные, которые изначально квантовые, плюс горстка ядерных методов (kernel methods) с доказанным разделением. Ничего похожего на языковую модель. А вот часть, которая нам кажется по-настоящему смешной. В апреле NVIDIA выпустила семейство открытых моделей Ising. Чем они заняты? Калибровкой квантовых процессоров и декодированием квантовой коррекции ошибок — существенно быстрее и точнее традиционных подходов. Перечитайте. AI используют, чтобы строить квантовые компьютеры, а не наоборот. Прогноз Gartner на ближайшие годы прямолинеен: до 2028 года ни одна корпоративная AI-нагрузка не будет работать на квантовом железе в промышленном масштабе, и даже отказоустойчивые системы к 2030 году не наберут логических кубитов для экономически оправданного сквозного AI. И одно предупреждение: «quantum-inspired» — маркетинговый термин для классического алгоритма на классическом железе. Когда вендор обещает ускорение обучения AI в двадцать раз за счёт квантовой оптимизации, проверьте, участвовал ли хоть один кубит. Обычно ни одного. ### Закономерность Хайп вокруг AI ошибся в том, что разработчики устареют, — и почти никто не заметил, что в другом он оказался прав: в чём-то худшем и куда более тихом. Мы сломали лестницу, которая превращает новичков в сеньоров. Мы решили проблему тренировочной работы, удалив тренировочную работу. Квантовые вычисления разыгрывают ту же партию. Настоящая веха — коррекция ошибок ниже порога — это исторический результат, и за пределами области о ней почти не говорят. Фальшивая — «шифрование умрёт в следующем году» — повсюду. Научитесь отличать одно от другого, и вы будете правы насчёт технологий чаще, чем большинство тех, кто в них работает. #### AI действительно сокращает число разработчиков? В сумме — нет. Занятость разработчиков не рухнула, а старшие когорты в затронутых AI профессиях остались на месте или выросли. Изменилось распределение: занятость разработчиков 22–25 лет упала примерно на 20% с конца 2022 года — по данным исследования Стэнфордской Digital Economy Lab на зарплатных данных ADP. AI убрал слой рутинных задач, а не разработчиков, и именно на этом слое джуниоры учились профессии. #### AI делает разработчиков быстрее? Менее надёжно, чем кажется. В контролируемом эксперименте METR опытные разработчики open-source, работавшие в своих собственных кодовых базах, оказались на 19% медленнее в задачах с AI-инструментами — и при этом оценили, что стали быстрее. Время генерации возвращается в виде ревью, отладки и поддержки. В отдельных сценариях выигрыш реален, но ощущение ускорения — ещё не доказательство, поэтому командам стоит измерять результат, а не доверять ощущению. #### На чём сосредоточиться джуниору в 2026 году? На работе, которую модель не умеет. Прочитать большую незнакомую кодовую базу и объяснить, почему она устроена именно так; отладить прод, когда логи вводят в заблуждение; распознать, что вывод AI уверенно неверен; отвечать за результат, а не за тикет. Целиться стоит в энтерпрайз, медтех, финтех, госсектор и средние SaaS-компании, где старый код и требования комплаенса создают реальный спрос. Стажировка по-прежнему стоит больше любого фреймворка, который можно выучить за месяц. #### Скоро ли квантовые компьютеры сломают шифрование? Не скоро, но мигрировать нужно уже сейчас. Для взлома RSA-2048 нужны тысячи логических кубитов, что при текущих частотах ошибок означает миллионы физических, против сотен физических сегодня. Экспертные опросы оценивают появление криптографически значимой машины в течение десяти лет примерно в 17–22%. Срочность создаёт схема «собери сейчас, расшифруй потом»: записанный сегодня трафик расшифруют позже, поэтому всё с долгим сроком конфиденциальности требует постквантовой миграции сейчас. Стандарты NIST утверждены. #### Сделают ли квантовые вычисления AI значительно лучше? Правдоподобного пути в обозримом будущем нет. Загрузка обычных данных в квантовое состояние может стоить столько же, сколько вычисление; градиенты экспоненциально исчезают в глубоких параметризованных схемах (бесплодные плато); результаты по деквантизации раз за разом показывают, что классические алгоритмы повторяют заявленные квантовые ускорения при том же доступе к данным. Gartner не ожидает промышленных корпоративных AI-нагрузок на квантовом железе до 2028 года. Показательно, что поток идёт в обратную сторону: AI применяют для калибровки и коррекции ошибок квантовых процессоров. #### Как отличить настоящую квантовую веху от маркетинга? Смотрите на метрику. Число физических кубитов — маркетинг; число логических кубитов, частота ошибок и глубина схемы — инженерия. Коррекция ошибок ниже порога, когда добавление физических кубитов снижает частоту логических ошибок, и есть по-настоящему исторический результат. Любое заявление о квантовом преимуществе стоит считать предварительным, пока специалисты по классическим алгоритмам не потратят месяцы на попытки побить его на обычном железе, — исторически многим это удаётся. А quantum-inspired означает классический: кубитов там нет. Если у вас кодовая база, куда код приезжал быстрее, чем кто-либо успевал его смотреть, — это ровно та ситуация, которую описывает налог на проверку, и именно для неё существует [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit). Мы также писали о том, [как мы разрабатываем с AI](https://ru.nerdy.pro/blog/building-with-ai), и об [одиннадцати проблемах, которые находим почти в каждой AI-кодовой базе](https://ru.nerdy.pro/blog/ai-code-audit-findings). А если вы тот, кто пытается войти в профессию прямо сейчас: лестница сломана, но здание — нет. Цельтесь в работу, которую модель не умеет, и в компании, чьи проблемы старые, неброские и настоящие. --- *Илья Никсан — основатель и ведущий разработчик [Nerdy Production](https://ru.nerdy.pro/), Flutter-first агентства, которое создаёт и поддерживает приложения в финтехе, медицине и ритейле.* ## dxpdf 0.5.0: как мы научили конвертер DOCX читать не только по-английски https://ru.nerdy.pro/blog/dxpdf-0-5-0-release [dxpdf](https://ru.nerdy.pro/open-source/dxpdf) превращает `.docx` в PDF. Без Microsoft Office, без headless-режима LibreOffice, без облачных API — один бинарник на Rust, который читает OOXML напрямую и рисует результат через Skia от Google. Мы написали его потому, что все альтернативы заставляли выбирать между точностью вёрстки, скоростью и возможностью не отправлять документы клиентов на чужие серверы, — а та, на которой мы в итоге и сидели, headless LibreOffice, оказалась десктопным приложением, которое приходится нянчить в продакшене. [Версия 0.5.0](https://github.com/nerdy-pro/dxpdf/releases/tag/v0.5.0) вышла 11 августа. Это релиз, в котором конвертер перестал — сразу в десятке мелочей — считать, что входящий документ написан по-английски. ### Ключевые выводы - **0.5.0 — релиз про интернационализацию.** Переносы строк по UAX #14 (включая тайский, лаосский, кхмерский и бирманский), двунаправленный текст по UAX #9 с зеркалированием по правилу L4, числа и даты из CLDR по атрибуту `w:lang` самого документа. - **Появился первый внешний контрибьютор** — [@ikashapov](https://github.com/ikashapov) добавил русские форматы нумерации, разбор `w:commentReference` и исправил три дефекта в метках списков. - **Данные локалей поставляются одним урезанным блобом** через `icu_provider_blob`, а не через `compiled_data` каждого крейта ICU4X: «корректно во всех локалях» не должно означать «и бинарник теперь огромный». - **Стоимость конвертации определяет не размер документа, а то, как разрешаются его шрифты** — документ на 9 страниц и 1,3 МБ конвертируется за 55 мс, а на 3 страницы и 34 КБ — за 170 мс, и вся разница именно в шрифтах. - **Покрытие ISO 29500: 74 возможности реализованы полностью, 11 частично, 12 пока нет** — и именно последняя колонка и есть самая честная дорожная карта, которую мы можем опубликовать. - **Он заменил пайплайн на headless LibreOffice** — десктопный пакет за очередью, сторожевым процессом и отдельным каталогом профиля на каждый воркер. Во что это обходилось — ниже. - dxpdf распространяется [под лицензией MIT](https://github.com/nerdy-pro/dxpdf), опубликован на [crates.io](https://crates.io/crates/dxpdf) и [PyPI](https://pypi.org/project/dxpdf/), а теперь ещё и собирается в `.deb`. ### Спасибо первому внешнему контрибьютору В 0.5.0 впервые появился раздел **New Contributors**, и открывает его на редкость удачная работа. [@ikashapov](https://github.com/ikashapov) сделал [#118](https://github.com/nerdy-pro/dxpdf/pull/118): разбор `w:commentReference`, форматы нумерации `russianUpper` и `russianLower` и исправления трёх разных дефектов вёрстки и нумерации в метках списков. Это не правка опечатки мимоходом. Форматы нумерации — ровно тот случай, когда проблему находит только тот, у кого такие документы есть на руках. Договор со списком `а)`/`б)`/`в)` не попадёт в набор фикстур, собранный на английском, а три найденных дефекта меток лежали в общем коде вёрстки и портили результат всем, у кого документ устроен похожим образом. Спасибо. Если вы это читаете и dxpdf ломает вёрстку уже *ваших* документов — это ровно тот вклад, которого нам не хватает больше всего. Об этом в конце. ### Что у нас работало раньше: headless LibreOffice До dxpdf был `soffice --headless --convert-to pdf`, обёрнутый в очередь и ретрай, — как в большинстве документных пайплайнов. Это работает, и долгое время это был правильный выбор: ничто другое не конвертирует DOCX с такой точностью за цену `apt install`. Чем это точно не является, так это компонентом, который можно поставить в обработку запроса и перестать о нём думать: - **Это десктопное приложение в костюме сервера.** Нет ни библиотечного API, ни внутрипроцессного вызова: вы запускаете бинарник, читаете код возврата и надеетесь на лучшее — снаружи упавшая конвертация и упавший процесс выглядят почти одинаково. Первая конвертация в новом процессе вдобавок оплачивает старт целого офисного пакета. - **Один процесс — один профиль.** `soffice` монополизирует свой каталог профиля, поэтому каждому параллельному воркеру нужен собственный, иначе они конкурируют за одни и те же файлы блокировок. Масштабирование превращается в управление процессами, а не в пул потоков. - **Он зависает.** Необычный или битый документ может оставить `soffice` ждать вечно, поэтому вокруг продакшен-установки вырастают таймаут, сторожевой процесс и сборщик осиротевших процессов. Рано или поздно каждая команда, которая на такое подписалась, пишет одну и ту же обвязку-няньку. - **Память и размер образа.** Сотни мегабайт резидентной памяти на экземпляр и контейнер, несущий в себе целый офисный пакет, — плюс шрифты, которые надо туда положить, иначе метрики молча подменятся и вёрстка поедет. - **Точность зависит от версии.** Один и тот же документ на другом релизе LibreOffice может разбиться на страницы иначе, и обновление базового образа превращается в изменение того, что получает клиент. Узнавать об этом от клиента — худший из способов. Это не претензия к LibreOffice: это офисный пакет, и он очень хорош в том, чтобы им быть. Это утверждение о том, что происходит, когда GUI-приложение оказывается несущей конструкцией серверного пайплайна. dxpdf вырос из желания получить один вызов библиотеки, предсказуемый профиль памяти и вывод, который меняется только тогда, когда мы сами его меняем. ### Проблема: движок вёрстки, знавший один алфавит Смысл dxpdf всегда был в точности вёрстки. Конвертер DOCX в PDF полезен ровно настолько, насколько разрывы страниц совпадают с тем, что показывает Word: счёт, у которого строка «Итого» уехала на вторую страницу, хуже, чем отсутствие PDF вообще. Вся [архитектура проекта](https://github.com/nerdy-pro/dxpdf#architecture) существует ради этого: разобрать OOXML в неизменяемую модель, развернуть каскад стилей, посчитать и разместить всё до того, как что-то будет нарисовано. К версии 0.4.0 у нас был движок, который делал это очень хорошо — *для текста из пробелов и латиницы*. Каждое допущение, лежавшее в его основе, оставалось невидимым, пока не ломалось: - **Строки переносились по пробелам.** Это не алгоритм переноса, а эвристика, которая просто случайно работает для английского. В тайском, лаосском, кхмерском и бирманском пробелов между словами нет — по такому правилу целый абзац становится одним неразрывным токеном и уезжает за край страницы. - **Текст всегда верстался слева направо.** Арабский или иврит превращались в визуальную бессмыслицу: глифы правильные, порядок неправильный, скобки не зеркалированы. - **Числа и даты форматировались по-американски.** Десятичная табуляция выравнивалась по `.` там, где немецкий документ имел в виду `,`. Поле `DATE` с локализованной маской — `ДД.ММ.ГГГГ` вместо `dd.MM.yyyy` — не понималось вовсе. - **Числительные прописью знали только английский.** `cardinalText` в немецком документе давал `One`, а не `Eins`. - **Межбуквенный интервал работал по кодовым точкам.** Добавьте 2 pt разрядки к строке с комбинируемым диакритическим знаком — и знак уедет от своей буквы. Ничего экзотического здесь нет. Так выглядит первая встреча конвертера, написанного англоговорящими, с документом, который написан не на их языке. ### Решение: настоящие спецификации вместо новых эвристик Сквозная идея всего 0.5.0 в том, что каждое из этих мест заменено настоящим алгоритмом из Unicode или OOXML, а не более удачной догадкой. **Переносы строк теперь считаются по UAX #14** через ICU4X, причём по *абзацу*, а не по отдельному фрагменту (run): слово, разрезанное границей `` из-за случайной смены форматирования, всё равно переносится там, где говорит алгоритм, а не там, где разрезан XML. Для четырёх письменностей, которые UAX #14 явно передаёт «сложному контекстному анализу», границы слов ищет LSTM-модель. Токен, который ни одно правило не разрешает разорвать, обрезается по краю контейнера, а не вылезает за него, — так делает Word, и именно это нужно узкой ячейке таблицы. **Двунаправленный текст теперь обрабатывается по UAX #9**: уровни встраивания считаются по абзацу, перестановка выполняется по строке, зеркалирование — по правилу L4, чтобы скобки смотрели в нужную сторону. Выравнивание `w:jc` и отступы `w:ind` разрешаются относительно базового направления абзаца, а не относительно «левого края». **Письменности с позиционными формами проходят шейпинг через HarfBuzz.** Арабская, сирийская, нко, монгольская, адлам и другие подобные письменности без курсивного соединения просто нечитаемы, поэтому фрагмент с такой письменностью идёт через HarfBuzz внутри Skia, а всё остальное остаётся на прежнем, более дешёвом пути через cmap. **Числа и даты следуют за `w:lang`.** Десятичные разделители берутся из CLDR с учётом региона — `de-CH` и `de-DE` расходятся между собой, и теперь dxpdf воспроизводит это расхождение правильно. Поля `DATE` и `TIME` вычисляются с локализованными именами масок из §17.16.4.2. Числительные прописью работают для английского, немецкого, французского и испанского (`Eins`, `Vingt et un`, `Veintiuno`, `1.º`), для остальных языков остаются цифры. **Разрядка и выключка работают по графемным кластерам** согласно UAX #29: разрядка по §17.3.2.35 больше не отрывает комбинируемый знак от базовой буквы, а выравнивание `distribute` по §17.3.1.13 распределяет лишнюю ширину *между* кластерами, а не внутри одного. Помимо интернационализации в 0.5.0 закрыты те дефекты пагинации, которые замечаешь сразу: непрерывный разрыв раздела повышается до разрыва страницы, если параметры страницы действительно различаются; абзац с «не отрывать от следующего» остаётся внизу своей страницы перед явным разрывом; абзацы, состоящие из одного разрыва, получают положенную им высоту строки. Плюс синтез жирного и наклонного начертаний для шрифтов, у которых настоящих начертаний нет, [Windows в матрице CI](https://github.com/nerdy-pro/dxpdf/pull/123) и сборка `.deb`. ### Три решения, которые стоит объяснить #### Единицы измерения — это типы, а не числа OOXML измеряет всё в twip'ах, EMU, полупунктах, восьмых долях пункта и тысячных долях процента, иногда по три штуки в одном элементе. Очевидный подход — привести всё к `f64` на границе парсера и жить дальше. В dxpdf сделано наоборот: каждая единица OOXML — отдельный тип поверх `i64` в `model::dimension`, они проходят разбор и разрешение стилей без преобразований и потому переводятся туда-обратно без потерь, вёрстка работает исключительно в `Pt`, а голый `f32` появляется только на границе со Skia. ```text DOCX (ZIP) → Parse → Document Model → Resolve → Layout → Subset → Paint → PDF Twips/Emu/HalfPoints ←──── Pt throughout ────→ Skia ``` Выигрыш в том, что сложение twip'ов с полупунктами становится ошибкой компиляции, а не документом, у которого один отступ отличается в десять раз. Такие баги мучительно ловить глазами — результат выглядит как правдоподобный документ, просто слегка неправильный, — и они исчезают полностью, если компилятор не даёт единицам смешаться. Геометрические типы обобщены по единице измерения по той же причине, а их `Pt`-специализации живут в `render::geometry`, чтобы слой модели вообще не зависел от Skia. #### Данные локалей — один урезанный блоб, а не вшитые в бинарник данные У крейтов ICU4X есть фича `compiled_data`, которая зашивает полный набор данных CLDR прямо в бинарник. Это простой путь — и очень дорогой по размеру: вы получаете все локали, все календари и все валюты независимо от того, упоминает их документ или нет. Вместо этого dxpdf собирает один урезанный блоб под те локали, которые действительно поддерживает, и загружает его через [`icu_provider_blob`](https://crates.io/crates/icu_provider_blob). Больше инфраструктуры сборки, ещё один артефакт, который надо держать в актуальном состоянии, — и бинарник, который пользователь CLI действительно захочет поставить. Тот же инстинкт виден в соседнем решении: [`unicode-joining-type`](https://crates.io/crates/unicode-joining-type) используется как *предикат*, единственная задача которого — не пускать HarfBuzz на латиницу. Дорогой путь включается для тех письменностей, которым он нужен, и больше ни для каких. В обоих случаях компромисс один и тот же: платить за корректность там, где она требуется, а не равномерно везде. #### У вёрстки появилась спекулятивная область Вопрос §17.6.22 — останется ли непрерывный разрыв раздела на текущей странице — нельзя решить, двигаясь только вперёд: ответ зависит от того, что идёт после разрыва. Значит, вёрстка обязана попробовать размещение, заглянуть вперёд и откатить попытку, если ответ окажется отрицательным. Это невозможно, если состояние вёрстки — это `&mut`, который вы мутируете по всей цепочке вызовов. В [#111](https://github.com/nerdy-pro/dxpdf/pull/111) появилась спекулятивная область `BuildState`: участок работы вёрстки, который можно целиком зафиксировать или целиком выбросить. Хорошая иллюстрация повторяющегося в проекте паттерна: спецификация говорит не только о том, что реализовать, но и о том, какой формы должна быть архитектура. В вёрстке Word есть заглядывание вперёд — значит, конвертеру, которому нужны разрывы страниц как в Word, нужно место, куда складывать отменённую попытку. ### Цифры Замерено на Apple M3 Max с помощью `hyperfine` (30 прогонов, 5 прогревочных) на v0.5.0, на фикстурах, закоммиченных в репозиторий, — так что числа воспроизводимы: | Фикстура | Страниц | Вход | Время конвертации | Пик RSS | | --- | --- | --- | --- | --- | | `sample-docx-files-sample3` | 3 | 34 КБ | **170 мс** | 55 МБ | | `sample-docx-files-sample-4` | 7 | 10 КБ | **170 мс** | 52 МБ | | `sample-docx-files-sample1` | 9 | 1,3 МБ | **55 мс** | 42 МБ | | `sample-docx-files-sample4` | 171 | 14 МБ | **420 мс** | 159 МБ | Интересна третья строка. Документ на 9 страниц, который несёт в сорок раз больше данных, конвертируется втрое быстрее трёхстраничного — потому что **стоимость конвертации определяет то, как разрешаются шрифты, а не размер документа**. Реестр шрифтов строится по уровням и лениво: документ, чьи шрифты встроены или уже есть в системе, до дорогого уровня не доходит и тратит там около 4 мс, а тот, которому приходится падать в системный индекс метаданных и сопоставлять по PostScript- и стилевым именам, платит 120–185 мс — один раз. На трёхстраничной фикстуре этот поиск примерно в пять раз дороже, чем разбор, вёрстка, сабсеттинг и отрисовка вместе взятые. Для оценки пакетной нагрузки это меняет сам вопрос. Важно не *насколько большие документы*, а *называют ли они шрифты, которые уже есть на хосте*. Документы, написанные в Word, обычно называют. Покрытие и распространение на момент публикации: - **74 возможности OOXML реализованы полностью, 11 частично, 12 пока нет**, проверка по ISO 29500. Полная матрица — в [README](https://github.com/nerdy-pro/dxpdf#ooxml-feature-coverage), вместе с пропусками. - **Около 6000 загрузок на crates.io** за 38 опубликованных версий, плюс wheel-пакеты на PyPI для macOS, Linux и Windows под Python 3.8+. - **29 звёзд, 6 форков**, лицензия MIT, проекту пять месяцев. - В продакшене используется в [nerdy.pro](https://nerdy.pro) и [formtastic.de](https://formtastic.de). И честная обратная сторона: нет переупорядочивания индийских письменностей, нет подстановки шрифта для отдельных глифов, нет автоматических переносов по слогам, нет отслеживания исправлений и комментариев, нет SmartArt и диаграмм, а обтекание изображений в режимах tight и through аппроксимируется описывающим прямоугольником, а не полигоном. Всё это перечислено с пометкой `❌` в той же таблице, что и достижения. ### Установка ```bash cargo install dxpdf # CLI pip install dxpdf # Python ``` ```bash curl -LO https://github.com/nerdy-pro/dxpdf/releases/download/v0.5.0/dxpdf_0.5.0-1_amd64.deb sudo apt install ./dxpdf_0.5.0-1_amd64.deb ``` - **Исходники и issues** — [github.com/nerdy-pro/dxpdf](https://github.com/nerdy-pro/dxpdf) - **Заметки о релизе** — [v0.5.0](https://github.com/nerdy-pro/dxpdf/releases/tag/v0.5.0) - **Крейт** — [crates.io/crates/dxpdf](https://crates.io/crates/dxpdf) · **Документация API** — [docs.rs/dxpdf](https://docs.rs/dxpdf) - **Пакет для Python** — [pypi.org/project/dxpdf](https://pypi.org/project/dxpdf/) - **Страница проекта** — [dxpdf на nerdy.pro](https://ru.nerdy.pro/open-source/dxpdf) - **Для контекста** — [как мы генерируем PDF на Rust с помощью Skia](https://ru.nerdy.pro/blog/skia-rust-pdf-rendering), фундамент, поверх которого нарисовано всё описанное выше ### Вклад в проект нужен по-настоящему dxpdf написан на [Rust](https://ru.nerdy.pro/technologies/rust), и внести вклад здесь несложно. Две вещи стоят больше, чем кажется: **DOCX, который рендерится неправильно, ценен не меньше патча.** Проект живёт на фикстурах: документ, воспроизводящий дефект и закоммиченный вместе с исправлением, — это то, как в таблице покрытия закрепилась каждая строка. Если dxpdf ломает ваш документ — [заведите issue](https://github.com/nerdy-pro/dxpdf/issues) с файлом или с минимальной его версией, которую можно показать. **Колонка `❌` — это дорожная карта.** Автоматические переносы, подстановка шрифта для отдельных глифов, зеркалированные табуляции при `w:bidi`, `chineseCounting` и остальные счётные форматы, SmartArt: каждая позиция — понятная по объёму задача с привязанным разделом спецификации. Первый PR от @ikashapov начался ровно оттуда. Пожалуйста, заводите issue перед большим PR, а перед пушем запускайте то же, что запускает CI: ```bash cargo fmt --all -- --check cargo clippy --all-targets -- -D warnings cargo test --all ``` Соглашения проекта описаны в [`AGENTS.md`](https://github.com/nerdy-pro/dxpdf/blob/main/AGENTS.md). ### Частые вопросы #### Что нового в dxpdf 0.5.0? 0.5.0 — релиз про интернационализацию: переносы строк по UAX #14 через ICU4X (включая тайский, лаосский, кхмерский и бирманский), двунаправленный текст по UAX #9 с зеркалированием по правилу L4, шейпинг через HarfBuzz для письменностей с курсивным соединением, десятичные разделители с учётом региона и локализованные маски полей DATE/TIME по атрибуту w:lang документа, а также числительные прописью для английского, немецкого, французского и испанского. Кроме того, исправлено несколько краевых случаев пагинации, добавлен синтез жирного и наклонного начертаний, Windows добавлена в матрицу CI и появилась сборка .deb для Debian и Ubuntu. #### Чем dxpdf отличается от headless LibreOffice? LibreOffice в режиме --headless — стандартный ответ на эту задачу, и конвертирует DOCX он с хорошей точностью, но это десктопный пакет, запущенный на сервере. Он монополизирует каталог своего профиля, так что каждому параллельному воркеру нужен свой; он может зависнуть на необычных документах, и вокруг него приходится строить таймаут, сторожевой процесс и сборщик осиротевших процессов; он занимает сотни мегабайт резидентной памяти и требует соответствующего образа контейнера; а его вывод меняется при смене версии LibreOffice. dxpdf — один бинарник на Rust с библиотечным API, предсказуемым профилем памяти и выводом, который меняется только вместе с самим конвертером. #### Нужен ли для dxpdf Microsoft Office или LibreOffice? Нет. dxpdf — самостоятельный бинарник на Rust, который читает DOCX напрямую и отрисовывает PDF через Skia. Не нужны ни установленный Office, ни headless-процесс LibreOffice, ни внешний сервис — а значит, документы не покидают машину, на которой идёт конвертация. #### Насколько быстро работает dxpdf? На Apple M3 Max закоммиченные фикстуры конвертируются за 55–170 мс, а документ на 171 страницу и 14 МБ — примерно за 420 мс. Размер документа значит меньше, чем то, как разрешаются шрифты: документ, чьи шрифты встроены или уже есть в системе, тратит на это около 4 мс, а тот, что падает в системный индекс метаданных, платит 120–185 мс один раз. #### Какие языки и письменности поддерживает dxpdf? Начиная с 0.5.0: переносы по UAX #14 для всех письменностей, включая те, что пишутся без пробелов (тайская, лаосская, кхмерская, бирманская), двунаправленный текст по UAX #9 для арабского и иврита с зеркалированием, шейпинг через HarfBuzz для письменностей с позиционными формами — арабской, сирийской, нко, монгольской, адлам. Переупорядочивание индийских письменностей пока не поддерживается, и нет подстановки шрифта для отдельных глифов: документ должен называть шрифт, покрывающий используемые символы, — так, как это делает Word. #### Можно ли пользоваться dxpdf из Python? Да. Установите его командой pip install dxpdf и вызывайте dxpdf.convert(bytes) или dxpdf.convert_file("input.docx", "output.pdf"). Wheel-пакеты публикуются для macOS, Linux и Windows под Python 3.8 и новее, так что тулчейн Rust для использования не нужен. #### Как помочь проекту dxpdf? Заведите issue с DOCX, который рендерится неправильно: проект живёт на фикстурах, поэтому воспроизводящий документ полезен не меньше патча. Если хочется писать код — неподдерживаемые пункты в матрице возможностей из README и есть дорожная карта, у каждого указан раздел ISO 29500. Перед большим PR заведите issue, а перед пушем прогоните cargo fmt, cargo clippy и cargo test. ### Конвертируете документы в больших объёмах? dxpdf распространяется [под лицензией MIT и с открытым исходным кодом](https://ru.nerdy.pro/open-source/dxpdf) — пользуйтесь, форкайте или расскажите, где он не справляется с вашими документами. Если обработка документов нужна не сбоку, а внутри продукта, [напишите нам](https://ru.nerdy.pro/contact): этот конвертер появился потому, что он всё время требовался нам в клиентских проектах, и поддерживать его открыто нам приятнее, чем каждый раз втихую собирать заново. --- *Илья Никсан — основатель и ведущий разработчик [Nerdy Production](https://ru.nerdy.pro/), Flutter-агентства, которое заодно пишет и поддерживает инфраструктурные инструменты вроде [dxpdf](https://ru.nerdy.pro/open-source/dxpdf) и [Orosu](https://ru.nerdy.pro/open-source/orosu-server), на которых держится его собственная разработка.* ## Orosu: почему мы заменили SSH-ключи для деплоя подписанными WebSocket-задачами https://ru.nerdy.pro/blog/orosu-secure-cicd-deployments Почти в каждом проекте рано или поздно возникает одна и та же будничная задача: перенести артефакт сборки из CI/CD на сервер и запустить его. На это никто не закладывает время — «это же просто деплой», — поэтому решают её один раз, под дедлайном, самым быстрым из возможных способов. Обычно это приватный SSH-ключ в секретах CI и шелл-скрипт, который копирует файл по SCP и перезапускает сервис. Работает. Но не масштабируется дальше первого проекта — а мы пересобирали чуть разные версии этой связки для каждого клиента, пока наконец не сели и не написали инструмент, который нам действительно был нужен: [Orosu](https://ru.nerdy.pro/open-source/orosu-server). > От японского 降ろす (*órosu*) — «разгружать», «сгружать». Orosu — небольшой сервис на Rust, `orosu-server`, который устанавливается один раз на машину. Вместо SSH-ключа, открывающего CI доступ к шеллу, CI получает ключ Ed25519, которым можно запустить ровно один из заранее заданного набора скриптов — через аутентифицированное WebSocket-соединение. Эта статья о том, почему эта разница достаточно важна, чтобы ради неё написать отдельный инструмент, и как это выглядит на практике. ### Ключевые выводы - **Проблема не в самом SSH, а в том, что подразумевает SSH-доступ.** Ключ, которым можно залогиниться и запустить один деплой-скрипт, может запустить что угодно. Любой креденшл, добравшийся до продакшн-машины, наследует весь радиус поражения шелла — нужен он задаче или нет. - **Orosu сужает это до закрытого набора заранее заданных скриптов.** CI выбирает скрипт по имени и передаёт аргументы; произвольную команду передать нельзя, что бы ни попало в скомпрометированный пайплайн. - **Аутентификация — это подпись, а не секрет в канале.** Каждая задача подписывается ключом Ed25519 клиента. Пароля или общего секрета, который можно перехватить, в канале не существует. - **Это один бинарник на Rust без рантайм-зависимостей** — ставится через apt или готовый бинарник из GitHub Releases и работает за любым обратным прокси, который уже терминирует TLS на этой машине. - **Опциональный слой сквозного шифрования** закрывает единственную брешь, которую оставляет TLS на прокси, — скрывает аргументы скрипта и вывод от самого этого прокси. - Orosu — [open source-проект под лицензией Apache-2.0](https://ru.nerdy.pro/open-source/orosu-server) — мы сами им пользуемся и предпочли бы, чтобы другие команды перестали изобретать это заново. ### Форма проблемы Уберите обёртку с «деплоя из CI» — и это всегда один и тот же запрос: запустить этот скрипт на той машине с этими файлами. Способ, которым большинство команд закрывает этот запрос по умолчанию, накапливает риск по одному и тому же предсказуемому паттерну: - **Приватный SSH-ключ** попадает в секреты CI. Обычно он не ограничен одним скриптом — он даёт доступ к шеллу целиком, потому что ограничить SSH «только этой одной командой» настолько хлопотно, что почти никто этого не делает. - Тот же ключ, или почти его копия, **переиспользуется во всех репозиториях и пайплайнах**, которым нужен доступ к этому серверу, потому что генерировать и ротировать отдельный ключ на каждый пайплайн — это лишний процесс, который никто не хочет брать на себя. - **Деплой-скрипт копируется** из прошлого проекта, мелкие различия накапливаются, и никто уже не помнит, какая версия — эталонная. - **Продакшн становится напрямую доступен** с CI-раннеров — а это ровно тот класс хостов, на который целится атакующий, потому что скомпрометированный раннер с SSH-доступом — это уже скомпрометированный сервер. - В день, когда ключ *действительно* утекает, его ротация означает поиск **всех пайплайнов, которые могли на него ссылаться**, — потому что реестра никогда не было, только секреты, разбросанные по всем репозиториям, которые успели обзавестись копией. Это не проблема квалификации. Это стандартная форма задачи «выполнить скрипт на удалённой машине из пайплайна», когда инструмент, за который хватаются, — универсальный удалённый шелл. SSH не плох для того, чтобы залогиниться и подебажить машину руками. Он неподходящий примитив для CI-задачи, которой всего-то нужно запускать одни и те же три-четыре деплой-скрипта. ### Чего мы на самом деле хотели Ещё до первой строчки кода требования были не столько про криптографию, сколько про то, что должен и не должен уметь деплой-креденшл: 1. **Креденшл CI не должен уметь открывать шелл.** Он должен уметь запускать скрипт по имени из списка, который контролирует владелец сервера, — и точка. 2. **Аутентификация не должна зависеть от секрета, который передаётся с каждым запросом.** Подпись доказывает владение ключом, не раскрывая его. 3. **Добавление нового деплой-таргета не должно означать генерацию новой пары SSH-ключей и ручное протаскивание её через конфиг сервера** — это должна быть запись в конфиге и команда `keygen`. 4. **Источником истины о том, что можно запускать, должен быть сервер**, а не пайплайн. Скомпрометированная или небрежная CI-задача никогда не должна уметь расширить собственные права. Из этого списка складывается WebSocket-протокол, а не старый SSH под новой обёрткой. Именно этим Orosu и стал. ### Как это работает `orosu-server` работает на целевой машине. Он слушает WebSocket-соединения, проверяет подпись Ed25519 у каждой входящей задачи, сверяет имя скрипта со списком разрешённых для этого клиента скриптов — и только если всё сошлось, запускает скрипт с теми аргументами и файлами, которые пришли вместе с задачей. ```yaml listen: tcp: "127.0.0.1:8081" clients: - name: my-ci-client secret_file: /etc/orosu/my-ci-client.pub scripts: - name: deploy command: - "bash" - "/etc/orosu/scripts/deploy.sh" ``` Этот список `scripts:` — вся модель безопасности в одном месте. `my-ci-client` может запустить `deploy` и ничего больше — не другой скрипт на той же машине, не произвольную команду шелла, не скрипт другого клиента. Если пайплайн CI, у которого есть ключ этого клиента, скомпрометирован, атакующий наследует ровно возможности одного деплой-скрипта, а не логин. Пара ключей клиента получается через `orosu-keygen`: ```shell orosu-keygen --name my-ci-client --private-key-output my-ci-client.key --public-key-output my-ci-client.pub ``` Публичная половина идёт в конфиг сервера выше; приватная становится секретом CI и используется только для подписи запросов — сама по себе она не даёт сессию. Запуск скрипта из GitHub Actions — через сопутствующий Action: ```yaml - name: Deploy uses: orosu-ci/orosu@v0 with: address: ${{ secrets.OROSU_SERVER_URL }} script: deploy key: ${{ secrets.OROSU_CLIENT_KEY }} arguments: ${{ github.sha }} ``` Вот и всё изменение в пайплайне: шаг с SSH становится шагом с Orosu, а серверная поверхность атаки сжимается: было «всё, что может шелл этого ключа», стало «этот один именованный скрипт». Сам `orosu-server` обычно слушает localhost и стоит за обратным прокси, который терминирует TLS и пробрасывает апгрейды WebSocket, — nginx, в типичном случае, та же машина, которая, скорее всего, уже делает это для сервиса, перезапускаемого деплой-скриптом. ### Почему не просто ограничить SSH-ключ через `command=` SSH технически поддерживает ограничение `command=` в `authorized_keys`, и нас часто спрашивают, почему бы не ограничиться этим. Две причины, по которым такого ограничения недостаточно на практике, и обе — о том, что происходит *вокруг* штатного сценария, а не о самом сценарии: - **Это конфиг на стороне сервера, который правят вручную под каждый ключ, — и правку не видит автор пайплайна.** Ничто не гарантирует, что каждый деплой-ключ в `authorized_keys` действительно несёт ограничение `command=` — у файла нет схемы, и на первый взгляд неограниченный ключ рядом с девятью ограниченными выглядит абсолютно так же. - **Ограниченная SSH-сессия всё равно говорит на языке шелла.** В зависимости от скрипта и того, как `command=` взаимодействует с аргументами, которые пробрасывает SSH, границу между «выполнить ровно это» и «выполнить ровно это, но с аргументами, куда можно что-то внедрить» легко провести неправильно, не заметив этого. Аргументы скрипта в Orosu — это поля протокола, а не токены шелла, собранные из командной строки SSH: шелла, из которого мог бы сбежать аргумент, в этом пути просто нет. Orosu не утверждает, что SSH небезопасен. Он утверждает, что безопасная версия «ограничить SSH одной командой» требует столько дополнительных телодвижений, что почти никто не делает это стабильно, — а инструмент, единственная задача которого — запускать один из нескольких заранее заданных скриптов, может сделать это поведением по умолчанию, а не опцией, которую нужно включать вручную. ### Конфиденциальность за обратным прокси WSS закрывает канал, но когда TLS терминируется на обратном прокси перед `orosu-server` — а это и есть стандартная схема, описанная выше, — аргументы скрипта, загруженные файлы и вывод остаются открытым текстом на этом прокси. Для большинства деплой-скриптов это не проблема; для некоторых — проблема (аргумент деплоя может быть креденшлом, который скрипт передаёт дальше, токеном отката, идентификатором клиента). Ответ Orosu — опциональный слой сквозного шифрования: X25519 для согласования ключей, HKDF-SHA256 для деривации сессионных ключей, ChaCha20-Poly1305 для шифрования, — который работает поверх WebSocket-транспорта и включается независимо с обеих сторон: ```shell orosu-keygen --kind server --private-key-output server.key --public-key-output server.pub ``` Публичный ключ сервера добавляется в его конфиг и в параметр `server_key` GitHub Action. Сервер с настроенным шифрованием по-прежнему обслуживает клиентов, не передающих `server_key`, точно так же, как раньше, — скоординированного перехода не требуется, дня «Х» не будет, и ничего не сломается у клиента, который ещё не обновился. ### Честно о защите Мы считаем это полноценной частью инструмента, а не галочкой в чек-листе. Несколько конкретных фактов — потому что заявление «нам важна безопасность» лучше подкреплять тем, что реально изменилось: релиз 0.7.0 добавил проверку на точки малого порядка при обмене X25519 (закрывающую известный класс атак на эллиптические кривые), защитил извлечение вложений так, что специально сформированный или чрезмерно большой zip не может выйти за пределы каталога извлечения или исчерпать диск через распаковку, и заставил `orosu-keygen` писать файлы приватных ключей с правами `0600` независимо от текущего umask. Все три изменения вышли как совместимое обновление — тот же конфиг, тот же протокол, тот же CLI. Полная история — в [`CHANGELOG.md`](https://github.com/orosu-ci/server/blob/main/CHANGELOG.md). ### Чем Orosu не является Ограниченность здесь — фича, а не пробел, но стоит сказать об этом прямо. Orosu не оркестрирует раскатку релизов, не управляет откатами и не понимает топологию вашего деплоя — он запускает скрипт, который вы уже написали сами, и именно скрипт решает, что такое деплой. Это не Kubernetes-нативный инструмент: если ваш таргет уже кластер с настоящим контроллером деплоя, этот контроллер почти наверняка лучший выбор, а Orosu решает проблему, которой у вас уже нет. Он находит своё место там, где машина *не* является нодой кластера, — одиночный VPS, голое железо, сервер «когда-нибудь мы его контейнеризируем», на котором до сих пор крутится часть стека у многих агентств и стартапов. ### Быстрый старт `orosu-server` и `orosu-keygen` поставляются как пакеты для Debian/Ubuntu: ```shell curl -fsSL https://packages.nerdy.pro/NerdyPro.gpg | sudo gpg --dearmor -o /usr/share/keyrings/nerdy-pro.gpg echo "deb [signed-by=/usr/share/keyrings/nerdy-pro.gpg] https://packages.nerdy.pro/ stable main" | sudo tee /etc/apt/sources.list.d/nerdy-pro.list sudo apt update sudo apt install orosu ``` GitHub Releases также публикует готовые бинарники — для машин, куда нельзя добавить apt-репозиторий. [Полное руководство по настройке](https://ru.nerdy.pro/open-source/orosu-server) — генерация ключей, конфиг сервера, подключение GitHub Action — на странице проекта. Orosu написан на [Rust](https://ru.nerdy.pro/technologies/rust) — том же языке, на котором написан [dxpdf](https://ru.nerdy.pro/open-source/dxpdf), наш движок конвертации DOCX в PDF. Это язык, к которому мы обращаемся, когда инструмент должен быть быстрым, предсказуемым и устойчивым к не вполне доверенным входным данным — например, к приложенному клиентом файлу, пришедшему по сети. ### Часто задаваемые вопросы #### Почему не использовать ограниченные SSH-ключи для деплоя из CI? Ограничение command= в authorized_keys может привязать ключ к одной команде, но это конфиг сервера без схемы, который правят вручную под каждый ключ, — ничто не мешает неограниченному ключу незаметно оказаться рядом с корректно ограниченными. Ограниченная сессия всё равно говорит на языке шелла, поэтому обработку аргументов приходится выверять вручную. Orosu делает allowlist скриптов поведением протокола по умолчанию, а не опциональной договорённостью, и передаёт аргументы как поля протокола: шелла, из которого они могли бы сбежать, в этой цепочке просто нет. #### Что произойдёт, если скомпрометируют CI-пайплайн, использующий Orosu? Атакующий наследует ровно те скрипты, на которые настроен ключ этого клиента, с теми аргументами, которые допускает протокол для этого скрипта, — никогда не произвольную команду шелла и никогда не скрипты другого клиента. Это заметно меньший радиус поражения, чем у скомпрометированного SSH-ключа, который обычно даёт полноценный логин-шелл. #### Заменяет ли Orosu деплой в Kubernetes? Нет. Orosu — для машин, которыми ещё не управляет контроллер кластера: VPS, голое железо, сервер со смесью сервисов вне Kubernetes. Если у вашего таргета уже есть настоящий контроллер деплоя, он и есть лучший инструмент. #### Шифруется ли соединение с orosu-server? По умолчанию соединение защищено WSS/TLS, обычно терминируемым на обратном прокси. Опциональный слой сквозного шифрования (X25519 + HKDF-SHA256 + ChaCha20-Poly1305) дополнительно защищает аргументы скрипта, файлы и вывод от самого этого прокси и включается независимо на клиенте и сервере. #### Orosu — это open source? Да, под лицензией Apache-2.0. Сервер, CLI orosu-keygen и GitHub Action — всё это на GitHub, вместе с apt-репозиторием и готовыми бинарниками релизов. ### Хотите перестать логиниться по SSH на сервер, куда деплоите? Именно для этого нужен Orosu. Прочитайте [полную документацию проекта](https://ru.nerdy.pro/open-source/orosu-server) — там всё про настройку, посмотрите остальные [наши open-source проекты](https://ru.nerdy.pro/open-source) или [свяжитесь с нами](https://ru.nerdy.pro/contact), если хотите, чтобы мы посмотрели, как ваша команда доставляет код в продакшн, — деплой-пайплайны, собранные под дедлайном, — это конкретная и частая находка в [аудите AI-кода](https://ru.nerdy.pro/services/ai-code-audit), а Orosu — тот самый фикс, который мы применяем не реже, чем рекомендуем. --- *Илья Никсан — основатель и ведущий разработчик [Nerdy Production](https://ru.nerdy.pro/), Flutter-first агентства, которое строит и поддерживает в том числе инфраструктурные инструменты — такие как [Orosu](https://ru.nerdy.pro/open-source/orosu-server) и [dxpdf](https://ru.nerdy.pro/open-source/dxpdf), — на которых держится его собственная работа над проектами.* ## Стоимость поддержки Flutter-приложения в 2026 году: сколько вы платите после запуска https://ru.nerdy.pro/blog/flutter-app-maintenance-cost **Коротко.** - Закладывайте примерно **15–25% от стоимости разработки Flutter-приложения в год** на поддержку — чтобы приложение за 2,7 млн ₽ оставалось в рабочем состоянии, нужно примерно 405–675 тыс. ₽ в год. - Поддержка делится на четыре категории: исправление багов (**корректирующая**), реакция на изменения ОС/SDK/API (**адаптивная**), улучшение того, что уже работает (**совершенствующая**), и погашение технического долга до того, как он аукнется (**превентивная**). - Flutter — это **один поток поддержки, а не два**: одна кодовая база для обновлений, тестирования и релизов вместо отдельных команд под iOS и Android, которые делают ту же работу дважды. - Дешёвая разработка не гарантирует дешёвую поддержку: наспех написанный или бесконтрольно сгенерированный AI-код без тестов и архитектуры раздувает расходы сразу по всем четырём категориям. - Поддержка — это процент, который начисляется каждый год, пока приложение живёт: закладывайте его в бюджет на старте, а не после первого инцидента. ### Что на самом деле входит в понятие «поддержка» «Поддержка» — расплывчатый термин для всего, что происходит после запуска, и именно поэтому её легко недооценить при планировании бюджета. Это не самодеятельная классификация: деление на четыре категории закреплено в ISO/IEC 14764, международном стандарте по сопровождению программного обеспечения, и на практике оно работает: - **Корректирующая** — исправление багов, которые дошли до продакшена: краши, неверные расчёты, сломанные сценарии. Это первое, что приходит в голову при слове «поддержка», и обычно — самая небольшая категория в здоровой кодовой базе. - **Адаптивная** — изменения, которые навязывает внешний мир: новый стабильный релиз Flutter, новая версия iOS или Android, обновление политики стора, платёжный провайдер меняет API. Вы не выбирали эту работу — её выбрала платформа. - **Совершенствующая** — улучшения, которые никто не требует: оптимизация производительности, доработка UX, новые функции на существующих экранах. Именно здесь «поддержка» незаметно превращается в «развитие продукта» — и это нормально, просто это тоже текущие расходы. - **Превентивная** — рефакторинг, обновление зависимостей, патчи безопасности и погашение технического долга *до* того, как что-то сломается, а не после. Это категория, которую первой урезают при сжатом бюджете, и та, что дороже всего аукается спустя пару лет. Реалистичный годовой бюджет на поддержку должен покрывать все четыре категории, а не только корректирующую, о которой обычно думают в первую очередь основатели. ### Сколько это стоит **Планируйте 15–25% от стоимости разработки в год.** Где именно вы окажетесь в этом диапазоне, зависит от того, насколько активно в приложение добавляются новые функции (совершенствующая работа растёт вместе со скоростью продукта), сколько сторонних интеграций оно несёт (каждая — источник будущей адаптивной работы), и работаете ли вы в регулируемой сфере, где требования комплаенса меняются у вас на глазах. Применим этот диапазон к уровням стоимости разработки из нашего [разбора стоимости Flutter-разработки](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026): | Уровень | Типичная стоимость разработки | Типичная годовая поддержка | | --- | --- | --- | | MVP | 1 350 000–2 700 000 ₽ | 202 500–675 000 ₽ | | Бизнес-приложение | 2 700 000–5 400 000 ₽ | 405 000–1 350 000 ₽ | | E-commerce | 4 500 000–8 100 000 ₽ | 675 000–2 025 000 ₽ | | Enterprise / AI | от 8 100 000 ₽ | от 1 215 000 ₽ | Эти цифры поддержки — то же самое правило, применённое к диапазонам стоимости разработки, а не отдельно посчитанная цифра: воспринимайте их как ориентир для планирования, а не как коммерческое предложение. Первый год после запуска обычно тянет к верхней границе диапазона: вы всё ещё находите корректирующие баги, которые всплывают у реальных пользователей и которые не поймало QA, а приложение ещё не стабилизировалось. Второй и третий год у качественно сделанного приложения обычно устаканиваются ближе к нижней границе. О том, как сама стоимость разработки раскладывается по фазам, — в нашем материале о [процессе разработки Flutter-приложения](https://ru.nerdy.pro/blog/flutter-app-development-process) и в [разборе сроков](https://ru.nerdy.pro/blog/how-long-to-build-a-flutter-app) — поддержка уже упомянута там как последняя фаза в обоих случаях. ### Что формирует эту стоимость Часть этих статей расходов касается любого приложения — мобильного или нет. Часть — специфична именно для Flutter. | Статья расходов | Специфично для Flutter? | Почему это стоит денег | | --- | --- | --- | | Обновления Flutter SDK | Да | Flutter выпускает несколько стабильных релизов в год; оставаться в актуальной версии — дёшево, отстать на два мажорных релиза — уже нет. | | Обновления iOS / Android и политика сторов | Нет | Apple и Google каждый год выпускают по мажорной версии ОС плюс минорные обновления, а требования сторов и API пересматривают по несколько раз в год. | | Изменения в зависимостях / пакетах | Частично | Пакеты pub.dev забрасывают или в них прилетают breaking changes; нативные приложения несут тот же риск в своих собственных экосистемах пакетов. | | Подписки на сторонние SDK | Нет | Аналитика, пуш-уведомления, платежи, карты — регулярные расходы на вендоров, не зависящие от фреймворка. | | Бэкенд / хостинг / API | Нет | Растёт вместе с нагрузкой, а не с тем, на чём написан клиент. | | Исправление багов (корректирующая) | Нет | В любом приложении есть баги; вопрос — сколько стоит найти и исправить каждый. | | Патчи безопасности | Нет | И приложение, и его зависимости нужно патчить по мере появления уязвимостей. | | Аналитика / мониторинг | Нет | Краш-репортинг и аналитика использования — это то, как вы находите проблемы раньше, чем это делают тикеты в поддержку. | | Итерации по функциям (совершенствующая) | Нет | Самая крупная и переменная статья — растёт вместе с тем, насколько активно вы продолжаете развивать продукт. | Строки, специфичные для Flutter, — меньшая часть таблицы. Основная часть расходов после запуска не имеет никакого отношения к тому, на каком фреймворке написано приложение, — это цена эксплуатации софта как такового. ### Где Flutter экономит (а где — нет) Честная версия тезиса «Flutter дешевле поддерживать» у́же, чем маркетинговая: **одна кодовая база — это один поток обновлений, один цикл QA и один релиз вместо двух.** Команда, которая поддерживает iOS и Android раздельно, платит за одно и то же исправление бага, один и тот же апдейт SDK и один и тот же прогон регрессионного тестирования дважды — по разу на платформу, по разным графикам, часто силами двух разных инженеров, которым приходится синхронизироваться друг с другом. Это тот же самый механизм, благодаря которому разработка на Flutter [обходится примерно на 30–40% дешевле](https://ru.nerdy.pro/blog/hire-flutter-developers-2026), чем два нативных приложения, — и по нашему собственному [сравнению Flutter и нативной разработки](https://ru.nerdy.pro/blog/flutter-vs-native-2026), после запуска эта экономия обычно не уменьшается, а растёт: каждая следующая доработка и фикс по-прежнему пишутся один раз, а не дважды. Где это работает не полностью: платформо-специфичные интеграции — глубокая работа с ARKit/ARCore, отдельные платёжные SDK, edge-кейсы фоновой обработки — всё равно требуют нативной экспертизы даже внутри Flutter-приложения, потому что в этот момент Flutter обращается к нативному коду, а не заменяет его. И Flutter-приложение несёт реальный риск зависимостей — так же, как нативное приложение зависит от своих библиотек: плагин может остаться без поддержки, или его вообще похоронит вендор, и его замена — это настоящая адаптивная работа. Flutter сокращает число случаев, когда за поддержку приходится платить дважды, но не отменяет саму стоимость поддержки. ### Почему плохо написанное приложение стоит дороже в поддержке Каждая из четырёх категорий поддержки дорожает, если код под капотом плохой, — и «плохой» здесь не значит, что приложение выглядит сломанным для пользователя. Это значит: нет тестов — и каждый фикс рискует принести новую регрессию. Нет цельной архитектуры — и изменение на одном экране отзывается побочными эффектами ещё на трёх, которые никто не предвидел. Задублированная логика — и один и тот же баг чинят один раз, а через полгода он всплывает в другом месте. Это не гипотеза. Мы [проаудировали десятки кодовых баз, написанных AI](https://ru.nerdy.pro/blog/ai-code-audit-findings) и почти всегда находили одни и те же одиннадцать проблем: открытые секреты, отсутствие валидации ввода, аутентификацию, которая никогда не проверяет, кто делает запрос, отсутствие тестов, задублированный код, отсутствие цельной архитектуры, callback hell вместо нормальных асинхронных паттернов и полное игнорирование среды деплоя. Ничего из этого не видно в демо. Всё это всплывает, как только вы пытаетесь безопасно что-то изменить. Наш [разбор Flutter-кода, написанного AI-агентами без присмотра](https://ru.nerdy.pro/blog/ai-agents-struggle-with-flutter), показывает ту же картину под другим углом — код, который сегодня работает и с каждым месяцем без ревью становится ощутимо сложнее трогать. Практический вывод: MVP за 1,4 млн ₽, собранный на скорую руку и без единого ревью, — это не приложение с поддержкой за 202,5 тыс. ₽ в год. Это кандидат на переписывание, притворяющийся бюджетом на поддержку. Если вам досталась чужая кодовая база — написанная AI, отданная на аутсорс или любая другая — и вы не знаете, что именно унаследовали, для этого и существует [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit): он покажет вашу реальную нагрузку по поддержке до того, как вы возьмёте на себя обязательства на год вперёд. ### Кто занимается поддержкой Три реальных варианта, те же компромиссы, что и при найме на изначальную разработку, — подробнее в нашем [разборе in-house, агентства и team augmentation](https://ru.nerdy.pro/blog/hire-flutter-developers-2026): - **In-house.** Оправдано, когда приложение — ядро бизнеса и вы хотите постоянно владеть кодом и знаниями о нём. Самые высокие фиксированные расходы, лучшее долгосрочное владение. - **Ретейнер с агентством.** Кто-то другой берёт на себя дежурства и экспертизу по Flutter и платформам; вы платите только за то, что использовали. Минимум управленческой нагрузки, меньше контроля за тем, кто именно и когда трогает код. - **Staff augmentation.** Инженеры работают внутри вашей команды, вашего репозитория, вашего процесса — вы управляете роадмапом, они закрывают потребность во Flutter-разработке. Хорошо работает, когда у вас уже есть инженерный менеджмент, но не хватает рук, — через [расширение команды](https://ru.nerdy.pro/services/team-augmentation). Свою собственную постоянную поддержку мы ведём как ретейнер с фиксированным числом часов в месяц, привязанный к конкретному приложению, а не как фиксированный пакет — финтех-приложение с тремя платёжными SDK и контентное приложение без единой интеграции не должны попадать в один и тот же план поддержки, и мы лучше оценим объём точно, чем продадим вам цифру, которая не подходит вашему приложению. ### Как снизить стоимость поддержки Ничего экзотического — в основном это дисциплина, которую дёшево соблюдать и дорого игнорировать: - **Обновляйте зависимости каждый квартал, а не раз в два года.** Один апдейт Flutter — это день работы. Три года отложенных апдейтов разом — это проект. - **Вкладывайтесь в тесты и CI на раннем этапе.** Регрессия, пойманная в CI, стоит минуты. Та же регрессия, пойманная пользователем, стоит тикета в поддержку, хотфикса и доверия. - **Закладывайте превентивную работу отдельной строкой**, а не тем, что остаётся после того, как выкатили функции. Это категория, которую первой урезают и которая обходится дороже всего, если её пропустить. - **Настороженно относитесь к хрупким или слабо поддерживаемым сторонним SDK.** Каждый добавленный — это будущий источник адаптивной работы вне вашего контроля: проверяйте активность поддержки пакета до того, как начнёте от него зависеть, а не после того, как он сломается. - **Мониторьте раньше, чем об этом сообщат пользователи.** Краш-репортинг и аналитика превращают тихий сбой в исправимый — ещё до того, как он станет отзывом в сторе. ### Частые вопросы #### Сколько стоит поддержка Flutter-приложения? Закладывайте примерно 15–25% от стоимости разработки приложения в год. Например, для приложения, разработка которого стоила 2 400 000 ₽, поддержка обходится примерно в 360 000–600 000 ₽ в год — сюда входят исправление багов, обновления ОС и SDK, патчи безопасности, хостинг и текущие улучшения. #### Какой процент от стоимости разработки составляет годовая поддержка? 15–25% в год — разумный ориентир для планирования. Приложения с большим числом сторонних интеграций, требованиями комплаенса или активной разработкой новых функций обычно тяготеют к верхней границе; более простые и стабильные приложения — к нижней. #### Flutter дешевле поддерживать, чем нативную разработку? В целом да, потому что одна кодовая база означает один цикл обновлений, QA и релизов вместо раздельных потоков для iOS и Android. Но это не полное освобождение от рисков: платформо-специфичные интеграции и риск зависимостей от плагинов всё равно требуют нативной экспертизы даже внутри Flutter-приложения. #### Что входит в поддержку приложения? Четыре категории: корректирующая (исправление багов), адаптивная (реакция на изменения ОС, SDK и API), совершенствующая (улучшения и новые функции) и превентивная (рефакторинг, патчи безопасности и погашение технического долга до того, как он аукнется). #### Как часто нужно обновлять Flutter-приложение? Flutter выпускает несколько стабильных релизов в год, а iOS и Android — по одной мажорной версии ОС ежегодно, плюс несколько минорных релизов и изменений политики сторов. Приложение, которое год простояло без изменений, накапливает адаптивную работу, которая дорожает тем сильнее, чем дольше её откладывают. #### Может ли дешёвое приложение оказаться дорогим в поддержке? Да. Наспех собранная или непроверенная разработка — без тестов, без цельной архитектуры, с задублированной логикой — делает любой будущий фикс и апдейт более рискованным и медленным, что выливается в раздутую стоимость поддержки независимо от того, насколько дёшево стоила изначальная разработка. #### Вы предлагаете постоянную поддержку Flutter-приложений? Да. Мы поддерживаем Flutter-приложения по ретейнеру с фиксированным числом часов в месяц, привязанному к конкретному приложению, — либо через team augmentation, либо через прямое соглашение о поддержке, а не по фиксированному универсальному пакету. ### Подберём план поддержки под ваше приложение Самый быстрый способ получить реальную цифру — не общее правило, а взгляд на вашу реальную кодовую базу. Если у вас уже есть живое Flutter-приложение и вы хотите знать, во сколько реально обойдётся его поддержка, — мы оценим план поддержки под него. Если не уверены, что именно унаследовали, начните с [аудита AI-кода](https://ru.nerdy.pro/services/ai-code-audit) — мы честно скажем, в каком оно состоянии. А если вы ещё на этапе разработки, наша команда [разработки приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development) делает приложения с расчётом на следующие три года, а не только на дату запуска. [Свяжитесь с нами](https://ru.nerdy.pro/contact) и расскажите, в какой точке сейчас находится ваше приложение. --- *Илья Никсан — основатель и ведущий разработчик [Nerdy Production](https://ru.nerdy.pro/), Flutter-first агентства, которое создаёт и поддерживает приложения в финтехе, здравоохранении и ритейле.* ## Как на самом деле работает сквозное шифрование: обмен ключами, алгоритм Double Ratchet и его реальные компромиссы https://ru.nerdy.pro/blog/how-end-to-end-encryption-works Сквозное шифрование обычно объясняют одной фразой: прочитать сообщение могут только отправитель и получатель. Эта фраза верна и почти бесполезна — она описывает гарантию, но не объясняет, как два устройства, которые никогда не встречались и общаются через сервер, которому ни одно из них не доверяет, на самом деле устанавливают общий секрет и сохраняют его в безопасности сообщение за сообщением, год за годом. Эта статья раскрывает детали: как работает первоначальное рукопожатие, когда получатель офлайн, как ключ шифрования меняется с каждым отдельным сообщением, и что E2EE намеренно оставляет незащищённым. ### Ключевые выводы - E2EE устанавливается через **протокол согласования ключей**, а не общий пароль — рукопожатие **X3DH** протокола Signal позволяет двум устройствам договориться об общем секрете, даже если одно из них в этот момент офлайн. - Как только сессия установлена, алгоритм **Double Ratchet** выводит совершенно новый ключ для каждого сообщения, обеспечивая **прямую секретность** (forward secrecy) — прошлые сообщения остаются в безопасности, даже если ключ утёк, — и **посткомпрометационную безопасность** (post-compromise security) — сессия самовосстанавливается после компрометации. - Групповые чаты не могут просто масштабировать одно и то же попарное рукопожатие — **Sender Keys** жертвуют небольшой долей прямой секретности ради того, чтобы шифровать каждое сообщение один раз, а не по разу для каждого получателя. - E2EE защищает **содержимое**, а не **метаданные** — кто с кем общался, когда и как часто, в большинстве реализаций по-прежнему видно серверу. - Самая сложная нерешённая проблема в продакшен-реализациях E2EE — не криптография, а **верификация ключей**: доказать, что полученный вами открытый ключ действительно принадлежит вашему контакту, а не злоумышленнику посередине. ### Что на самом деле означает «установление» шифрования Симметричные шифры, такие как AES, требуют, чтобы обе стороны уже владели одним и тем же ключом. Это прекрасно работает, когда общий секрет уже существует, но не объясняет, как он там оказался — вы не можете отправить ключ по тому же каналу, который пытаетесь защитить, и не можете рассчитывать, что приложения двух незнакомцев заранее чем-то обменялись. Установление E2EE означает решение именно этой изначальной проблемы: выработку общего секрета между двумя устройствами с использованием только публичной информации, по сети, контролируемой стороной, которой ни одно из устройств не доверяет. Механизм, который использует практически каждый современный защищённый мессенджер, — это **протокол Signal**, разработанный Тревором Перрином и Мокси Марлинспайком. На нём работают сам Signal, WhatsApp* и зашифрованный уровень Google Messages (RCS). Его рукопожатие называется **X3DH** — Extended Triple Diffie-Hellman (расширенный тройной Диффи-Хеллман) — и оно решает проблему, которую обычный обмен по Диффи-Хеллману решить не может: начать сессию с тем, кто прямо сейчас не в сети. ### Рукопожатие X3DH: договориться о секрете с тем, кто офлайн Телефонный звонок требует, чтобы обе стороны взяли трубку одновременно. Текстовое сообщение — нет: вы его отправляете, и оно ждёт. Мессенджерам нужно реализовать шифрование, обладающее тем же свойством асинхронности: Алиса должна иметь возможность начать зашифрованный разговор с Бобом, даже если телефон Боба выключен. X3DH достигает этого за счёт того, что каждый пользователь заранее публикует на сервере небольшой набор открытых ключей *до* того, как они понадобятся: 1. **Идентификационный ключ (IK)** — долгосрочная пара ключей, идентифицирующая устройство. Она редко меняется, и именно её в конечном счёте проверяет верификация кода безопасности. 2. **Подписанный предварительный ключ (SPK)** — среднесрочная пара ключей, периодически обновляемая (спецификация X3DH предлагает интервал порядка нескольких недель — до месяца), подписанная идентификационным ключом, чтобы получатель мог убедиться, что он действительно пришёл от этого устройства. 3. **Одноразовые предварительные ключи (OPK)** — набор одноразовых пар ключей. Сервер выдаёт по одному на каждую новую входящую сессию и удаляет его после использования, а каждое устройство периодически загружает новые, чтобы пополнить запас. Когда Алиса хочет впервые написать Бобу, она получает один из этих наборов с сервера (закрытых ключей там нет — только открытые половины). Затем она вычисляет **три или четыре отдельных обмена по Диффи-Хеллману** между комбинациями своих ключей и ключей Боба: - `DH1` = её идентификационный ключ с подписанным предварительным ключом Боба - `DH2` = её эфемерный (только что сгенерированный, одноразовый) ключ с идентификационным ключом Боба - `DH3` = её эфемерный ключ с подписанным предварительным ключом Боба - `DH4` = её эфемерный ключ с одноразовым предварительным ключом Боба, если он был доступен Она объединяет результаты и пропускает их через функцию вывода ключа (HKDF), чтобы получить единый общий секрет. Боб, когда выходит в сеть, располагает всеми закрытыми половинами, необходимыми, чтобы вычислить точно такое же значение самостоятельно — DH коммутативен ровно в том смысле, который и делает это возможным. Ни одна из сторон никогда не передаёт сам секрет — каждая независимо *вычисляет* одно и то же число из смеси долгосрочных, среднесрочных и одноразовых ключей. Почему четыре отдельных вычисления DH вместо одного? Каждое даёт конкретное свойство: - `DH1` и `DH2` привязывают сессию к долгосрочным идентичностям обеих сторон — именно это делает обмен **аутентифицированным**, а не просто секретным. - `DH3` (и `DH4`, если был доступен одноразовый ключ) добавляют свежий, одноразовый материал, так что если долгосрочный идентификационный ключ будет скомпрометирован позже, прошлые сессии, согласованные до компрометации, невозможно будет пересчитать. Это зачаток прямой секретности ещё до того, как запустится ратчет. - Одноразовый предварительный ключ специально защищает от сервера, который лжёт о подписанном предварительном ключе — использование каждого OPK ровно один раз ограничивает то, сколько может повторно воспроизвести вредоносный или скомпрометированный сервер. Результат X3DH — единый 256-битный общий секрет. Сам по себе он мало что даёт: использовать этот единственный секрет для каждого сообщения на протяжении всего разговора — это ровно та проблема «один утёкший ключ раскрывает всё», с которой начиналась эта статья. Этот секрет — не конец истории, а зерно для Double Ratchet. ### Double Ratchet: новый ключ для каждого сообщения Трещотка (ratchet) механически крутится только в одну сторону. Алгоритм Double Ratchet заимствует это название намеренно: состояние шифрования движется только вперёд, и нет способа отмотать его назад, чтобы восстановить прошлый ключ из более позднего. Он сочетает два ратчета, работающих вместе: **Ратчет симметричных ключей.** Каждая сторона хранит «цепочечный ключ» (chain key). Каждый раз при отправке сообщения текущий цепочечный ключ пропускается через хеш-функцию (HMAC), чтобы получить два значения: **ключ сообщения**, используемый для шифрования этого одного сообщения и затем отбрасываемый, и **новый цепочечный ключ**, который заменяет старый. Ключи сообщений никогда не используются повторно и не могут быть выведены друг из друга в обратном порядке — хеш-функция работает только вперёд. Если злоумышленник записывает шифротекст, а затем крадёт цепочечный ключ, он сможет расшифровать все сообщения, отправленные *после* этого момента, но ничего до него. **Ратчет Диффи-Хеллмана.** У симметричного ратчета самого по себе всё ещё есть слабое место: украденный цепочечный ключ бессрочно компрометирует всё, что будет отправлено дальше. Чтобы это исправить, каждый раз, когда разговор «поворачивается» — примерно каждый раз, когда собеседник отвечает, — каждая сторона генерирует новую эфемерную пару ключей DH, выполняет новый обмен по Диффи-Хеллману с последним открытым ключом собеседника и подмешивает результат в цепочечный ключ. Это означает, что в систему на регулярной основе поступает новая порция свежей, непредсказуемой энтропии, которую злоумышленник не может ни предсказать, ни вычислить заранее. Их сочетание даёт две отдельные гарантии, которые часто путают, хотя это не одно и то же: - **Прямая секретность** (forward secrecy) — компрометация сегодняшнего ключа не раскрывает вчерашние сообщения. Обеспечивается симметричным ратчетом: старые цепочечные ключи уже прохешированы вперёд и отброшены. - **Посткомпрометационная безопасность** (post-compromise security; в спецификации Double Ratchet она называется «восстановлением после взлома», break-in recovery, а неформально — «самовосстановлением») — компрометация сегодняшнего ключа не раскрывает и *завтрашние* сообщения, потому что следующий шаг DH-ратчета вносит новую случайность, которую злоумышленник никогда не видел. Обеспечивается DH-ратчетом. Вместе они означают, что единичная компрометация в конкретный момент времени — украденное устройство, ключ, полученный под принуждением, — имеет *ограниченный радиус поражения*, а не приводит к постоянному взлому. Разговор самовосстанавливается в течение одного-двух сообщений благодаря свежим обменам DH, и это принципиально иная модель безопасности, чем «ключ скомпрометирован — разговор скомпрометирован навсегда». ### Групповые сообщения: почему попарный протокол не масштабируется X3DH вместе с Double Ratchet описывает сессию один на один. У группы из 200 человек нет «одной сессии» — есть до 200×199 потенциальных попарных сессий, и шифрование каждого сообщения по разу на получателя означало бы 200 отдельных операций шифрования (и в 200 раз больше трафика) для одного-единственного сообщения. Ответ Signal — **Sender Keys**. Вместо попарных ратчетов между всеми участниками каждый из них генерирует один симметричный «ключ отправителя» и раздаёт его всем остальным участникам группы индивидуально, через уже установленные попарные сессии Double Ratchet (это первоначальное распространение — дорогая часть, выполняемая при каждом изменении состава участников). После этого отправитель шифрует групповое сообщение ровно **один раз** своим ключом отправителя, и каждый участник, получивший этот ключ, может расшифровать его напрямую — без шифрования для каждого получателя на горячем пути. Это даёт огромный прирост производительности ценой некоторых свойств ратчета: ключ отправителя не продвигается вперёд с каждым сообщением так же, как это делает попарная цепочка Double Ratchet, поэтому он обеспечивает более слабую прямую секретность в рамках своего собственного времени жизни, и его нужно явно ротировать при каждом изменении состава участников — когда кто-то покидает группу, каждый оставшийся участник должен сгенерировать и заново распространить новый ключ отправителя, иначе копия ушедшего участника всё ещё могла бы расшифровывать будущие сообщения. Именно поэтому удаление кого-то из большой группы с высокой текучкой участников измеримо дороже, чем отправка ему обычного сообщения, — ротация выполняет реальную криптографическую работу, а не просто обновление в базе данных. ### Проблема, которую одна криптография решить не может: верификация ключей Всё вышеописанное предполагает, что Алиса действительно получила от сервера открытые ключи *именно Боба*, а не злоумышленника. Если сервер (или кто угодно, способный действовать от его имени) выдаст Алисе набор ключей, контролируемый злоумышленником, X3DH отработает идеально и выдаст абсолютно валидный общий секрет — но не с тем человеком. Это классическая атака **«человек посередине»** (man-in-the-middle), и никакая математика вывода ключей её не исправит, потому что эта математика никогда не проверяет, *чей* именно ключ она получила. Смягчение этой проблемы — верификация по внешнему каналу: Signal и WhatsApp* отображают **код безопасности** (safety number) — отпечаток, выведенный из идентификационных ключей обеих сторон, — который пользователи могут сверить лично, по голосовой связи или отсканировав QR-код. Если числа совпадают, идентификационные ключи подлинные и никто не перехватывает обмен. Этот шаг необязателен, большинство пользователей никогда его не выполняют, и именно этот пробел — а не криптография — является местом, где на практике происходят реалистичные атаки на E2EE-мессенджеры: скомпрометированный или принуждённый к сотрудничеству сервер распространения ключей, а не взломанный шифр. ### Что это даёт, а что — нет **Плюсы:** - **Конфиденциальность переживает компрометацию сервера.** Поскольку сервер хранит только шифротекст (а в X3DH — ещё и открытые ключи), взломанная база данных, резервная копия, изъятая по повестке, или недобросовестный сотрудник не могут раскрыть содержимое сообщений — раскрывать попросту нечего. - **Прямая секретность ограничивает ущерб от кражи ключа.** Украденное устройство или скомпрометированный долгосрочный ключ не открывает задним числом всю историю сообщений пользователя. - **Посткомпрометационная безопасность означает, что система восстанавливается.** В отличие от единственного статического ключа, скомпрометированная сессия Double Ratchet восстанавливается в течение нескольких сообщений. - **Групповые сообщения масштабируются без повторного шифрования для каждого получателя** благодаря Sender Keys — приемлемая производительность при таких размерах групп, где попарное шифрование уже не справилось бы. **Минусы:** - **Метаданные остаются незащищёнными.** Сервер по-прежнему видит, кто кому пишет, как часто, с какого IP-адреса и как долго — [исследования стабильно показывают](https://www.eff.org/deeplinks/2013/06/why-metadata-matters), что одни только метаданные раскрывают чувствительные закономерности в отношениях и поведении, иногда даже надёжнее, чем содержимое. - **Компрометация конечного устройства обходит всё.** E2EE защищает данные при передаче и при хранении на сервере — но не на устройстве, уже скомпрометированном вредоносным ПО или физическим доступом. Расшифрованные сообщения по определению читаемы на устройстве, которое их расшифровало. - **Верификация ключей ручная и в основном пропускается.** Протокол настолько же надёжен, насколько надёжно его самое слабое звено, а для большинства пользователей это звено — «я никогда не проверял код безопасности». - **Дорого пристраивать постфактум.** Функции, которые предполагают видимость на стороне сервера — поиск, обнаружение спама, превью ссылок, модерация контента, резервное копирование, — приходится перестраивать или перепроектировать вокруг шифротекста, который сервер не может прочитать. Это ровно та миграция, которую инженерная команда Meta* [задокументировала для Messenger](https://engineering.fb.com/2024/02/22/security/end-to-end-encryption-messenger-update/), и именно поэтому отказ Instagram* в 2026 году от E2EE в личных сообщениях был продуктовым и регуляторным решением, а не криптографическим — механизм, который описывает эта статья, уже был построен и работал. Мы разбираем этот разворот и то, как E2EE сочетается с AES, TLS и постквантовой криптографией в более широкой архитектуре безопасности, в статье [«Шифрование простыми словами»](https://ru.nerdy.pro/blog/encryption-explained). - **Изменения состава группы стоят реальной криптографической работы.** Удаление участника требует ротации и повторного распространения ключей отправителя, а не переключения флага доступа. ### Проектировать заранее, а не пристраивать потом Повторяющийся урок из всех продакшен-реализаций E2EE — Signal, WhatsApp*, iMessage — в том, что части мессенджера, которые кажутся не связанными с криптографией (поиск, превью уведомлений, спам-фильтры, резервное копирование, синхронизация между устройствами), на самом деле и определяют, осуществимо ли E2EE. Протокол обмена ключами и ратчет на сегодняшний день — хорошо изученные, публично проверенные строительные блоки; сложная инженерная работа — везде, где раньше у сервера была видимость, а теперь её нет. Если вы создаёте или проверяете продукт, работающий с чувствительными пользовательскими данными — медицинскими записями, финансовой перепиской, юридической корреспонденцией, — решение о сквозном шифровании должно приниматься на этапе архитектуры, а не задним числом. Именно такие пробелы мы ищем в [аудите AI-кода](https://ru.nerdy.pro/services/ai-code-audit): места, где функция незаметно предполагает серверный доступ к открытому тексту, которого модель безопасности обещала никогда не допускать. Если вы хотите, чтобы кто-то ещё раз взглянул на то, как шифрование вписывается в архитектуру вашего приложения, [свяжитесь с нами](https://ru.nerdy.pro/contact). ### Источники - Perrin, T. & Marlinspike, M. (2016). [The X3DH Key Agreement Protocol](https://signal.org/docs/specifications/x3dh/). Signal. - Perrin, T. & Marlinspike, M. (2016). [The Double Ratchet Algorithm](https://signal.org/docs/specifications/doubleratchet/). Signal. - Cohn-Gordon, K. et al. (2016). [A formal security analysis of the Signal messaging protocol](https://eprint.iacr.org/2016/1013.pdf). Cryptology ePrint Archive. - WhatsApp (2024). [WhatsApp Security Whitepaper](https://www.whatsapp.com/security/WhatsApp-Security-Whitepaper.pdf). - EFF (2013). [Why metadata matters](https://www.eff.org/deeplinks/2013/06/why-metadata-matters). - Meta Engineering (2024). [End-to-end encryption on Messenger](https://engineering.fb.com/2024/02/22/security/end-to-end-encryption-messenger-update/). --- *Meta Platforms Inc. признана экстремистской организацией, её деятельность запрещена на территории Российской Федерации. WhatsApp и Instagram являются продуктами Meta Platforms Inc. ## Сколько времени занимает разработка Flutter-приложения? (таймлайн 2026) https://ru.nerdy.pro/blog/how-long-to-build-a-flutter-app **Коротко.** MVP на Flutter обычно занимает **6–12 недель** — от старта до публикации в сторах. Стандартное бизнес-приложение — **3–6 месяцев**. Сложное или регулируемое (финтех, медицина, всё, что будет читать аудитор) — **6–12+ месяцев**. Сильнее всего на эти числа влияет не скорость написания кода, а **объём работ и то, как быстро принимаются решения на стороне клиента**. Фазы к тому же перекрываются, поэтому календарный срок всегда короче суммы фаз — именно поэтому «сколько времени» и «сколько работы» — два разных вопроса. Любое агентство отвечает на этот вопрос словами «зависит от проекта». Действительно зависит — но под такой ответ нельзя запланировать запуск. Ниже — те цифры, которые мы называем сами, что в них входит и из-за чего они сдвигаются. ### Сколько занимает Flutter-приложение по уровням сложности? Три уровня покрывают почти всё, что нас просят построить. Сроки — от старта до публикации в сторах, при согласованном объёме и нормальном доступе к тому, кто принимает решения. | Уровень | Типичный срок | Пример состава | Команда | Главный фактор сроков | | --- | --- | --- | --- | --- | | **MVP** — проверить идею | **6–12 недель** | 5–8 ключевых функций, iOS + Android, Firebase или лёгкий REST-бэкенд, UI на библиотеке компонентов, базовая аналитика | 1–2 Flutter-разработчика, дизайнер part-time, ведущий инженер | Дисциплина объёма. Каждое «небольшое дополнение» — это неделя. | | **Стандартное бизнес-приложение** | **3–6 месяцев** | 15–20 функций, своя дизайн-система, кастомный бэкенд, админ-панель, сегментированные пуши, офлайн, платежи | 2–3 Flutter-разработчика, бэкендер, дизайнер, QA | Интеграции и готовность бэкенда — обычно не само приложение | | **Сложное или регулируемое** | **6–12+ месяцев** | Compliance (HIPAA, PCI-DSS, SOC 2), ролевой доступ, данные в реальном времени, интеграции с ERP/CRM, ML на устройстве | Полная команда плюс DevOps и менеджер проекта | Зависимости, которыми вы не управляете: аудиторы, банки, вендоры | Типичное бизнес-приложение — это три-пять месяцев; шестой появляется, когда интеграции или compliance приходят поздно, а приходят они поздно часто. Уровни совпадают с ценовыми в статье [сколько стоит разработка Flutter-приложения в 2026](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026) — примерно **1,4–2,7 млн ₽** за MVP и **2,7–5,4 млн ₽** за бизнес-приложение. Срок и бюджет двигаются вместе: вы покупаете команду на определённое число недель. ### Куда уходят недели? Здесь важнее всего третья колонка: эти фазы **не** идут одна за другой. | Фаза | Типичный срок | Пересекается с | | --- | --- | --- | | Дискавери и оценка объёма | 3 дня – 2 недели | Ничего реального не начинается раньше | | UX/UI-дизайн | 2–4 недели | Стартует до конца дискавери, хвостом заходит в разработку | | Архитектура и фундамент | 1–2 недели | Дизайн | | Основная разработка | 4–16 недель | Хвост дизайна, QA, работа над бэкендом | | Интеграции | 1–2 недели на каждую крупную | Основная разработка | | QA и тестирование | Непрерывно плюс 1–2 недели стабилизации | Основная разработка | | Сабмит и ревью в сторах | 1–2 недели (само ревью обычно 24–48 часов) | Идёт последним, ассеты готовятся заранее | Сложите строки в лоб — получите восемь месяцев на проект, который выходит за четыре. Разницу даёт перекрытие: дизайн доделывает экраны, пока разработчики собирают уже согласованные, QA идёт внутри каждого спринта, а бэкенд нагрузочно тестируется против мока. Что именно происходит внутри каждой фазы — в парном материале про [процесс разработки Flutter-приложения](https://ru.nerdy.pro/blog/flutter-app-development-process): эта статья про *сколько времени*, та — про *как*. ### Сколько занимает MVP против полноценного продукта? MVP занимает 6–12 недель, потому что MVP — это вопрос, а не продукт: 5–8 функций, UI на библиотеке компонентов, бэкенд, который вы не писали с нуля, и список отсечённого, который вы действительно соблюдаете. Именно под эту форму собрана наша услуга [разработки MVP](https://ru.nerdy.pro/services/flutter-app-development/mvp). Шесть недель — реальный срок, но с условиями. Нужен объём, который влезает на одну страницу, один человек, способный утвердить решение без комитета, и никакого нового технического риска: ни видеопайплайна, ни ML на устройстве, ни платёжных рельсов в новой юрисдикции. Перенос Telegram-бота с существующей аудиторией в нативное приложение занял [шесть недель](https://ru.nerdy.pro/blog/telegram-bot-to-app-6-weeks) именно потому, что бот уже проверил продукт, а объём был зафиксирован. Десять-двенадцать недель встречаются чаще, и лишний месяц почти никогда не про «код оказался сложнее» — это платёжный флоу, появившийся на четвёртой неделе, незапланированный раунд дизайна или две недели ожидания доступа к API. Полноценный продукт занимает 3–6 месяцев по причине, не связанной с числом функций: он строится, чтобы выдержать реальную нагрузку пользователей — своя дизайн-система, настоящая бизнес-логика, офлайн-поведение, именованные состояния ошибок, тесты в CI, админка для тех, кто отвечает на обращения. Это и есть та работа, которая отличает приложение, которое можно развивать, от того, которое переписывают через полтора года. ### Что на самом деле определяет сроки? Шесть вещей, примерно в порядке причиняемого ущерба. **1. Объём.** С большим отрывом. «Простое приложение» превращается в 40 экранов, как только посчитаны онбординг, авторизация, настройки, удаление аккаунта, платежи, пуши и офлайн — а всё это всегда было в том приложении, которое вы описали. Две дополнительные функции добавляют не две недели кода, а код, дизайн, QA и новый набор пограничных случаев. **2. Готовность бэкенда.** Если API существует, задокументирован и стабилен, Flutter-команда идёт на полной скорости. Если его параллельно пишет другая команда, ваш срок теперь равен их сроку. **3. Интеграции.** Каждая значимая — платежи, карты, чат, видео, биометрия, CRM — это одна-две недели. SDK подключается за вечер; пограничные случаи занимают остальные две недели. **4. Зрелость дизайна.** Приходить со своей дизайн-системой — это минус две-три недели, потому что разработчики собирают экраны из уже существующих компонентов. Отсутствие дизайна не ускоряет: работа просто переезжает в разработку, где стоит дороже. **5. Ревью в сторах.** Одна-две недели на фазу сабмита, и большая часть этого времени — ваша работа, а не Apple. Подробнее ниже. **6. Скорость решений — обычно это вы.** В проекте десятки открытых вопросов, и каждый ждёт кого-то. Десять вопросов с ответом на четвёртый день вместо первого добавляют шесть недель к проекту, в котором никто не написал ни одной медленной строчки. На здоровом проекте клиент — самое частое узкое место. И самое дешёвое в устранении. ### Flutter действительно быстрее нативной разработки? Да — но нужно точно понимать, что именно экономится, потому что здесь агентства обычно перегибают. Flutter сокращает **трудозатраты** примерно на **30–40 %** по сравнению с двумя нативными приложениями, и разрыв растёт по мере жизни продукта, потому что каждая следующая функция пишется один раз, а не два. Откуда берётся это число, мы разбирали в статье [Flutter против нативной разработки в 2026](https://ru.nerdy.pro/blog/flutter-vs-native-2026). Чего Flutter **не** делает — так это не сокращает календарь вдвое. Дискавери не становится короче от выбора Flutter, дизайн делается один раз в любом случае, QA всё равно нужны реальные устройства на обеих платформах, а бэкенду безразлично, на чём написан клиент. Шестимесячный нативный проект превращается примерно в четырёхмесячный на Flutter — не в трёхмесячный, — но с одной командой вместо двух и одной кодовой базой в поддержке. Flutter — это способ получить две платформы почти по цене одной. Это не способ получить полугодовое приложение за шесть недель. ### Сколько времени добавляет ревью в App Store и Google Play? Заложите **одну-две недели** на всю фазу сабмита — и почти никогда не ошибётесь. Само ревью здесь — меньшая часть. Ревью Apple обычно занимает **24–48 часов** после отправки. «Обычно» не значит «гарантированно», но причины отказов достаточно предсказуемы, чтобы проектировать с их учётом: нет удаления аккаунта, экран входа без демо-доступа, неполная декларация приватности, платежи в обход внутренних покупок. Google, как правило, проверяет быстрее, но совсем новый аккаунт разработчика может застрять в расширенном ревью на несколько дней. Время реально теряется на цикле отказа — это 24–72 часа туда и обратно, и первый сабмит, который его получает, — норма. Заложите один реджект, и вы его переварите; не заложите — и он придётся на дату запуска. Дольше идут два случая: к регулируемым данным у ревью появляются дополнительные вопросы, а выпуск нескольких брендированных приложений из одной кодовой базы превращает гайдлайн Apple **4.2.6** в отдельную дисциплину — те же экраны с новым логотипом отклоняют как дубликаты. Мы описывали, [что реально проходит ревью на масштабе](https://ru.nerdy.pro/blog/white-label-app-platform-flutter); учесть это заранее — разница между недельным сабмитом и месяцем апелляций. ### Где сроки поедут? Почти все срывы сроков, которые мы видели, объясняются четырьмя сценариями. **Расползание объёма мелкими просьбами.** Ни одно отдельное «а можно ещё…» не выглядит неразумным. Шесть таких — это месяц. **API, который приходит поздно.** Самая частая причина простоя Flutter-команды. Моки выигрывают пару недель, дальше не выигрывают ничего. **Дизайн через круги согласований.** Три раунда «давайте посмотрим ещё один вариант» на главном экране задерживают все последующие фазы: разработчики не могут собирать против движущейся цели. **Нерешительность, включая молчание.** Спринт, сборку которого никто на стороне клиента не поставил на телефон, — это спринт, чьи недопонимания вскроются через месяц. Три из четырёх пунктов — на стороне клиента. Это не перевод ответственности, а указание на рычаг: агентство может ускорить разработку процентов на десять, решительный клиент сжимает календарь на тридцать. ### Чек-лист: как выйти быстрее Шесть рычагов, и все они в ваших руках: 1. **Зафиксируйте объём письменно и ведите список отсечённого.** Функции, которые явно *не* входят в первую версию, — самый ценный артефакт проекта. 2. **Назначьте одного человека, принимающего решения** — того, кто может утвердить дизайн или закрыть вопрос в тот же день. 3. **Приносите API — или примите, что он на критическом пути.** Если бэкенд пишется параллельно, договоритесь об интерфейсе заранее и заморозьте его. 4. **Приходите с дизайн-системой** — палитра, типографическая шкала, состояния компонентов — или заложите три недели на её создание. Пропустить не получится, затраты просто переедут. 5. **Оформите аккаунты в сторах, декларации приватности и демо-доступы на первой неделе**, а не на той, когда вы сабмитите. 6. **Добавляйте ресурсы, а не давление** — и добавляйте заранее. Модели разобраны в статье [как нанимать Flutter-разработчиков в 2026](https://ru.nerdy.pro/blog/hire-flutter-developers-2026), а [расширение команды](https://ru.nerdy.pro/services/team-augmentation) — это как мы подключаем инженеров к существующей команде за дни, а не за месяцы. Один антирычаг: разработчики, добавленные в последний месяц, делают проект медленнее, а не быстрее — они изучают код у тех, кто и так был узким местом. ### Частые вопросы #### Сколько времени занимает разработка Flutter-приложения? MVP на Flutter обычно занимает 6–12 недель от старта до публикации в сторах. Стандартное бизнес-приложение со своей дизайн-системой, настоящим бэкендом и админкой — 3–6 месяцев. Сложное или регулируемое приложение — 6–12 месяцев и больше. Объём работ и скорость решений на стороне клиента влияют на эти числа сильнее, чем скорость разработки. #### Сколько времени занимает разработка MVP? Реалистичный диапазон для MVP на Flutter в 2026 году — от шести до двенадцати недель. Шесть недель требуют объёма, который влезает на одну страницу, одного человека с правом решения и отсутствия нового технического риска: видео, ML на устройстве, новые платёжные рельсы. Десять-двенадцать недель встречаются чаще, и лишнее время обычно уходит на объём, добавленный по ходу, или ожидание доступа к API, а не на медленный код. #### Flutter быстрее нативной разработки? Да, для большинства приложений. Одна кодовая база под iOS и Android сокращает трудозатраты примерно на 30–40 % против двух нативных приложений, и экономия растёт со временем, потому что каждая следующая функция пишется один раз. Календарь сокращается меньше, чем трудозатраты: дискавери, дизайн, QA, бэкенд и ревью в сторах не уменьшаются вдвое от того, что клиент кросс-платформенный. #### Сколько идёт ревью в App Store? Ревью Apple обычно занимает 24–48 часов после отправки, Google Play, как правило, быстрее, но совсем новый аккаунт разработчика может застрять в расширенной проверке на несколько дней. На всю фазу сабмита закладывайте одну-две недели: большая часть этого времени — ассеты для сторов, декларации приватности и вероятность одного цикла отказа. Первый сабмит часто отклоняют по предсказуемой причине — нет удаления аккаунта, нет демо-доступа, неполная декларация приватности. #### Можно ли сделать Flutter-приложение за месяц? За месяц можно сделать что-то настоящее, но не полноценный продукт. Четырёх недель хватает на узко очерченный прототип или приложение с одним сценарием на готовом бэкенде и библиотечном UI — этого достаточно для демо, пилота или разговора с инвестором. Готовому к сторам MVP с авторизацией, платежами и обязательными для Apple экранами настроек нужно 6–12 недель. #### Что сильнее всего замедляет разработку приложения? Рост объёма и медленные решения, в этом порядке. Функции, добавленные по ходу, стоят не только кода, но и дизайна с тестированием, а вопросы, на которые отвечают четыре дня вместо одного, суммируются по десяткам решений, которые требует проект. Третья причина — поздний бэкенд: когда API приходит на восьмой неделе двенадцатинедельного проекта, никакая скорость разработки календарь уже не вернёт. #### Большая команда сделает приложение быстрее? До определённого предела и только если размер команды выбран с самого начала. MVP хорошо идёт силами одного-двух Flutter-разработчиков; третий редко ускоряет, потому что работа на таком объёме не делится чисто. Люди, добавленные в конце проекта, замедляют его: онбординг отнимает время у тех инженеров, которые и были ограничением. ### Нужен срок под ваше приложение? Общие диапазоны годятся, чтобы спланировать квартал, и бесполезны, чтобы спланировать запуск. Нужна цифра под *ваш* состав функций, состояние бэкенда и ограничения сторов. Пришлите, что вы строите, — вернёмся с разобранным по фазам сроком: что идёт параллельно, что мы бы отрезали, чтобы попасть в дату, и какие части оценки зависят от вас, а не от нас. Если дата недостижима, мы скажем это до того, как вы что-то подпишете. Это и есть наша практика [разработки приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development): фиксированный объём, устанавливаемая сборка каждые одну-две недели и срок, который мы готовы защищать, — посмотрите [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf), рыночные данные в реальном времени в регулируемой сфере, и [Arcana](https://ru.nerdy.pro/portfolio/arcana), AI-чат, доведённый до сторов. [Расскажите, что вы строите](https://ru.nerdy.pro/contact) — и мы назовём дату, а не диапазон, в котором удобно спрятаться. --- *Дима — ведущий Flutter-разработчик [Nerdy Production](https://ru.nerdy.pro/), Flutter-first агентства, которое доводит приложения от дискавери до App Store в финтехе, ритейле и AI-продуктах.* ## Процесс разработки Flutter-приложения: как мы доводим идею до App Store https://ru.nerdy.pro/blog/flutter-app-development-process **Коротко.** Процесс разработки Flutter-приложения состоит из семи фаз: **дискавери и определение объёма**, **UX/UI-дизайн**, **архитектура и фундамент**, **итеративная разработка**, **QA и тестирование**, **релиз** и **поддержка после запуска**. Связывает их то, что это *циклы обратной связи, а не водопад*: дизайн стартует раньше, чем закончится дискавери, QA идёт параллельно сборке, а не после неё, и каждые две недели вы получаете не статус-отчёт, а сборку, которую можно поставить себе на телефон. Единственное правило, от которого мы не отступаем: **инварианты идут раньше кода фич.** Модель состояния, тема, таксономия ошибок, CI-пайплайн и стратегия тестирования решаются на первой неделе, потому что каждый экран, написанный потом, либо им следует, либо с ними воюет. Типичная сборка — 2–3 месяца для MVP и 3–5 месяцев для полноценного продукта, а релиз — это середина процесса, а не его конец. --- ### Этапы разработки Flutter-приложения одним взглядом Семь фаз в том порядке, в котором они стартуют. Это весь жизненный цикл разработки Flutter на одном экране. | Фаза | Что происходит | Ключевой результат | Кто участвует | Типичный срок | | --- | --- | --- | --- | --- | | **1. Дискавери и объём** | Идея превращается в конкретный упорядоченный бэклог; согласуется список отсечения | Бэклог, оценка с фиксированным объёмом, сроки | Основатель/продакт, ведущий разработчик, дизайнер | 3 дня – 2 недели | | **2. UX/UI-дизайн** | Сначала дизайн-система, потом сценарии и экраны под неё | Дизайн-система + кликабельный прототип | Дизайнер, продакт, ведущий разработчик | 2–4 недели (внахлёст со сборкой) | | **3. Архитектура и фундамент** | Модель состояния, структура папок, таксономия ошибок, CI/CD, стратегия тестов | Работающий скелет приложения, зелёный пайплайн, ADR | Ведущий разработчик, бэкенд-разработчик | 1–2 недели | | **4. Итеративная разработка** | Вертикальные срезы фич, демо каждый спринт | Устанавливаемая сборка каждые 1–2 недели | Разработчики, дизайнер, продакт | 4–16 недель | | **5. QA и тестирование** | Widget-, golden-, интеграционные тесты и тесты на реальных устройствах | Набор тестов в CI + фаза стабилизации | Разработчики, QA, тестировщики со стороны клиента | Непрерывно + 1–2 недели стабилизации | | **6. Релиз** | Материалы для сторов, подача, ревью, поэтапная раскатка | Живое приложение в App Store и Google Play | Ведущий разработчик, продакт, аккаунты клиента | 1–2 недели | | **7. После запуска** | Мониторинг, разбор падений, итерации по реальному использованию | Регулярный цикл релизов, crash-free rate | Разработчики, продакт | Постоянно | Это диапазоны, и они перекрываются — календарь короче, чем сумма строк. Куда на реальном проекте уходят недели, разбираем в парном материале: [сколько времени занимает разработка Flutter-приложения](https://ru.nerdy.pro/blog/how-long-to-build-a-flutter-app). Сколько стоит каждая фаза — в [стоимости разработки Flutter-приложения в 2026](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026). По структуре это тот же процесс разработки мобильного приложения, который ведёт любая вменяемая команда, — эти фазы придумал не Flutter. Flutter меняет экономику внутри них: одна кодовая база для дизайна, одна для тестов, одна для релиза, и слой UI, который живёт в коде, а не в стилях. Именно последнее и делает фазы дизайна и архитектуры здесь весомее, чем где-либо ещё. --- ### 1. Дискавери и определение объёма Дискавери — это фаза, на которой идея превращается в конкретный упорядоченный список того, что надо построить. Это не воркшоп со стикерами. Это ведущий разработчик и дизайнер, которые допрашивают приложение до тех пор, пока у каждого экрана не появится причина существовать. Из неё выходят три вещи: - **Упорядоченный бэклог.** Не «управление пользователями», а «вход через Apple, вход по почте, сброс пароля, удаление аккаунта». Размытые пункты — это место, где умирают оценки. - **Список отсечения.** Фичи, которых *нет* в первой версии, записанные и согласованные. Самый ценный артефакт всего процесса и тот, которому клиенты сопротивляются сильнее всего. - **Оценка с фиксированным объёмом и сроками**, выведенная из бэклога, а не из ощущения «ну, приложение как приложение». Именно здесь на самом деле определяются бюджет и сроки — поэтому скомканное дискавери — самый дорогой способ сэкономить неделю. Когда вы описываете «простой трекер тренировок», а мы возвращаем бэклог на 40 экранов, этот разрыв — не накрутка: онбординг, авторизация, настройки, удаление аккаунта, платежи, пуши и офлайн всегда были в том приложении, которое вы описали. Как объём складывается в цифру — в [разборе стоимости](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026). **Где ломается:** дискавери с тем, кто не может принимать решения. Если каждый ответ уходит на согласование в комитет, дискавери растягивается с недели до четырёх, а оценка строится на песке. --- ### 2. UX/UI-дизайн UX/UI-дизайн — это фаза, на которой определяется визуальный язык приложения и язык взаимодействия. Начинается он с системы, а не с экранов. Первый результат — **дизайн-система**: палитра со светлым и тёмным вариантами, шкала типографики, шкала отступов, состояния компонентов (обычное, нажатое, недоступное, загрузка, ошибка) и словарь взаимодействий: как *это* приложение показывает загрузку, как показывает пустой список, как подтверждает необратимое действие. Экраны рисуются уже под эту систему. Такой порядок — не про эстетику, а про скорость сборки. Flutter отрисовывает каждый пиксель из кода на Dart, и зрелая дизайн-система ложится на `ThemeData` почти один в один: подключается один раз, а каждый следующий экран собирается из уже существующих компонентов. Без неё получается то, что мы описываем в материале [почему AI-агенты буксуют на Flutter](https://ru.nerdy.pro/blog/ai-agents-struggle-with-flutter): сорок экранов, каждый из которых работает, но которые не складываются в одно приложение, цвета захардкожены в каждом месте вызова, а ребрендинг стоит диффа на несколько сотен файлов. Мы сразу проектируем и под те ограничения, которые позже ломают Flutter-вёрстку, — длинные строки во второй локали, масштаб текста 200%, маленькие экраны на 320pt. Поймать это в Figma стоит минут; поймать на QA стоит редизайна. На выходе фазы — дизайн-система и кликабельный прототип, ещё до первой строки кода фич. **Где ломается:** дизайн по кругу согласований. Три раунда «а покажите ещё вариант» на главном экране сдвигают все последующие фазы, потому что нельзя вести разработку по цели, которая всё время сдвигается. --- ### 3. Архитектура и фундамент Архитектура — это фаза, на которой принимаются решения, от которых зависит каждый последующий экран, — до того, как появился хоть один экран. На практике это значит, что одну-две недели мы строим приложение, которое ничего не делает. Эти две недели определяют следующие полгода. Что решается: - **Управление состоянием.** Один выбор, применённый везде. Sealed-классы состояний, чтобы недопустимые комбинации — загрузка *и* ошибка одновременно — были непредставимы, а не просто маловероятны. - **Структура папок и границы модулей.** Где живёт фича и что ей можно импортировать. - **Таксономия ошибок.** Именованные режимы отказа — сеть, протухшая авторизация, валидация, отклонённый платёж, конфликт — вместо одного «Что-то пошло не так», после которого любой баг невоспроизводим. - **CI/CD.** Зелёный пайплайн с первого дня: `flutter analyze` с линтами, поднятыми до ошибок, `flutter test`, артефакт сборки на каждый коммит, Fastlane, подключённый к TestFlight и внутреннему треку Google Play. - **Стратегия тестирования.** Какие слои покрываются widget-тестами, какие экраны — golden-тестами, какой бюджет на кадр. Мы называем это инвариантами, и они идут первыми, потому что дёшево их потом не встроишь. «Следуй существующему паттерну» — дешёвая инструкция и для человека, и для агента, но только когда есть на что показать. В репозитории без инвариантов каждый изобретает свои, и получается та самая археология, за [аудит](https://ru.nerdy.pro/services/ai-code-audit) которой нам платят два года спустя. Фаза заканчивается работающим скелетом, зелёным пайплайном и короткими записями решений по тем вопросам, которые следующий инженер иначе будет обсуждать заново. --- ### 4. Итеративная разработка Итеративная разработка — это фаза, на которой фичи собираются и выкатываются небольшими демонстрируемыми инкрементами, а не выдаются одним куском в конце. Рабочий процесс разработки на Flutter здесь строится на **вертикальных срезах фич**: одна фича, сделанная насквозь — UI, состояние, интеграция с API, обработка ошибок, тесты, — вместо «сначала все экраны», а потом «вся обвязка». Срез, который можно открыть на телефоне, говорит вам правду. Папка несвязанных экранов — нет. Ритм — демонстрируемый инкремент каждые одну-две недели, на вашем устройстве через TestFlight или внутренний трек Google Play. Не скриншот и не запись экрана, а приложение у вас в руках, которое можно дать коллеге. Ревью — про **решения, а не про строки**. Это та же модель состояния, что и в остальном приложении? Оформление взято из темы? Это значение выводится один раз или уже в третий? Эта строка не разъедется на русском? Агенты пишут заметную долю кода, а наши инженеры отвечают за эти решения — [так мы и строим](https://ru.nerdy.pro/blog/building-with-ai): построчное ревью сгенерированного кода не масштабируется, ревью на уровне решений — да. Параллельно продолжают идти дизайн и QA, а вместе с ними и бэкенд — поэтому систему вроде [слоя реалтайм-котировок в ExtraETF](https://ru.nerdy.pro/blog/extraetf-realtime-fintech-flutter-go) можно строить и нагрузочно тестировать, пока Flutter-клиент ещё собирает экраны на моках. **Где ломается:** молчаливые спринты. Если проходят две недели, а вы не открыли сборку, цикл разорван — и о недопонимании вы узнаете на месяц позже. --- ### 5. QA и тестирование QA — это слой, который идёт от первого среза фичи до последнего, а не фаза, приклеенная в конце: автоматические проверки в CI на каждый коммит плюс фаза стабилизации перед релизом. Что гоняется в CI на каждый коммит: - **Widget-тесты** на поведение: кнопка блокируется на время отправки, ошибка гаснет при повторном вводе, список подгружается на нужном оффсете. - **Golden-тесты** на внешний вид — самый выгодный тип тестов во Flutter, потому что golden превращает вопрос «а не сломал ли редизайн пустое состояние на 320pt в тёмной теме при масштабе текста 200%?» в проверку, которая проходит за секунды, вместо вопроса, который никто не задаёт. - **Интеграционные тесты** на сценарии, которые при поломке стоят денег: регистрация, оформление заказа, оплата. - **Регрессионный тест на каждый исправленный баг**, чтобы он не вернулся. То, что не может машина, делает человек на живом железе: приложение при масштабе текста 200%, в тёмной теме, на обоих языках, на маленьком старом Android-телефоне, с придушенной сетью, а потом с обрывом посреди запроса. Десять минут такого находят целый класс дефектов, о которых ни одна статическая проверка никогда не сообщит. И мы держим **бюджет кадра** — 16,6 мс при 60 fps, проверяется на profile-сборках. Список, который начинает дёргаться после того, как кто-то добавил тень и `Opacity` внутрь билдера элемента, — это регрессия, которую не ловит ни один юнит-тест и которую чувствует каждый пользователь. --- ### 6. Релиз Релиз — это фаза, на которой приложение проходит подачу в сторы, ревью и раскатку. Это 1–2 недели работы, которые в большинстве планов заложены как один вечер. **Материалы и метаданные для сторов** — скриншоты во всех требуемых размерах, описания, ключевые слова, privacy nutrition labels, декларации по безопасности данных. Удаление аккаунта обязано быть внутри приложения; оба стора это требуют. **Реальность ревью.** Ревью Apple обычно занимает 24–48 часов, но «обычно» не значит «гарантированно», и категории отказов предсказуемы: нет удаления аккаунта, стена авторизации без демо-доступа, неполная декларация приватности, платежи в обход внутренних покупок. Для тех, кто выпускает много брендированных приложений, правило 4.2.6 — отдельная дисциплина, и [что реально проходит ревью](https://ru.nerdy.pro/blog/white-label-app-platform-flutter) мы разбирали на примере целого флота приложений. Ревью Google обычно быстрее, но новый аккаунт разработчика может провисеть в расширенной проверке несколько дней. **Поэтапная раскатка.** Мы выкатываем на 10% в Google Play и смотрим на crash-free rate, прежде чем расширять; phased release в App Store делает то же самое. Плохая сборка, пойманная на 10%, — это испорченный вечер. Она же на 100% — испорченная неделя. **Автоматизация.** Fastlane собирает, подписывает и загружает в оба стора — включая мультииздательские схемы, где каждый бренд выходит под своим аккаунтом. Ручной релиз — это то место, где в 23:00 неправильно набирают код версии. --- ### 7. После запуска и поддержка Поддержка после запуска — это фаза, на которой приложение встречается с реальными пользователями и продолжает меняться. Запуск — её начало, а не конец проекта. Реальное использование мгновенно вскрывает то, что не поймает ни один тест: оболочку Android от вендора, которая ломает пуши, обновление ОС, которое объявляет API устаревшим, падение на устройстве, которого у вас не было. Поэтому процесс продолжается: - **Мониторинг** — отчёты о падениях, трассировки производительности и аналитика по значимым сценариям: ежечасно первые 48 часов и еженедельно дальше. - **Итерации** — тот самый бэклог, который вы отсекли на дискавери, переприоритизированный под то, что люди делают на самом деле. - **Поддержка** — ежегодные релизы ОС, обновления SDK, изменения правил сторов, ротация сертификатов и ключей API. Flutter и его плагины двигаются; приложение, которое не трогали год, не стабильное, а протухшее. Закладывайте бюджет на продукт, а не на проект. Приложение, которое выпустили и бросили, медленно едет обратно к переписыванию с нуля. --- ### Как мы держим это в графике Процессы не проваливаются драматично. Они сползают по несколько дней за раз. Четыре вещи предотвращают большую часть этого, и у каждой есть известный режим отказа. **Жёсткий объём, записанный на бумаге.** Список отсечения с дискавери — это контракт со сроками; новые идеи уходят в список v2, и мы говорим об этом вслух. *Ломается, когда* список никто не защищает: каждая «мелкая добавка» действительно мелкая, а двадцатая — это месяц. **Решительный заказчик и внятный ритм.** Один человек, который может согласовать экран в тот же день, когда его увидел, еженедельный созвон и сборка для установки. *Ломается, когда* решения идут через комитет. На шестинедельной сборке неделя задержки решений — это 20% перерасхода ещё до того, как разработчик сделал что-то не так. **Инварианты, заданные сразу.** Причина, по которой наш третий месяц стоит примерно столько же, сколько первый. *Ломается, когда* дедлайн соблазняет пропустить первую неделю и сразу взяться за экраны. **Короткие циклы обратной связи.** Устанавливаемые сборки каждые одну-две недели, QA в CI, демо, которое вы правда открываете. *Ломается, когда* клиент замолкает: цикл работает, только если на другом конце кто-то есть. И честное: **наши оценки ошибаются в одну сторону.** Сильнее всего они промахиваются на интеграциях. Любой сторонний SDK выглядит в презентации как работа на вечер и превращается в двухнедельную отладку пограничных случаев на Android 10. Мы закладываем на это запас, а не делаем вид, что он не понадобится, — и если фаза поедет, вы услышите об этом на той же неделе, а не на дедлайне. --- ### Частые вопросы #### Какие шаги нужны, чтобы создать Flutter-приложение? Шагов семь: дискавери и определение объёма, UX/UI-дизайн, архитектура и фундамент, итеративная разработка, QA и тестирование, релиз и поддержка после запуска. Дискавери превращает идею в упорядоченный бэклог и фиксированный объём, дизайн даёт дизайн-систему и прототип, а архитектура задаёт модель состояния, обработку ошибок, CI-пайплайн и стратегию тестирования до того, как написана хоть одна строка кода фич. Дальше разработка собирает фичи вертикальными срезами, QA идёт параллельно, а не после, и завершается всё релизом в сторы и постоянной поддержкой. #### Как устроен процесс разработки на Flutter? Он устроен как набор перекрывающихся циклов обратной связи, а не как линейный водопад. Дизайн стартует до окончания дискавери, инженерный фундамент закладывается, пока дорисовываются последние экраны, а QA идёт непрерывно в CI, а не отдельной фазой в конце. Клиент получает устанавливаемую сборку каждые одну-две недели — именно этот цикл ловит недопонимание, пока его ещё дёшево исправить. Типичный проект — два-три месяца на MVP и три-пять месяцев на полноценный продукт. #### Что делается в первую очередь при разработке приложения? Первым идёт дискавери: превращение идеи в конкретный упорядоченный бэклог с явным списком того, чего в первой версии не будет. Бюджет и сроки на самом деле определяются здесь, а не позже, потому что всё дальнейшее — это исполнение уже принятых решений. На инженерной стороне первым строится не фича, а фундамент — модель состояния, структура папок, таксономия ошибок, CI-пайплайн и стратегия тестирования, — потому что дёшево встроить это позже, когда уже есть сорок экранов, не получится. #### Как тестируют Flutter-приложения? QA идёт непрерывно, а не отдельной фазой в конце. Widget-тесты покрывают поведение, golden-тесты ловят визуальные регрессии, сравнивая отрисованные экраны с эталонными изображениями, а интеграционные тесты закрывают сценарии, поломка которых стоит денег. Сверх этого инженеры проверяют приложение на реальных устройствах при масштабе текста 200 процентов, в тёмной теме, на всех поддерживаемых языках и при придушенной или оборванной сети. Время кадра на profile-сборках сверяется с бюджетом 16,6 мс, потому что подтормаживания не ловит ни один юнит-тест, а чувствует их каждый пользователь. #### Сколько занимает каждая фаза Flutter-проекта? Дискавери занимает от трёх дней до двух недель, дизайн — две-четыре недели, архитектура и фундамент — одну-две недели. Итеративная разработка идёт от четырёх до шестнадцати недель в зависимости от объёма, QA работает непрерывно плюс одна-две недели стабилизации. Релиз занимает одну-две недели вместе с материалами для сторов, ревью и поэтапной раскаткой. Фазы сильно перекрываются, поэтому календарь короче суммы этих диапазонов — обычно два-три месяца на MVP и три-пять на полноценный продукт. #### Что происходит после запуска Flutter-приложения? Запуск — это начало продукта, а не конец проекта. Первые 48 часов уходят на наблюдение за отчётами о падениях, трассировками производительности и процентом поэтапной раскатки, прежде чем её расширять. Дальше работа превращается в итерации по реальным данным использования плюс поддержку: ежегодные релизы iOS и Android, обновления Flutter и плагинов, изменения правил сторов, ротация сертификатов и ключей API. Приложение, которое выпустили и в которое больше ничего не вкладывают, не стабильное — оно тихо едет в сторону переписывания с нуля. #### Как agile применяется в разработке на Flutter? Он применяется как короткие циклы обратной связи, а не как набор церемоний. Работа режется вертикальными срезами фич — от экрана до бэкенда, — поэтому каждый инкремент можно установить и потрогать, а не посмотреть на папку несвязанных экранов. Спринты длятся одну-две недели, и каждый заканчивается сборкой на устройстве клиента через TestFlight или внутренний трек Google Play. Намеренно вне agile остаётся архитектура: модель состояния, тема, таксономия ошибок и стратегия тестов задаются сразу, потому что менять их, когда уже есть сорок экранов, — это переписывание, а не спринт. --- ### Давайте определим объём вашей сборки Вот так и создаются Flutter-приложения, когда процессом действительно кто-то управляет. Если вы выбираете подрядчика, процесс и есть продукт: скриншоты вам покажет кто угодно. Выйдет ли приложение в срок, решает то, стоит ли за ним настоящая машина — список отсечения, который кто-то защищает, инварианты, заданные на первой неделе, сборка у вас на телефоне каждые две недели и тесты, которые проходят раньше, чем на код посмотрит человек. - **Запишитесь на дискавери-звонок**, и мы прогоним вашу идею через первый шаг: [напишите нам](https://ru.nerdy.pro/contact). На выходе — упорядоченный бэклог, список отсечения и оценка с фиксированным объёмом, а не вилка, в которой можно спрятаться. - **Посмотрите предложение** — команда, стек и тарифы на странице [разработки Flutter-приложений](https://ru.nerdy.pro/services/flutter-app-development). - **Уже есть сборка?** Если этот процесс до нас вёл кто-то другой и вёл плохо, [аудит кода](https://ru.nerdy.pro/services/ai-code-audit) покажет, во сколько обойдётся починка на месте — почти всегда дешевле, чем переписывание, которое вам предлагают. Расскажите, что вы строите и где застряли, а мы честно скажем, какие фазы пройдут легко, а какие будут болеть. --- *Дима — ведущий Flutter-разработчик [Nerdy Production](https://ru.nerdy.pro/), Flutter-first агентства, которое доводит приложения от дискавери до App Store в финтехе, ритейле и AI-продуктах.* ## Почему AI-агенты буксуют на Flutter: 7 пробелов, которые закрывает опыт https://ru.nerdy.pro/blog/ai-agents-struggle-with-flutter **Коротко.** Это не аргумент против того, чтобы AI писал Flutter. Агенты пишут большую часть нашего кода, и мы [осознанно выстроили процесс вокруг этого](https://ru.nerdy.pro/blog/building-with-ai). Это аргумент о том, что происходит, когда ими никто не управляет. Мы выпускаем Flutter вместе с агентами — и делаем аудит Flutter, который агенты написали без присмотра. Между этими двумя вещами лежат одни и те же семь пробелов. Агент без присмотра **пересчитывает производные данные** везде, вместо того чтобы держать единый источник правды; **не пишет ни тестов, ни бенчмарков**, поэтому единственным барьером остаётся код-ревью, а регрессии свободно уезжают в прод; **сваливает сложную асинхронность в пирамиды из if и switch поверх разрозненных флагов** вместо стрима; **держит дизайн-токены константами в `constants.dart`** вместо `ThemeData`, который фреймворк уже даёт; **изобретает новый UX-язык на каждом экране**, потому что не видит приложение; **склеивает строки** там, где правила плюрализации ICU обязательны; и **упрощает равномерно**, сглаживая ровно те части домена, где сложность как раз и нужна. Ничего из этого не является проблемой Flutter, и ничего из этого не делает код бесполезным. Это значит, что решения уровня всей системы по-прежнему наши. --- ### Почему Flutter наказывает за это сильнее большинства стеков Каждая из этих ошибок встречается на любом языке. Flutter просто делает их громче — по четырём структурным причинам. **Интерфейс — это код, до самого низа.** Нет стилей, живущих снаружи исходников и удерживающих единообразие, нет каскада, нет общего CSS-файла, который второй разработчик вынужден заметить. Каждый пиксель — это выражение на Dart. Если единообразие не закреплено *внутри* кода, его не закрепляет ничто. **Всё компилируется.** Flutter предлагает примерно десять разумных способов сделать что угодно, и все десять работают. Нет ошибки компиляции «здесь надо было взять тему», нет предупреждения «это третий раз, когда ты выводишь эту сумму», нет линта «этот экран грузится не так, как остальные тридцать девять». Обратной связи, которая нужна агенту, просто не существует — пока её кто-то не построит. **Агент никогда не видит кадр.** Он пишет экран, получает чистый `flutter analyze` и рапортует об успехе — на вёрстке, которая переполняется на 320pt, прячет кнопку отправки за клавиатурой и проваливает контраст в тёмной теме. Человек замечает все три за две секунды. У агента нет глаз, пока вы их не дадите. **В обучающих данных — десятилетие смешанного Flutter.** Фреймворк движется быстро, корпус — нет. Поэтому агенты уверенно пишут `RaisedButton` (удалён в 3.0), `WillPopScope` (заменён на `PopScope` после 3.12), `MediaQuery.of(context).textScaleFactor` (заменён на `TextScaler` в 3.16), `MaterialStateProperty` (переименован в `WidgetStateProperty` в 3.19), `Color.withOpacity` (объявлен устаревшим в пользу `withValues` в 3.27) и `ThemeData.accentColor`, который удалён в 3.7 и не существует уже несколько лет. Дело не в том, что модель ошибается, — она отвечает так, будто до сих пор актуальна версия Flutter, на которой уже никто не выпускает приложения, и половина этого до сих пор компилируется с предупреждением, которое никто не читает. Теперь сами семь. --- ### 1. Единый источник правды или пересчёт на каждом углу **Что делает агент.** Он выводит одно и то же значение в каждом месте, где оно понадобилось. Сумма корзины считается через fold на странице корзины. Потом ещё раз — в закреплённом футере оформления заказа. И третий раз — в сводке заказа, причём там скидка применяется до налога, а не после. По отдельности *ни один* файл не выглядит неправильным; просто три расходятся между собой, и расхождение невидимо ровно до момента, когда с покупателя списали не ту сумму. Во Flutter та же проблема выглядит хуже: состояние зеркалируется прямо в виджет. ```dart class _CartPageState extends State { double _total = 0; // копия правды №2 @override void initState() { super.initState(); _total = widget.items.fold(0, (sum, i) => sum + i.price * i.qty); } // ...и теперь _total никогда не обновится вслед за корзиной } ``` Классическая копия в `initState`. В демо работает, потому что в демо корзина не меняется после открытия страницы. Ломается при первом же изменении количества из боттом-шита. **Что делаем мы.** Значение выводится ровно один раз, в слое состояния, и все виджеты его читают. Если что-то можно вычислить из состояния — это не состояние. ```dart // Одно вычисление. Страница корзины, футер и сводка читают именно его. Stream get total => _cart.map( (c) => c.items.fold(Money.zero, (sum, i) => sum + i.subtotal), ); ``` Обратите внимание на тип `Money` вместо `double`. Агенты хватаются за `double` для денег постоянно, а [`0.1 + 0.2 != 0.3` перестаёт быть академическим примером, когда это чей-то баланс](https://ru.nerdy.pro/blog/floating-point-and-money). **Почему агент так делает.** Найти существующее вычисление стоит контекста; перегенерировать его инлайн не стоит ничего. Каждый запрос начинается с чистого листа, поэтому вопрос «а это уже где-то посчитано?» он структурно не может задать. Каждая копия локально разумна. Расхождение — это цена, которую вы платите. --- ### 2. Тесты и бенчмарки или надежда **Что делает агент.** Выкатывает фичу и останавливается. Ни виджет-тестов, ни голденов, ни бенчмарка. Определение готовности — «компилируется и экран выглядит правдоподобно по тому описанию, которое я сам написал». Единственным барьером качества остаётся код-ревью — и вот эту часть недооценивают. Ревью было узким местом ещё тогда, когда код писали люди с человеческой скоростью. Направьте на кодовую базу трёх агентов, и объём диффа, приходящего на этот барьер, вырастет на порядок, а количество людей, способных его отревьюить, останется равным одному. Ревью не масштабируется. Исполняемые проверки — масштабируются. **Что делаем мы.** Пишем тесты и бенчмарки *первыми* — не из соображений добродетели, а как ту самую обратную связь, которой агенту не хватает. - **Виджет-тесты** на поведение: кнопка блокируется на время отправки, ошибка сбрасывается при новом вводе, список подгружается на нужном скролле. - **Golden-тесты** на внешний вид. Это самое полезное, что вообще можно дать агенту, работающему с UI, потому что голден — ближайший аналог зрения. `matchesGoldenFile` превращает вопрос «сломал ли редизайн пустое состояние на 320pt в тёмной теме при масштабе текста 200%?» в проверку, которая проходит в CI за четыре секунды. Без голденов на этот вопрос отвечает только человек, открывший приложение, — то есть иногда. - **Бенчмарки** на то, что тихо гниёт. Бюджет кадра — 16,6 мс; список, который начинает дёргаться после того, как кто-то добавил тень и `Opacity` внутрь билдера элемента, — это регрессия, которую не ловит ни один юнит-тест. Тайминги кадров на profile-сборке, сверенные с бюджетом, ловят. - **Регрессионный тест на каждый исправленный баг**, чтобы агент, который его починил, не расчинил его тремя запросами позже. Паттерн, который стоит усвоить: **агент отлично умеет удовлетворять проверку, которую может запустить, и беспомощен там, где проверки нет.** Дайте ему `flutter test` — он будет итерироваться до зелёного. Не дайте ничего — он скажет, что уверен в результате. Что происходит с кодовой базой через год по второму сценарию, мы разобрали в [материале о находках аудита](https://ru.nerdy.pro/blog/ai-code-audit-findings). --- ### 3. Стримы или пирамида из флагов **Что делает агент.** Представляет асинхронное состояние как набор параллельных полей и потом ветвится по ним. ```dart bool _isLoading = false; String? _error; List _items = []; // build(): if (_isLoading) return const CircularProgressIndicator(); if (_error != null) return Text(_error!); if (_items.isEmpty) return const Text('Ничего нет'); return ListView(/* ... */); ``` Три поля, восемь представимых комбинаций, четыре из них бессмысленны. Что отрисуется, если `_isLoading` истинно *и* `_error` не пуст? То, в каком порядке случайно оказались `if`. Потом появляется поиск, и агент добавляет `Timer` для дебаунса, счётчик `_requestId`, чтобы отбрасывать устаревшие ответы, и проверку `mounted` перед каждым `setState` — самодельную и неочевидно сломанную реализацию `switchMap`. Рядом разрастается `utils.dart` со статическими хелперами, потому что логике структурно негде жить. Форма проблемы именно такая: **дело не в том, что агенты избегают switch, а в том, что они переключаются по флагам, которые сами же и выставляют, вместо типа, который может проверить компилятор.** **Что делаем мы.** Состояние — запечатанная иерархия, поэтому недопустимые комбинации непредставимы, а таймингом занимается стрим. ```dart sealed class SearchState {} final class Idle extends SearchState {} final class Loading extends SearchState {} final class Failed extends SearchState { Failed(this.error); final AppError error; } final class Loaded extends SearchState { Loaded(this.hits); final List hits; } // build() — компилятор требует обработать каждый случай return switch (state) { Idle() => const SearchHint(), Loading() => const AppSpinner(), Failed(:final error) => AppErrorView(error), Loaded(:final hits) when hits.isEmpty => const EmptyResults(), Loaded(:final hits) => HitList(hits), }; ``` И асинхронная часть — `debounceTime`, `switchMap` и `startWith` это расширения `Stream` из rxdart, а не из `dart:async`, — там, где в версии на флагах тихо возникают гонки: ```dart // Дебаунс и отмена — декларативны. Устаревший запрос не может победить, // потому что switchMap уже отменил его подписку. Stream get states => _query .debounceTime(const Duration(milliseconds: 300)) .switchMap(_search) .startWith(Idle()); ``` Добавьте новое состояние — скажем, `RateLimited` — и компилятор перечислит каждый `switch`, который нужно обновить. В версии на флагах добавление состояния означает найти все цепочки `if` руками и понадеяться. **Почему агент так делает.** Императивные флаги — самый частый паттерн в корпусе и самый удобный для генерации по строчке за раз. Стрим требует держать в голове всю машину состояний целиком, а это ровно то, что хуже всего даётся генератору с ограниченным контекстом. Это тот же сбой, который мы описывали как [callback hell в аудитах](https://ru.nerdy.pro/blog/ai-code-audit-findings), просто переодетый в Dart. --- ### 4. Знание фреймворка: `ThemeData` — это не файл констант Здесь знание фреймворка проявляется нагляднее всего. **Что делает агент.** Создаёт `constants.dart`, набивает его строчками `static const primary = Color(0xFF6750A4)`, а потом пишет стили инлайн в каждой точке вызова. ```dart Text( 'Баланс', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ) ``` В этой одной строке четыре отдельные проблемы, и очевидна только первая: 1. **Тёмная тема превращается в ручной `if`.** `AppColors.textPrimary` — это один цвет. Поддержка второй темы означает тернарник по `Theme.of(context).brightness` в каждой из 200 точек — что агенты потом и пишут, по одному экрану за раз. 2. **Ребрендинг — это дифф на 400 файлов.** Дизайнер, меняющий типографскую шкалу, должен править один файл. Здесь это поиск с заменой по всему приложению, и разъехавшиеся места (`fontSize: 15` на двух экранах, потому что та генерация округлила иначе) будут пропущены. 3. **Системное масштабирование текста ломает вёрстку.** Захардкоженные размеры вместе с захардкоженными отступами означают, что пользователь с масштабом 200% получит переполнение, а не перекомпоновку. 4. **`Theme.of(context)` перестаёт работать как переопределение.** Обернуть поддерево в другую тему — тёмная секция на светлом экране, брендированный чекаут внутри нейтрального приложения — не даст ничего, потому что никто в поддереве не спрашивает тему ни о чём. **Что делаем мы.** Дизайн-система живёт в `ThemeData`, один раз, в корне. ```dart MaterialApp( theme: AppTheme.light, // ColorScheme.fromSeed + TextTheme + темы компонентов darkTheme: AppTheme.dark, // ... ) // Каждый виджет спрашивает фреймворк, а не файл констант: Text('Баланс', style: Theme.of(context).textTheme.titleMedium) ``` Темы компонентов (`FilledButtonThemeData`, `CardThemeData`, `InputDecorationThemeData`) означают, что кнопка выглядит правильно *по умолчанию*, — и агент, делающий двадцатый экран, не сможет ошибиться, даже если попытается. Для брендовых токенов, под которые в Material нет слота, — палитра графиков, градиент, семантическая пара «рост/падение» в финтехе, — `ThemeExtension` держит их внутри того же механизма, а не рядом с ним: ```dart final class BrandColors extends ThemeExtension { const BrandColors({required this.positive, required this.negative}); final Color positive; final Color negative; // copyWith / lerp ... } // использование final brand = Theme.of(context).extension()!; ``` Тёмная тема после этого стоит одной дополнительной `ThemeData`, а не тернарника на виджет. В [Arcana](https://ru.nerdy.pro/portfolio/arcana) вся кастомная дизайн-система выражена именно так — поэтому правка дизайна там даёт дифф, который читается за одно ревью. **Почему агент так делает.** Константа *локально* корректна и мгновенно приносит удовлетворение: цвет появился, экран собрался. Протаскивание дизайн-системы через фреймворк окупается только на множестве экранов и второй теме — а окупаемость в масштабе всего приложения и есть та ось, которую оптимизатор в пределах одного запроса увидеть не может. --- ### 5. Глаза и десять лет практики **Что делает агент.** Строит сорок экранов, каждый из которых по отдельности оправдан, а вместе они ощущаются как приложение, собранное из сорока туториалов, — потому что в некотором смысле так и есть. Загрузка — это `CircularProgressIndicator` на главной, шиммер-скелет в ленте и кастомная анимация логотипа в профиле. Ошибки — `SnackBar` здесь, встроенный красный текст там и модальный `AlertDialog` на третьем экране. Подтверждение разрушительного действия — `AlertDialog` на одном экране и боттом-шит на следующем. У пустых состояний три разные иллюстрации, две разные интонации и одно, где просто написано «Пусто». Одни формы валидируются на каждое нажатие, другие — по сабмиту, одна — по потере фокуса. Главные кнопки на всю ширину в одном флоу и по содержимому — в другом. Каждый выбор по отдельности разумен. Проблема в том, что пользователь учит приложение один раз. Когда одно и то же действие выглядит по-разному в трёх местах, он перестаёт доверять своему пониманию того, что сейчас произойдёт, — и эта цена всплывает в обращениях в поддержку, а не в отчёте линтера. Дальше идёт класс дефектов, которые существуют только на экране: - `RenderFlex overflowed by 27 pixels` на любом устройстве с экраном уже, чем вообразил агент - области нажатия меньше 44/48dp — в тестах отлично, реальным большим пальцем промахиваешься постоянно - клавиатура, закрывающая поле, в которое печатают, потому что ничего не скроллится - текст, обрезанный при системном масштабе 200% - контент под вырезом камеры или за индикатором home, потому что `SafeArea` не был частью запроса - контраст, который проходит в светлой теме и проваливается в тёмной `flutter analyze` чист по каждому пункту. **Что делаем мы.** Определяем язык взаимодействия один раз — как выглядит загрузка, как всплывают ошибки, как подтверждаются разрушительные действия, когда валидируются формы, как ведёт себя навигация, что написано в пустом состоянии, — и дальше каждый экран реализует *его*. Десять с лишним лет практики в основном дают знание о том, какие паттерны у пользователя уже в пальцах, потому что их туда положили тысячи других приложений. Знакомое выигрывает у остроумного практически всегда, а проверяется это тем, что приложение открывают на живом устройстве, при реальном масштабе текста, в обеих темах и пальцем. **Почему агент так делает.** Он действительно не видит. Он генерирует правдоподобный экран по описанию, без памяти о тридцати девяти предыдущих экранах и без картинки того, что только что произвёл. Локальное правдоподобие — единственная доступная ему цель. --- ### 6. ICU не опционален, а склейка строк — не локализация Мы делаем приложения для рынков, где ошибка здесь видна в первом же предложении первого экрана. Агенты ошибаются здесь по умолчанию. **Что делает агент.** ```dart Text('$count файлов'); // 1 файлов Text('$count ${count == 1 ? "файл" : "файла"}'); // «умно» и всё равно сломано Text('Привет, ' + name + '!'); // порядок слов зашит в код Text('${d.day}.${d.month}.${d.year}'); // неверно для половины мира Text('\$${amount.toStringAsFixed(2)}'); // не тот разделитель, не то место ``` Тернарник для множественного числа — самый показательный, потому что выглядит так, будто разработчик подумал. Он зашивает предположение, истинное в английском и ложное почти везде: что у языка ровно две формы множественного числа. В русском таких категорий четыре (`one`, `few`, `many`, `other`) — 1 файл, 2 файла, 5 файлов, — поэтому тернарник неверен для большинства чисел на экране. В арабском их шесть. В японском одна, и любая плюрализация читается как поломка. Никакая вложенность тернарников это не чинит, потому что *правило* — это данные конкретного языка, а не условие, которое вы пишете. Дальше то же самое. Склейка зашивает английский синтаксис в исходники, и переводчик не может переставить части предложения. Десятичный разделитель, разделитель разрядов и позиция символа валюты — это данные локали (`1 234,56 €` против `$1,234.56`). Порядок компонентов даты — тоже данные локали. А в RTL-вёрстке `EdgeInsets.only(left: 16)`, который агенты пишут рефлекторно, кладёт отступ не с той стороны — там, где `EdgeInsetsDirectional.only(start: 16)` был бы корректен. **Что делаем мы.** Сообщения живут в ARB-файлах с ICU MessageFormat, из которых `gen_l10n` генерирует типизированные аксессоры: ```json { "unreadMessages": "{count, plural, =0{Нет новых сообщений} one{{count} новое сообщение} few{{count} новых сообщения} many{{count} новых сообщений} other{{count} новых сообщения}}", "@unreadMessages": { "placeholders": { "count": { "type": "int" } } } } ``` В английском ARB тот же ключ обходится формами `one`/`other` — в формате есть место под язык, а фреймворк выбирает правильную ветку по данным CLDR. `NumberFormat.currency` и `DateFormat.yMMMd(locale)` закрывают остальное, а направленные отступы держат RTL честным. Точка вызова превращается в `Text(l10n.unreadMessages(count))` — короче тернарника и при этом корректно. **Почему агент так делает.** Интерполяция строк — кратчайший путь к тексту на экране, и она *визуально корректна* на том языке, на котором был написан запрос. Сбой проявляется только в локали, о которой агента не спросили, и на числе, которое он не пробовал. --- ### 7. Знать, где упрощать — и где нельзя Последний пункт — про суждение, и его передать труднее всего. **Что делает агент.** Применяет упрощение равномерно, что даёт два противоположных сбоя в одной кодовой базе. Он **сглаживает сложность, которая была несущей**: ```dart try { await api.charge(order); } catch (_) { // проглочено: сетевая ошибка, отказ по карте и конфликт идемпотентности // стали одним событием, а ретрай теперь спишет деньги дважды } ``` Логика ретраев и идемпотентности схлопывается в один вызов, потому что «выглядело избыточным». Гонка при обновлении токена получает простой `await`, потому что мьютекс «вроде не использовался». Разные режимы отказа сливаются в одно «Что-то пошло не так» — ровно то сообщение, которое делает баг в платежах невоспроизводимым. Токены отмены выбрасываются, потому что на них не ссылался ни один тест. И он **переабстрагирует то, что было в порядке**: `AppButton` с четырнадцатью опциональными именованными параметрами и шестью булевыми флагами, generic-иерархия `BaseRepository` поверх трёх эндпоинтов, `AbstractBaseViewModel`, от которого нельзя отнаследоваться, не прочитав его целиком, и неизбежный `utils.dart`, куда сорок несвязанных статических функций приходят умирать. **Что делаем мы.** Сложность — это бюджет, и он тратится там, где домен действительно сложен: платежи, офлайн-синхронизация и разрешение конфликтов, обновление токенов авторизации, идемпотентность, всё, что касается денег и личности. Эти места остаются явными, многословными и намеренно скучными: каждый режим отказа назван, каждый ретрай осознан. Во всём остальном схлопываем агрессивно: пять почти одинаковых элементов списка становятся одним виджетом, экран настроек не получает никакого слоя абстракции, а репозиторий поверх одного эндпоинта — это просто функция. **Почему агент так делает.** «Какие части этого домена сделают кому-то больно, если окажутся неверными?» — это не та информация, которая есть в коде. Она берётся из продукта, из регуляторного контекста и из того, что вы когда-то были на разборе инцидента. Агент, оптимизирующий читаемые минимальные диффы, обращается с ретраем платежа и с переключателем в настройках одинаково — потому что синтаксически это одно и то же. --- ### Что такое оркестрация на самом деле Прочитайте семь пунктов вместе — и форма становится очевидной: каждый из них является **свойством всей системы**: единообразие, покрытие, модель состояния, дизайн-система, язык взаимодействия, модель локализации, бюджет сложности. Ни одно из этих свойств не может возникнуть по одному запросу за раз, потому что ни один запрос не видит целого. Это не изъян, который лечится промптом, — это и есть окно контекста. Поэтому работа инженера смещается вверх по стеку, и состоит она в основном из четырёх вещей. **Задать инварианты до того, как появится код.** Тема, модель состояния, настройка локализации, структура папок, таксономия ошибок. Как только это есть, у фразы «делай как в существующем коде» появляется на что указывать — а следовать уже лежащему в репозитории паттерну агенты умеют по-настоящему хорошо. Первые два дня определяют следующие полгода — поэтому в [нашем процессе разработки](https://ru.nerdy.pro/blog/flutter-app-development-process) под них выделена отдельная фаза. **Построить обратную связь, которой у агента нет.** Строгий `analysis_options.yaml` с линтами, поднятыми до ошибок. `flutter test` с виджет- и golden-покрытием. Бюджет времени кадра, проверяемый на profile-сборках. Каждая из этих вещей превращает вопрос суждения в проверку, которую агент может сам запустить и под которую может итерироваться, — а это единственный способ сделать его скорость преимуществом, а не источником проблем. **Ревьюить решения, а не строки.** Это та же модель состояния, что и во всём приложении? Это стилизовано из темы? Это то самое единственное вычисление значения? Эта строка переживёт русский язык? Построчное ревью выхлопа агентов не масштабируется и не будет масштабироваться; ревью на уровне решений — масштабируется, потому что решений мало. **Открыть приложение.** На устройстве, при масштабе текста 200%, в тёмной теме, на обоих языках, с придушенной сетью. Это десять минут, и это находит весь класс дефектов, о которых ни одна статическая проверка никогда не сообщит. Вот в чём реальная разница между Flutter, написанным агентом, и Flutter, написанным командой, [которая агентами управляет](https://ru.nerdy.pro/blog/building-with-ai). Не в коде — код часто нормальный. В рамках вокруг него. --- ### Частые вопросы #### Могут ли AI-агенты писать хороший код на Flutter? Могут — внутри кодовой базы, в которой уже есть структура: тема, модель состояния, настроенная локализация и тесты, под которые можно итерироваться. Чего они не могут, так это создать эту структуру, потому что каждый её элемент является свойством всей системы, а агент оптимизирует каждый запрос локально. Если есть паттерн, которому надо следовать, выхлоп агента действительно силён. Если есть пустой репозиторий и никаких рамок, получаются сорок экранов, каждый из которых работает, но которые не складываются в одно приложение. #### Почему сгенерированный AI код на Flutter выглядит по-разному на разных экранах? Потому что у агента нет памяти о ранее собранных экранах и нет способа увидеть приложение, которое он строит. Каждый экран генерируется как правдоподобный самостоятельный ответ, поэтому индикаторы загрузки, обработка ошибок, пустые состояния, диалоги подтверждения и момент валидации форм различаются от экрана к экрану. Каждый отдельный выбор оправдан; в сумме это читается как приложение, собранное из несвязанных туториалов, и пользователи это замечают, потому что приложение учат один раз. #### Почему AI-агенты хардкодят цвета и стили текста вместо ThemeData? Константа локально корректна и мгновенно приносит результат: цвет появился, экран собрался. Протаскивание дизайн-системы через ThemeData окупается только на множестве экранов и второй теме, а это ровно тот горизонт, который оптимизатор в пределах одного запроса увидеть не может. Цена приходит позже: тёмная тема превращается в ручное условие в каждой точке вызова, ребрендинг — в дифф на несколько сотен файлов, а системное масштабирование текста ломает вёрстку, потому что размеры заданы фиксированными числами вместо типографской шкалы. #### Чем плоха интерполяция строк для множественного числа во Flutter? Она зашивает предположение, истинное в английском и ложное почти везде: что у языка ровно две формы множественного числа. В русском четыре категории множественного числа, в арабском шесть, в японском одна. Условие вида count равен единице не выражает ни одну из них. ICU MessageFormat в ARB-файлах обрабатывает категории множественного числа по данным CLDR для каждого языка; то же касается форматирования чисел, позиции символа валюты, порядка компонентов даты и раскладки справа налево — ничто из этого не переживает склейку строк. #### Пишут ли AI-агенты тесты для Flutter-приложений? Не пишут, если не попросить. Сгенерировать фичу и сгенерировать тесты к ней — это два разных запроса, и второй обычно так и не случается, поэтому код-ревью остаётся единственным барьером качества ровно в тот момент, когда агенты кратно увеличивают объём приходящего на него кода. Для UI самое эффективное решение — golden-тесты: голден — ближайший аналог зрения для агента, он превращает вопросы вроде того, сломал ли редизайн пустое состояние в тёмной теме, в проверку, которая проходит в CI за секунды. #### Почему AI-агенты избегают стримов во Flutter? Императивные флаги — самый частый паттерн в обучающих данных и самый удобный для генерации по строчке за раз, тогда как стрим требует держать в голове всю машину состояний целиком. Поэтому агенты заводят параллельные булевы и nullable-поля и ветвятся по ним, из-за чего недопустимые комбинации становятся представимыми, а устаревшие ответы — возможными. Если моделировать то же самое запечатанным классом состояния, который читается из стрима, компилятор начинает требовать полноты разбора, а дебаунс и отмена становятся декларативными вместо самодельных. #### Нужно ли переписывать Flutter-приложение, собранное с помощью AI? Обычно нет. Фичи работают, продукт существует; не хватает соединительной ткани — единого источника правды для производных данных, слоя тестов и голденов, нормальной модели состояния для сложной асинхронности, темы вместо констант, единого языка взаимодействия и настоящей локализации. Всё это чинится на месте, и на месте это существенно быстрее, чем переписывать. Точечный аудит показывает, какой из семи пунктов обходится дороже всего и в каком порядке их закрывать. ### Если у вас Flutter-приложение, написанное агентом Если вы узнали свою кодовую базу в этом списке — это не признак того, что использовать AI было ошибкой. Так выглядит выхлоп агентов без присмотра на Flutter, каждый раз, и это чинится без переписывания. Мы так и работаем: агенты пишут большую часть кода, а наши инженеры отвечают за семь решений выше. Посмотрите, [как мы делаем разработку на Flutter](https://ru.nerdy.pro/services/flutter-app-development), прогоните существующую кодовую базу через [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit) или [запишитесь на бесплатную оценку](https://ru.nerdy.pro/contact) — и мы скажем, какой из семи пунктов обходится вам дороже всего. ## Как мы построили финтех-приложение реального времени на Flutter и Go https://ru.nerdy.pro/blog/extraetf-realtime-fintech-flutter-go Мы построили [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) — инвестиционное приложение реального времени для ETF и акций — на Flutter с бэкендом на Go для слоя рыночных данных. Оно вышло в 2022 году для Isarvest GmbH и до сих пор доступно в App Store и Google Play. Эта статья — тот инженерный разбор, который не помещается на странице портфолио: как на самом деле работает слой стриминга, как мы строим финансовые графики во Flutter и — раз уж честность здесь главное — где стек сопротивлялся нам и что мы изменили бы в 2026 году. Сразу оговорка про цифры. Мы не публикуем бизнес-метрики клиентов — число пользователей, выручку, удержание — и уже [писали о том, почему расплывчатые заявления о росте ничего не стоят](https://ru.nerdy.pro/blog/mobile-cost-reduction-case-studies). Показатели здесь — про *систему*: переиспользование кода, размеры буферов, частота обновлений, — а не про рынок клиента. ### Коротко о главном ExtraETF — это насыщенное графиками инвестиционное приложение, которое стримит живые цены множеству клиентов одновременно. Разработку определяли две задачи. Первая — **веерная рассылка рыночных данных в реальном времени**: доставить один поток цен каждому клиенту, подписанному на конкретный инструмент, с ограниченной частотой обновлений, не падая, когда соединения обрываются. Вторая — **рендеринг настоящих финансовых графиков во Flutter** — оси, сетка, несколько серий, перекрестие с протяжкой — достаточно эффективно, чтобы укладываться в бюджет кадра на телефоне. Архитектура — три слоя: вышестоящий источник рыночных данных, **WebSocket-сервис на Go**, который управляет соединениями и веерно рассылает обновления с обратным давлением, и **клиенты на Flutter**, которые потребляют поток и рисуют интерфейс. Go берёт на себя конкурентность; Flutter — единую кодовую базу для iOS и Android с полным контролем над рендерингом. Вот история в трёх предложениях; остальное — про то, как каждый слой оправдывает своё место. ### Что на самом деле требует инвестиционное приложение реального времени Уберите брендинг — и живое инвестиционное приложение окажется сложной системной задачей в финансовом костюме. Планку задают четыре требования: - **Живые цены приходят по push, а не по опросу.** Котировки меняются в течение торгового дня. Опрашивать REST-эндпоинт каждые несколько секунд — и слишком медленно для пользователя, и слишком дорого при масштабировании. Нужен push-канал. - **Много клиентов, один источник.** Вышестоящий поток читается один раз; цена инструмента должна дойти до каждого клиента, который сейчас за ним следит. Это задача веерной рассылки (fan-out), и именно на ней наивные реализации плавятся. - **Плотные интерактивные графики.** Линия цены, заливка области, несколько серий и оверлеи сравнения, перекрестие с протяжкой — всё на 6-дюймовом экране, всё в бюджете кадра. - **Планка надёжности финтеха.** Пользователь простит соцсети пропущенный кадр. Но не простит денежному приложению устаревшую цену, молча оборванное соединение или рывок при чтении графика. Переподключение, переподписка и корректность — не полировка, а сам продукт. Всё, что ниже, вытекает из этих четырёх пунктов. ### Почему Flutter и Go **Flutter для клиента.** Нам нужна была одна команда, выпускающая одну и ту же функциональность на iOS и Android в сжатые сроки, и нам нужна была *возможность владеть пикселями* — настоящий финансовый график — кастомный рендеринг, а не стандартный виджет. Flutter даёт и то, и другое: единую кодовую базу на Dart и холст, на котором можно рисовать напрямую. Переиспользование кода между iOS и Android составило около 99% — кодовая база на Dart переиспользуется целиком, и лишь несколько сотен строк платформенного нативного «клея» приходятся на биллинг, OAuth и пуши. Развёрнутый разбор этого компромисса — в статье [Flutter против нативной разработки в 2026](https://ru.nerdy.pro/blog/flutter-vs-native-2026). **Go для слоя WebSocket.** Сервис стриминга — это в первую очередь задача конкурентности. Модель Go — пара лёгких горутин на соединение плюс единый цикл рассылки — подходит под эту форму почти идеально: заблокированная горутина обходится в килобайты памяти, а не в целый поток ОС, а каналы делают логику рассылки читаемой, а не лабиринтом коллбэков. Цена одного соединения — одна-две припаркованные горутины и буфер, так что честный потолок — файловые дескрипторы, а не CPU. Честные компромиссы. Обработка ошибок в Go многословна, а WebSocket-сервис — сплошные краевые случаи: полуоткрытые соединения, медленные потребители, частичные записи — так что `if err != nil` там много. Flutter в 2022-м означал борьбу с фреймворком на нескольких деталях рендеринга и принятие того, что у части нативных SDK (особенно платежей) поддержка плагинов была сырее, чем у нативного приложения. Ни то, ни другое не стало непреодолимым препятствием. Но и то, и другое было реальным. ### Архитектура Система — это прямой конвейер, в середине которого один сервис делает всю сложную работу, — обычная архитектура Flutter с WebSocket. У неё три слоя, слева направо: - **Вышестоящий источник рыночных данных** — сырой поток цен, читается один раз. - **WebSocket-сервис на Go** — слой веерной рассылки в середине и единственный компонент, который общается с вышестоящим источником. Каждый клиент подключается сюда, а не к источнику напрямую. - **Клиенты на Flutter** на iOS и Android — каждый держит одно WebSocket-соединение, превращает входящий поток в состояние интерфейса и рисует его в живых ячейках цен и слое графиков. Цены текут слева направо — от источника в сервис, из сервиса к клиентам, — а подписки идут в обратную сторону: каждый клиент сообщает сервису, какие символы сейчас на экране, и обратно приходят обновления только по этим символам. Три механизма делают систему устойчивой, и живут они почти целиком в сервисе посередине: - **Маршрутизация подписок.** Клиенты не получают весь поток. Каждый клиент подписывается на символы на экране, сервис хранит набор подписчиков по каждому символу, и обновление доставляется только тем клиентам, кто запросил этот символ. При подписке сервис сразу повторяет последнюю известную котировку, так что свежий экран никогда не пуст. - **Переподключение и переподписка.** Мобильные соединения обрываются постоянно — туннели, уход в фон, переключение сетей. Клиент считает оборванный сокет нормой, переподключается после короткой паузы и *заново отправляет свои текущие подписки*, чтобы сервер заново построил состояние маршрутизации для этого клиента. - **Не перестраивать мир на каждый тик.** Ячейка цены обновляется перестройкой лишь одного виджета; график вообще не завязан на сырой поток тиков. Подробнее — ниже про оба случая. ### Сложная задача №1 — реальное время в масштабе В основе — классический хаб: одна горутина владеет рассылкой. У каждого соединения своя горутина чтения и своя горутина записи — писатель вычерпывает буферизированный канал и пишет в сокет, — а хаб хранит по каждому инструменту набор подписанных на него клиентов. Наивная альтернатива — синхронно писать каждому подписчику прямо внутри рассылки — заклинивает в тот момент, когда сокет одного клиента становится медленным, потому что все остальные ждут за ним. Это первое, что ломается. Решение стандартное, но его легко сделать неправильно: дать каждому клиенту **буферизированный канал отправки** и сделать рассылку **неблокирующей отправкой**. Если буфер клиента полон — он не успевает, и для живых цен правильный ход — не блокировать рассылку. ```go // Рассылаем обновление всем, кто подписан на символ. // Медленный клиент не блокирует остальных: если его буфер полон, // мы отключаем всё соединение, а не тормозим рассылку. func (h *Hub) broadcast(symbol string, update Update) { for _, c := range h.subscribers[symbol] { select { case c.send <- update: // буферизированный канал, неблокирующе default: // Буфер полон → клиент не успевает. Отключаем его; // он переподключится и переподпишется с чистым буфером. h.drop(c) } } } ``` Два решения превратили это из «работает в демо» в «работает на открытии рынка»: - **Обратное давление — состояние соединения, а не отдельного сообщения.** Буфер отправки намеренно глубокий — застопорившийся клиент может отстать на десятки тысяч сообщений, прежде чем что-то поддастся, — так что короткая заминка поглощается, а не карается. Но клиент, который постоянно переполняет буфер, *отключается*, а не тянется на устаревших данных. Он переподключается чисто. Буфер меняет память на запас прочности; это регулятор, и мы его выкрутили. - **Схлопывание происходит выше по потоку, до слоя сокетов.** Это честная часть, которую большинство статей про «реальное время» пропускают. Обновления схлопываются *до* рассылки — примерно одно обновление на инструмент в секунду, побеждает последнее значение, — так что активный символ никого не затапливает. Значит, это **не** стриминг каждого тика с задержкой меньше 100 мс; это ровный **темп ~1 обновление в секунду**. Ровно то, что нужно человеку, следящему за ценой, и то, что бережёт CPU и трафик клиента. Отдача от модели с горутинами видна именно здесь: цена простаивающего подписчика — припаркованная горутина и буфер, так что один узел держит куда больше простаивающих соединений, чем сервер с потоком на соединение. Настоящее ограничение всегда было в лимитах сокетов и файловых дескрипторов, а не в рантайме Go — под это вы и закладываете потолок дескрипторов. В продакшене один узел несёт тысячи одновременных соединений, оставляя запас на порядок. ### Сложная задача №2 — рендеринг финансовых графиков во Flutter ExtraETF насыщен графиками, и рендеринг настоящего финансового графика во Flutter — отдельная дисциплина. Вот как мы их строим. Для всего, что сложнее лёгкого спарклайна, мы строим график как **кастомный `RenderObject`**, а не `CustomPainter`. `RenderBox` с собственным `Element` позволяет обращаться с подписями осей, легендой и маркерами серий как с настоящими дочерними render-объектами со своей раскладкой — гораздо чище, чем вручную раскладывать всё на одном плоском холсте, и даёт точный контроль над тем, что происходит на этапе layout, а что на этапе paint. Именно это разделение — вся история производительности. Приёмы, которые держат его дешёвым: - **Геометрию — один раз, на layout.** Объекты `Path` и `Paint` для каждой серии строятся в `layout()` и кэшируются; `paint()` лишь рисует кэшированные пути. Дорогая работа — обход серии, проекция точек — происходит при изменении данных или размеров, а не на каждом кадре. - **Мемоизировать масштаб.** Отображение из пространства данных в пиксели (и прямоугольник графика) кэшируется и пересчитывается только при изменении входных данных, за сеттерами с проверкой равенства. - **Отсекать перерисовки у источника.** Вместо эвристики `shouldRepaint` сеттеры render-объекта сначала сравнивают — `listEquals` по списку серий — и вызывают `markNeedsLayout` / `markNeedsPaint` только когда реально изменилось что-то видимое. ```dart set series(List value) { if (listEquals(value, _series)) return; // ничего видимого не изменилось _series = value; markNeedsLayout(); // пути, масштаб и подписи пересобираются в layout(), затем одна перерисовка } ``` - **Тяжёлую подготовку данных — вне UI-потока.** Фильтрация длинной серии-бенчмарка до выбранного диапазона выполняется в изоляте через `compute()`, так что главный поток не стопорится, готовя то, что график нарисует. Единственный жест, который важен на финансовом графике, — **перекрестие с протяжкой**. Мы вешаем его на `HorizontalDragGestureRecognizer` — выбранный намеренно, чтобы график выигрывал арену жестов у свайпа родительского `PageView`/`TabBar`, а вертикальная прокрутка при этом продолжала работать. Протяжка двигает маркер и перестраивает лишь небольшой оверлей легенды/индикатора, с тактильным откликом; `setState` на дереве при этом не вызывается. Где Flutter сопротивлялся: самое интересное по затратам — путь протяжки. Движение перекрестия инвалидирует и `paint`, и `layout`, так что активная протяжка заново прогоняет `layout()` и перестраивает те кэшированные пути — кэш, который щадит обычные кадры, протяжку не щадит. Это то, что находишь только с открытым бюджетом кадра перед глазами, и туда идёт следующий раунд оптимизации. Одна намеренная «нефича»: нет ни пинч-зума, ни панорамирования окна. Смена временного диапазона **перезапрашивает данные и порождает новую серию**; это не преобразование внутри графика. Так график остаётся без состояния относительно вьюпорта, а слой данных — единственным источником правды. Это реальный компромисс, сделанный осознанно. Ничто из этого — не про погоню за бенчмарком. Бюджет кадра — **16,6 мс**, а полный финансовый график — оси, сетка, несколько серий, легенда, маркеры — это много для него. Именно поэтому работа с геометрией живёт в `layout()`, а `paint()` остаётся дешёвым. Отдельно: живые числа цен на экране обновляются на каждый тик — перестройкой лишь одного виджета, value-listenable вокруг ячейки цены, так что движущаяся цена никогда не перестраивает график или страницу. Напоследок одно финтех-специфичное правило, потому что рано или поздно на нём обжигаются все: **никогда не считайте деньги в плавающей точке.** Цены, проценты и суммы портфеля — целочисленные минорные единицы или десятичный тип, а не `double`. Мы написали [полное объяснение, почему `0.1 + 0.2 ≠ 0.3` важно для денег](https://ru.nerdy.pro/blog/floating-point-and-money) — в финансовом приложении это не абстракция. ### Слой платформенной интеграции Сервис стриминга и графики — это интересная инженерия. А слой платформы — то, куда на самом деле уходит *время*, потому что каждый пункт здесь — нативный SDK со своими краевыми случаями, которые кросс-платформенный фреймворк не может абстрагировать: - **Вход через Apple и OAuth от Google.** Счастливый путь быстр; краевые случаи — нет. Apple отдаёт имя и email пользователя *только при самой первой авторизации* — пропустите этот момент, и его больше нет — так что либо вы сохраняете их сразу, либо не получите уже никогда. Связывание двух провайдеров с одним человеком — отдельная небольшая задача проектирования. - **Пуш-уведомления для ценовых оповещений.** Доставить пуш «сработало ваше оповещение» — значит управлять жизненным циклом токена, показывать запрос разрешения в момент, когда пользователь действительно согласится, и проектировать полезную нагрузку так, чтобы она оставалась осмысленной, когда приложение в фоне или убито. - **Встроенные покупки и подписочные права.** Именно это отнимает больше всего времени в любом финтех-проекте. Уровни, пробные периоды, восстановление покупок между устройствами пользователя и сверка между чеком стора и вашим собственным состоянием прав. Правила Apple и Google различаются, и ни те, ни другие не прощают ошибок. Flutter не делает это сложнее, чем нативно — но и заметно легче тоже не делает. - **Диплинки на конкретные бумаги.** Ссылка должна открывать приложение сразу на нужном инструменте, в том числе когда на момент нажатия приложение не было установлено. Отложенный диплинкинг — по-настоящему хитрый случай, который стал сложнее после закрытия Firebase Dynamic Links; мы разобрали, [как делаем это теперь через App Clips и Play Install Referrer](https://ru.nerdy.pro/blog/deferred-deep-linking-app-clips-install-referrer). Приложение состоит из почти 70 экранов — онбординг, живые котировки, новости, списки наблюдения, графики с несколькими таймфреймами и управление подписками — и всё это из одной кодовой базы Flutter. ### Что мы сделали бы иначе в 2026 Проект 2022 года, пересмотренный в 2026-м, — полезная проверка на честность. Что хорошо состарилось: форма. Go для слоя рассылки и Flutter для клиента — ровно то, что мы выбрали бы и сегодня; у модели с горутинами и единого клиента с кастомным рендерингом аргументов только прибавилось. Что мы изменили бы: - **Перевести графики полностью на нативный рендеринг.** Строить график как кастомный `RenderObject` — а не опираться на более тяжёлые готовые решения — это то, к чему мы пришли, и то направление, в котором развиваем наши графики. Impeller усиливает эту ставку: кастомно отрисованные графики теперь куда предсказуемее, чем на старом пути Skia, с меньшими подтормаживаниями из-за компиляции шейдеров при первой отрисовке. Приложение с обилием графиков, начатое сегодня, опирается на это с первого дня. - **Сначала взять проверенный realtime-слой, а не писать свой.** Наш сервис на Go несложен, но управление соединениями, переподключение и рассылка — решённые задачи. Для нового проекта мы всерьёз взвесили бы управляемый или обкатанный realtime-слой и потратили сэкономленное время на доменную логику — а на собственный сервис на Go перешли бы, только если этого потребовала бы экономика или требования по задержке. - **Ввести сквозную типизацию контракта клиент-сервер.** Сообщения потока сериализовались вручную. Сегодня мы описали бы схему один раз и сгенерировали бы типы и для Go, и для Dart, чтобы переименование поля не могло молча рассинхронизировать обе стороны. Ничто из этого — не упрёк исходному проекту. Он вышел, он до сих пор живёт, и ключевые решения выдержали проверку. Просто вот что дают четыре года прогресса экосистемы. ### FAQ #### Может ли Flutter работать с финансовыми данными реального времени? Да. Flutter хорошо справляется с потоком рыночных данных, потому что работа с сетью и декодирование идут вне UI-потока, и обновиться нужно только виджетам, привязанным к изменившемуся значению. Ограничение редко бывает в самом Flutter, чаще — в том, как вы доставляете обновления. Схлопывайте тики выше по потоку до ограниченной частоты, чтобы не затопить клиента, и обновляйте минимально возможное поддерево виджетов на каждое изменение, чтобы интерфейс укладывался в бюджет кадра. #### Подходит ли Flutter для финтех-приложений? Flutter хорошо подходит для большинства финтех-приложений: одна кодовая база для iOS и Android, полный контроль над кастомным рендерингом графиков и зрелая экосистема плагинов для OAuth, пуш-уведомлений и встроенных покупок. Он подходит хуже только тогда, когда продукт зависит от эксклюзивного для платформы железа или от выпуска совсем новой функции ОС в день её выхода. #### Как рендерить производительные графики во Flutter? Для лёгкого графика достаточно CustomPainter. Для полноценного финансового графика мы строим кастомный RenderObject: он позволяет управлять осями, сеткой, легендой и маркерами серий как дочерними render-объектами, один раз построить и закэшировать объекты Path и Paint на этапе layout, мемоизировать масштаб и перерисовывать только когда реально меняются данные. Тяжёлую подготовку данных выносите из главного потока через compute(). Смена временного диапазона — это перезапрос данных, порождающий новую серию, а не преобразование панорамирования или зума внутри графика. #### Зачем использовать Go для WebSocket-сервиса? Go отображает каждое соединение в пару лёгких горутин, так что один инстанс может держать многие тысячи одновременных WebSocket-соединений с небольшим и предсказуемым потреблением памяти, ограниченным лимитами файловых дескрипторов, а не CPU. Его конкурентность на каналах делает веерную рассылку, то есть трансляцию одного обновления цены множеству подписчиков, простой для корректной реализации, включая обработку обратного давления, которую требуют медленные клиенты. #### Что такое WebSocket-сервис веерной рассылки? WebSocket-сервис веерной рассылки — это сервер, который держит по одному постоянному соединению на клиента и проталкивает одно и то же событие каждому подписанному клиенту по мере его поступления, вместо того чтобы каждый клиент опрашивал обновления сам. Он централизует управление соединениями, учёт подписок и контроль частоты, так что вышестоящий источник данных читается один раз и транслируется многим клиентам. #### Могут ли приложения на Flutter работать с подписками и встроенными покупками? Да. Приложения на Flutter интегрируются с биллингом App Store и Google Play через официальные плагины, покрывая уровни подписки, пробные периоды, восстановление покупок и серверную проверку чеков. Сложность не во Flutter, а в правилах сторов и краевых случаях прав, и они одинаковы независимо от того, построено приложение на Flutter или нативно. ### Строите что-то реального времени или финтех? Это ровно та работа, которой мы занимаемся. Если вы стримите живые данные мобильным клиентам, рендерите что-то тяжелее списка или подключаете подписки и OAuth без острых углов — мы это выпускали. Смотрите [страницу проекта ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) со скриншотами и деталями, загляните в [наши open-source пакеты для Flutter](https://ru.nerdy.pro/open-source) или почитайте про услугу [разработки приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development) и о том, как мы подходим к [разработке финтех-приложений](https://ru.nerdy.pro/services/flutter-app-development/fintech). Задумали проект? [Напишите нам](https://ru.nerdy.pro/contact) — расскажите, в чём сложность, а мы честно скажем, подходят ли под неё Flutter и Go. ## Как мы выпускаем 20+ брендированных приложений из одной Flutter-кодовой базы — и проходим Apple 4.2.6 https://ru.nerdy.pro/blog/white-label-app-platform-flutter **Коротко.** Да — можно выпускать много брендированных приложений из одной кодовой базы и проходить ревью App Store, но только если каждое приложение *по-настоящему различается контентом и поведением*, а не просто перекрашено новым логотипом. Критерий простой: **если два ваших приложения показывают одни и те же экраны с одними и теми же данными и меняется только брендинг, Apple сочтёт их дубликатами и отклонит по правилу 4.2.6; если же каждое приложение аутентифицируется как отдельный тенант и тянет свой контент, каталог, фичи и конфигурацию с сервера — это функционально разные продукты, которые проходят ревью.** Ловушка, в которую попадают шаблонные фабрики, — относиться к white-label как к рескину. А работает это благодаря слою «клиент — сервер», который делает каждое приложение содержательно разным в рантайме. Всё, что ниже, — как именно мы это строим: архитектура, стратегия подачи на ревью и экономика, в которой двадцатое приложение стоит малую долю от первого. Мы — Flutter-first агентство, и одна из построенных нами платформ обслуживает **15+ живых брендированных приложений из одной кодовой базы** ([платформа кэшбэка и лояльности](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform)). Это честная версия того, как это устроено, — включая ту часть, где ленивая реализация приводит к снятию приложений из стора. ### Для кого это Вы покупаете не *одно* приложение. Вы покупаете *приложения* — во множественном числе, и их нужно много. - **Агентства**, перепродающие мобильные приложения своим клиентам, — вы хотите подключить нового клиента и отдать ему брендированное приложение, не запуская каждый раз новый проект с нуля. - **Франчайзинговые сети**, где каждой точке или региону нужно собственное присутствие в сторе под своим брендом, хотя продукт под капотом один. - **Мультибренд-операторы**, ведущие портфель брендов, каждому из которых нужно приложение для клиентов. - **Платформы курсов, коучинга и сообществ**, выпускающие приложение под каждого автора или поток. - **Организаторы мероприятий**, которые поднимают приложение под событие или площадку и сворачивают его после. Общая форма запроса — «мне нужно 10, 50 или 500 приложений, и я не могу позволить себе строить и поддерживать каждое с нуля». Если это про вас, вам наверняка уже продавали «white-label» — и если вы обожглись, то почти наверняка потому, что вам продали рескин, а Apple это заметила. ### Почему большинство white-label приложений отклоняют: ловушка 4.2.6 **Правило App Store Review 4.2.6 — то самое правило, по которому Apple удаляет приложения, «созданные из коммерческого шаблона или сервиса генерации приложений», если их подаёт не тот бизнес, *для которого* приложение сделано; и то же правило она применяет, чтобы отклонять приложения, которые являются дубликатами или рескинами друг друга.** Замысел прост: стор не должен наполняться сотнями почти одинаковых приложений, не несущих самостоятельной ценности. Apple хочет, чтобы каждое приложение в сторе было настоящим продуктом, а не спамом с конвейера. Вот почему типичное white-label-предложение заходит прямо в эту ловушку. Шаблонная фабрика берёт один бинарник, меняет логотип, подбирает другую палитру, меняет название и подаёт сорок копий. Каждое из этих приложений: - показывает **те же экраны** в том же порядке, - отображает **тот же контент и данные**, - открывает **те же фичи**, - и нередко выходит с **почти одинаковыми скриншотами и метаданными**. Для ревьюера — и всё чаще для автоматических инструментов Apple — это сорок копий одного приложения. Дифференциация косметическая. Правило 4.2.6 существует ровно для того, чтобы это отклонять, и отказы жёсткие: приложения снимают спустя месяцы после релиза, аккаунты разработчиков помечают, целые флоты застревают в ревью. История, с которой приходит большинство обжёгшихся покупателей, именно такая — «мы выпустили шесть брендированных приложений, Apple одобрила четыре, а потом задним числом отклонила всю партию как дубликаты». Вывод, который из этого обычно делают, чаще всего неверен. Делают вывод: «нельзя выпускать много приложений из одной кодовой базы». Можно. Проблема никогда не была в общей кодовой базе. **Проблема была в том, что приложения — один и тот же продукт под разными вывесками.** ### Дифференциация, которая реально проходит ревью Это самая суть, поэтому сформулирую точно. Есть два слоя дифференциации. Первый делают почти все. Именно второй отличает настоящую платформу от шаблонной фабрики. **Слой 1 — визуальный white-label (необходимо, но недостаточно).** Каждое приложение получает свою тему (цвета, типографику, логотип, сплэш, светлую или тёмную схему), своё название, свой bundle identifier / application ID, свою иконку и свою карточку в сторе. Это база. Она делает приложения *разными на вид*. Сама по себе — это ровно то, что отклоняет 4.2.6. **Слой 2 — слой «клиент — сервер» (настоящая защита).** Здесь мы применяем white-label не только к *представлению* — мы дифференцируем *контент и поведение*. Каждое брендированное приложение в рантайме **аутентифицируется в бэкенде как собственный тенант** и тянет с сервера свой мир: - свой **контент** (реальные данные экранов, тексты, медиа, ленту), - свой **каталог** (товары, листинги, курсы, офферы — то, *о чём* приложение), - свои **данные** (пользователь в приложении бренда A никогда не видит данные бренда B; контекст бренда определяется для каждого запроса), - свой **набор фич** (feature-флаги по тенанту включают и выключают целые модули), и - свою **удалённую конфигурацию** (поведение, кампании, раскатка — всё управляется с сервера). Конкретное «до/после»: > **Рескин** показывает тот же каталог, ту же ленту, те же фичи — с новым логотипом сверху. > > **Наши приложения** тянут *другой* каталог, *другую* ленту и могут открывать *разные* фичи под каждого клиента — два приложения из одного и того же бинарного шаблона становятся по-настоящему разными продуктами в тот момент, когда стартуют и обращаются к серверу. Именно эта содержательная, серверная дифференциация делает каждое приложение настоящим самостоятельным приложением в глазах Apple. Ревьюер, открывая два наших приложения, видит не одно и то же дважды — он видит два продукта с разным контентом и, часто, с разными возможностями. Это и есть то, чего требует 4.2.6, и это невозможно подделать чистым рескином — потому что у рескина просто нет ничего другого, что он мог бы показать. > **Одна фраза, которую стоит запомнить** > > Визуальная тема делает приложение *разным на вид*. Слой «клиент — сервер» делает его *разным по сути*. Правило Apple 4.2.6 отклоняет первое, когда это всё, что у вас есть, и принимает приложения, которые действительно различаются контентом и поведением, — так что серверный слой не «приятное дополнение», а именно то, что даёт одобрение. ### Архитектура Вот пайплайн от начала до конца, шаг за шагом — от менеджера, создающего бренд, до подписанного приложения, попадающего в нужный аккаунт стора. 1. **Админ-панель (Nuxt).** Менеджер создаёт новый бренд — название, ассеты, тему и её светлую или тёмную схему, выбор фич, метаданные для стора — и отправка формы генерирует конфигурацию этого бренда. Инженер в процессе не участвует. 2. **Бэкенд / мультитенантный API.** Конфигурация попадает в бэкенд как новый тенант: его токены темы, его каталог, контент и данные, его feature-флаги и собственные учётные данные тенанта. Это «мир» тенанта — то, что приложение позже подтянет с сервера. 3. **Пайплайн сборки (build time).** Генерация конфига запускает сборку. Кастомный тулинг на Dart применяет *идентичность* бренда к Flutter-бинарнику — тема, название, bundle identifier, иконки — детерминированно переписывая конфигурацию сборки Android и iOS из конфига (никакой ручной правки `build.gradle` или `Info.plist` под каждое приложение). Затем [Fastlane](https://fastlane.tools) подписывает бинарники iOS и Android. 4. **Брендированное приложение (рантайм).** Каждое приложение — один и тот же Flutter-бинарный шаблон. При запуске оно аутентифицируется в бэкенде *как собственный тенант* и тянет свой контент, каталог, данные, набор фич и remote config — так что приложение №17 показывает другой мир, чем №3, хотя код у них один. 5. **Мультиаккаунтная выкатка.** Fastlane отправляет каждое подписанное приложение в нужную точку публикации — аккаунт App Store A, аккаунт App Store B, Google Play и так далее — так что бренды могут жить под отдельными аккаунтами разработчиков, а не под одним. Читайте это как два потока, встречающихся в приложении. **На этапе сборки** (шаги 1–3) пайплайн вшивает *идентичность* бренда в бинарник и подписывает его — это делает приложение отдельной карточкой в сторе. **В рантайме** (шаг 4) приложение аутентифицируется как свой тенант и тянет *контент и поведение* с бэкенда — это делает его отдельным продуктом. Сборка делает его отдельной карточкой; рантайм делает его отдельным продуктом. Разделение между тем, что фиксируется на сборке, и тем, что резолвится в рантайме, — это и есть весь замысел, поэтому вот он таблицей: | Слой | Различается на | Что меняется у каждого приложения | Почему это важно для 4.2.6 | | --- | --- | --- | --- | | **Идентичность бинарника** | Сборка | Название, bundle ID / application ID, иконки, сплэш | Каждое приложение — своя карточка в сторе под своим аккаунтом | | **Тема и брендинг** | Сборка + рантайм | Цвета, типографика, логотип, светлая/тёмная схема | Визуальный white-label — необходим, но сам по себе недостаточен | | **Аутентификация тенанта** | Рантайм | Учётные данные тенанта, контекст бренда | Каждое приложение — свой аутентифицированный клиент на сервере | | **Контент и каталог** | Рантайм | Данные экранов, листинги, лента, медиа, тексты | По-настоящему разный контент = самостоятельная ценность, которой хочет Apple | | **Набор фич** | Рантайм | Включённые модули, feature-флаги по тенанту | Приложения могут *вести себя* по-разному, а не только выглядеть | | **Remote config** | Рантайм | Поведение, кампании, поэтапная раскатка | Постоянное расхождение без пересборки под каждое изменение | Экономически это возможно благодаря Flutter. Строить такой флот нативно означало бы поддерживать кодовые базы на Swift *и* Kotlin, помноженные на каждый бренд, — неподъёмная ноша. Flutter сводит это к **одной кодовой базе на Dart, одной команде, всем платформам и брендам**, и это тот же фундамент, что и в нашей работе по [разработке на Flutter](https://ru.nerdy.pro/services/flutter-app-development), масштабированный с одного приложения до флота. Поскольку Flutter сам рисует каждый пиксель, тема бренда применяется одинаково на всех устройствах, а фикс, выпущенный один раз, попадает во все N приложений в следующем релизе. ### Аккаунты разработчиков и стратегия подачи на ревью Прохождение 4.2.6 — это не только про код. *Подача* тоже должна выглядеть как N реальных продуктов, потому что именно её ревьюер и видит. Самый явный тревожный сигнал для Apple — это **один аккаунт разработчика, публикующий сорок визуально похожих приложений**. Это почерк шаблонной фабрики, и он провоцирует ровно ту проверку на дубликаты, которой вы пытаетесь избежать. Поэтому стратегия аккаунтов — часть архитектуры, а не то, о чём думают в последнюю очередь: - **Отдельные аккаунты под бренд или клиента** — самая безопасная модель и часто попросту правильная: когда приложение сделано *для* вашего клиента (агентство перепродаёт ему, франчайзи, независимый партнёрский бренд), его следует публиковать под *его* аккаунтом. Правило 4.2.6 прямо это поощряет: приложения из шаблона допустимы, когда их подаёт бизнес, которому приложение служит. - **Один аккаунт при настоящей дифференциации** может работать для суббрендов одной компании, но планка по дифференциации контента и метаданных выше, потому что всё находится под одним издателем. - **Метаданные и скриншоты должны различаться у каждого приложения** — реальные описания под каждый бренд и скриншоты из *реального контента этого бренда*, а не один набор скриншотов с подменённым логотипом. Дублирующиеся маркетинговые ассеты помечают даже тогда, когда само приложение дифференцировано. Наш пайплайн поддерживает все три модели публикации и смешивает их под каждый бренд, потому что правильный ответ зависит от ваших партнёрских договорённостей и от того, *для кого* приложение юридически сделано, — мы подробно разбираем компромиссы на странице услуги [white-label разработка приложений](https://ru.nerdy.pro/services/whitelabel-app-development). ### Построить, купить или лицензировать У вас три честных варианта, и мы скажем, какой подходит, даже если это не мы. **Построить самому.** Вы владеете всем, и оно заточено под вашу модель. Но вы финансируете платформу — слой конфигурации, админ-панель, мультитенантный бэкенд, тулинг сборки на Dart, пайплайн Fastlane — ещё до выхода первого приложения, и берёте на себя риск 4.2.6 без набитых шишек. Это имеет смысл, когда мобильная платформа *и есть* ваш бизнес и вы годами будете держать вокруг неё инженерную команду. Если вы хотите строить сами, но вам не хватает Flutter-разработчиков, для этого есть [расширение команды](https://ru.nerdy.pro/services/team-augmentation) — наши инженеры в вашем репозитории, платформа остаётся вашей. **Купить шаблонную фабрику.** Самый низкий ценник, самое быстрое демо и самый высокий шанс отказа по 4.2.6, потому что рескины — ровно то, во что целится это правило. Если вам обещают «меняем логотип и цвета и подаём», вы покупаете отказ, а не избегаете его. У шаблонных билдеров есть своё место — внутренние инструменты, корпоративная дистрибуция, прототипы — но не публичный мультибренд-флот в App Store. **Лицензировать платформу или взять в партнёры команду, которая это уже выпускала.** Вы получаете архитектуру платформы и плейбук по подаче на ревью, не финансируя R&D и не набивая шишки на риске отказа лично. Плата за это — партнёр в контуре. Именно эту модель стоит выбирать большинству агентств и мультибренд-операторов: вы покупаете проверенный пайплайн, а не выясняете на собственном опыте, где всё ломается. Универсально правильного ответа нет. Есть *неправильный*: купить рескин и узнать о 4.2.6 из письма с отказом. ### Экономика: почему приложение 2…N дешёвое Причина, по которой эта модель существует, — кривая издержек, так что вот её честная форма. **Первое приложение несёт на себе платформу.** Первое приложение — на самом деле не «приложение»: это кодовая база, система тем и feature-флагов, мультитенантный бэкенд, админ-панель и автоматический пайплайн сборки и публикации. Это и есть инвестиция, и она приходится на самое начало. Грубый ориентир: разработка white-label платформы по деньгам попадает примерно в диапазон серьёзного кастомного проекта и растёт с размером флота и глубиной фич — тарифы на [странице услуги](https://ru.nerdy.pro/services/whitelabel-app-development), а общая механика стоимости — в статье [Стоимость разработки на Flutter в 2026](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026). **Приложения со второго по N-е кардинально дешевле**, потому что они разделяют всё, что стоило денег. Новое брендированное приложение — механически — это *конфигурация*: ассеты бренда, тема, выбор фич, метаданные стора и аккаунт разработчика. Ни нового проекта сборки, ни форка кодовой базы, ни второго раунда архитектуры. Предельная стоимость приложения N приближается к стоимости *заполнения формы и подготовки ассетов для стора* — это часы, а не пересборка. Грубая модель, без выдумывания ваших цифр: - **Суммарная стоимость N приложений ≈ стоимость платформы + (N × стоимость конфигурации на бренд).** - Стоимость платформы фиксирована и платится один раз. Стоимость на бренд мала и примерно постоянна. - Поэтому **средняя стоимость приложения падает с ростом N** — чем больше брендов вы выпускаете, тем ближе среднее к предельной стоимости конфигурации. Поверх этой кривой лежит структурная экономия Flutter: одна кодовая база под iOS и Android **примерно на 30–40% дешевле, чем два отдельных нативных приложения**, и эта экономия *умножается по всему флоту* — вы получаете её не один раз, а N раз. Про наши собственные цифры: мы выпустили **20+ брендированных приложений** из наших white-label кодовых баз, а на [одной платформе](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform) флот перешагнул **15+ живых приложений** из одной кодовой базы, где запуск следующего бренда почти бесплатен. ### FAQ #### Что такое white-label мобильное приложение? White-label мобильное приложение — это одно приложение, которое ребрендируется и переиздаётся как несколько отдельных, независимо брендированных приложений. У каждого своё название, логотип, цвета и карточка в App Store или Google Play, но все они работают на одной общей кодовой базе. Если всё сделано правильно, каждое брендированное приложение к тому же тянет свой контент, каталог и фичи с сервера в рантайме, так что приложения различаются тем, что показывают и делают, а не только тем, как выглядят. #### Что такое правило App Store 4.2.6? Правило App Store Review 4.2.6 — это правило Apple против приложений, созданных из коммерческого шаблона или сервиса генерации приложений, и против приложений, которые являются дубликатами или рескинами друг друга. Его цель — не давать стору наполняться спамом и почти одинаковыми приложениями без самостоятельной ценности. Приложения из шаблона допустимы, когда их подаёт тот бизнес, для которого приложение реально сделано, — например, публикуется под аккаунтом разработчика самого клиента или франчайзи, а не массово под одним издателем. #### Как избежать отказа по 4.2.6? Нужно сделать каждое приложение по-настоящему разным по контенту и поведению, а не только по виду. Каждое брендированное приложение аутентифицируется в бэкенде как собственный тенант и тянет свой контент, каталог, данные, набор фич и конфигурацию с сервера, так что два приложения из одной кодовой базы показывают разное и могут вести себя по-разному. Дифференцируйте и подачу — отдельные аккаунты под бренд или клиента там, где уместно, и уникальные скриншоты и метаданные под каждый бренд. Одна визуальная тема — ровно то, что отклоняет 4.2.6; содержательная, серверная дифференциация — то, что проходит. Гарантировать одобрение не может никто, но именно это существенно отличает приложения, которые проходят, от рескинов, которые нет. #### Можно ли и правда построить много приложений из одной кодовой базы? Да. Одна Flutter-кодовая база может стать любым числом брендированных приложений, потому что то, чем они различаются — брендинг, тема, контент, каталог, feature-флаги и метаданные стора — живёт в конфигурации вне кода, и каждое приложение резолвит свою конфигурацию и контент с сервера в рантайме. Фикс или фича, выпущенные один раз, попадают в каждое приложение в следующем релизе. Одна построенная нами платформа обслуживает 15+ живых брендированных приложений из одной кодовой базы, и предельная стоимость следующего приложения близка к нулю. #### Сколько стоит white-label платформа приложений? Первое приложение несёт стоимость платформы — общую кодовую базу, систему тем и feature-флагов, мультитенантный бэкенд, админ-панель и автоматический пайплайн сборки и публикации — так что стоит оно как серьёзная кастомная разработка. Каждое приложение после него кардинально дешевле, потому что новый бренд — это конфигурация, а не новый проект, и его предельная стоимость приближается к стоимости подготовки ассетов бренда и метаданных стора. Средняя стоимость приложения падает по мере запуска новых брендов. Одна кодовая база Flutter к тому же примерно на 30–40% дешевле, чем отдельные нативные приложения под iOS и Android, — и эта экономия умножается по всему флоту. #### Чем это отличается от шаблонного билдера приложений? Шаблонный билдер приложений выдаёт рескины: то же приложение с новым логотипом и цветами, показывающее те же экраны, данные и фичи. Это ровно то, что отклоняет правило App Store 4.2.6. Наш подход добавляет поверх общей кодовой базы слой «клиент — сервер», так что каждое приложение аутентифицируется как собственный тенант и тянет с сервера по-настоящему разный контент, каталог, данные и фичи. Приложения — функционально разные продукты, а не косметические копии, поэтому они выдерживают ревью там, где приложения шаблонных фабрик снимают с публикации. ### Обсудить с нами white-label проект Если вам нужно много приложений, а не одно, интересный вопрос не «справится ли Flutter», а «переживут ли эти приложения ревью и чего на самом деле требует такой пайплайн». Мы построили эту архитектуру, прошли пограничные случаи 4.2.6 и выпустили флоты, которые остались живыми. - **Записаться на демо платформы** — посмотреть, как админ-панель поднимает новое брендированное приложение, а пайплайн подписывает и публикует его: [напишите нам](https://ru.nerdy.pro/contact). - **Изучить предложение** — архитектура, модели публикации и тарифы на странице услуги [white-label разработка приложений](https://ru.nerdy.pro/services/whitelabel-app-development). - **Строите сами?** Мы посадим Flutter-инженеров в ваш репозиторий через [расширение команды](https://ru.nerdy.pro/services/team-augmentation) и оставим платформу в ваших руках. Расскажите, сколько брендов вы планируете и *для кого* эти приложения, и мы дадим честную оценку по модели, стратегии подачи и кривой издержек — а не питч шаблонной фабрики. --- *Илья Никсан — основатель и ведущий разработчик [Nerdy Production](https://ru.nerdy.pro/), Flutter-first агентства, которое строит white-label платформы и выпускает флоты брендированных приложений в финтехе, ритейле и лояльности.* ## Как нанять Flutter-разработчиков в 2026 году: своя команда, агентство или staff augmentation https://ru.nerdy.pro/blog/hire-flutter-developers-2026 **Коротко.** Есть три способа нанять Flutter-специалистов в 2026 году, и правильный зависит от того, что именно вы покупаете. **Нанимайте in-house, когда Flutter — ядро вашего продукта на годы вперёд и вы хотите владеть кодом и знанием. Нанимайте агентство, когда нужно быстро выпустить очерченный продукт с минимальными управленческими накладными расходами. Используйте staff augmentation, когда у вас уже есть команда, способная управлять инженерами, и не хватает только Flutter-мощности.** Правило в одну строку: *если мобильное приложение — это и есть бизнес, стройте in-house; если приложение нужно построить — нанимайте агентство; если построить быстрее — усильте свою команду.* Всё, что ниже, — детали за этой фразой: реальные ставки 2026 года, скрытые издержки и чек-лист для проверки того, кого вы нанимаете. Мы управляем Flutter-first агентством, размещаем инженеров в командах клиентов и воочию видели, как все три модели и срабатывают, и подводят. Это честная версия — включая те места, где ответ звучит как «не нанимайте нас». ### Три модели с высоты птичьего полёта | | In-house | Агентство | Staff augmentation | | --- | --- | --- | --- | | **Первоначальные затраты** | Самые высокие — гонорары рекрутеров, зарплаты, соцпакет, техника ещё до первой строки кода | Средние — очерченная стоимость проекта, предсказуемо | Низкие — платите за инженера, помесячно, быстрый старт/стоп | | **Время до продуктивности** | Самое долгое — 2–4 месяца на найм, потом ramp-up | Самое быстрое для целого продукта — команда уже собрана | Быстро — от нескольких дней до пары недель на инженера | | **Управленческие накладные расходы** | Высокие — на вас найм, удержание, рост, планирование спринтов | Самые низкие — разработкой управляет агентство | Средние — инженерами вы управляете сами, изо дня в день | | **Гибкость и масштабирование** | Низкая — нанимать и увольнять медленно и дорого | Средняя — масштаб вверх/вниз на границах контракта | Самая высокая — добавляйте и убирайте инженеров помесячно | | **Владение кодом и удержание знаний** | Лучшее — знание остаётся внутри компании | Слабее всего по умолчанию — знание может уйти при передаче | Сильное — код и контекст живут в *вашем* репозитории и команде | | **Когда подходит** | Flutter — ядро продукта, roadmap на годы | Очерченный продукт, фиксированный дедлайн, минимум внутренних ресурсов | У существующей команды не хватает Flutter-мощности прямо сейчас | | **Главный риск** | Медленно, дорого, и за плохой найм отвечаете вы | Lock-in и обрыв знаний при передаче | Управлять всё равно придётся: augmentation не автопилот | ### Сколько на самом деле стоит нанять Flutter-разработчика в 2026 году? Стоимость зависит больше от того, *где* и *как* вы нанимаете, чем от фреймворка. Вот диапазоны, которые мы видим на рынке в 2026 году. **Агентство (смешанная почасовая ставка, по регионам).** Это ставка «всё включено» за команду, где есть инженеры, проджект-менеджмент и обычно дизайн: - **Агентства из США:** 13,5–22,5 тыс. ₽/час - **Западная Европа (UK, Германия, Нидерланды):** 9–16,2 тыс. ₽/час - **Восточная Европа:** 4,5–8,6 тыс. ₽/час - **Южная и Юго-Восточная Азия:** 2,3–5 тыс. ₽/час (более широкий разброс по качеству) **In-house (ежемесячная стоимость, типичные рыночные диапазоны 2026).** Senior Flutter-инженер обходится примерно в 900 тыс. ₽ – 1,4 млн ₽ в месяц и выше в США, 495–828 тыс. ₽ в Западной Европе и 261–522 тыс. ₽ в Восточной Европе. Дальше умножьте на **1,25–1,4×**, чтобы получить полную стоимость — налоги на ФОТ, соцпакет, технику, софт и офисные или удалённые накладные расходы. Зарплата в 1,1 млн ₽/мес — это примерно 1,4 млн ₽/мес затрат. Плюс заложите разовую стоимость рекрутинга в 15–25% от годовой зарплаты, если пользуетесь агентством или внутренним рекрутером. Важный нюанс: в отличие от агентства и augmentation, этот ежемесячный чек — постоянное обязательство. Вы платите его и в те 2–4 месяца, пока идёт найм и ramp-up, ещё до первого значимого релиза. **Staff augmentation (за инженера, помесячно).** Augmentation обычно оказывается на **10–30% ниже** полной проектной ставки агентства при той же квалификации, потому что вы покупаете время инженера, а не всю обвязку вокруг разработки (PM, QA-лид, дизайн). Вы полностью избегаете гонораров рекрутеров и долгосрочных трудовых обязательств — платите помесячную ставку и останавливаетесь, когда работа сделана. Одна цифра верна для всех трёх моделей: **Flutter снижает стоимость разработки примерно на 30–40% по сравнению с двумя отдельными нативными приложениями**, потому что один кодбейз покрывает iOS, Android и часто web. Эта экономия одинакова, кого бы вы ни наняли, — это свойство фреймворка, а не модели найма. Если вы считаете стоимость реальной разработки, мы разобрали всё по уровням в посте [«Стоимость разработки Flutter-приложения в 2026 году»](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026). ### Когда имеет смысл нанимать in-house? Нанимайте in-house, когда мобильное приложение *и есть* продукт, а не проект — когда ваш roadmap рассчитан на годы, конкурентное преимущество живёт именно в приложении, и вы будете непрерывно выпускать релизы ещё долго после v1. Вы хотите, чтобы это знание было внутри компании. In-house выигрывает в том, что накапливается: институциональная память, глубокий продуктовый контекст и инженеры, которым не всё равно, потому что это и их продукт тоже. Никто не понимает странный edge-case в вашем онбординге лучше человека, который поддерживает его уже два года. Подвох в том, что in-house — самый медленный и дорогой способ *начать*. Вы месяцами платите гонорары рекрутеров, зарплаты и соцпакет ещё до того, как выйдет значимый код, а один плохой senior-найм может отбросить вас на целый квартал. Стройте in-house, когда можете позволить себе оптимизировать вдолгую, а не под быстрый старт. ### Когда правильный выбор — агентство? Нанимайте агентство, когда у вас есть очерченный продукт под запуск, дедлайн и мало внутренних ресурсов, чтобы самим управлять разработкой. Хорошее агентство — это уже собранная команда (инженеры, проджект-менеджер, дизайнер, QA), которую вы направляете на объём и получаете обратно выпущенное приложение, никого не нанимая сами. Реальная ценность модели агентства — не код, а *управленческие накладные расходы, которые вы не несёте*. Вы не нанимаете, не ведёте планирование спринтов, не подменяете того, кто ушёл в отпуск. Вы утверждаете объём и смотрите демо. Для основателя без технического со-основателя в этом весь смысл. Агентство — правильный выбор для MVP (обычно [1,4–2,7 млн ₽](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026)) и полноценных бизнес-приложений (2,7–5,4 млн ₽), где цель — «выпустить хорошо и быстро». Именно это мы делали для клиентов вроде [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) (финтех-приложение с обилием графиков) и [YouMi](https://ru.nerdy.pro/portfolio/youmi) — очерченный продукт, выпущенный командой, которая уже делала проекты этой категории риска. Где агентство перестаёт быть ответом: когда вам нужно долгосрочное владение и приложение никогда по-настоящему не «заканчивается». В этот момент вы бесконечно арендуете команду, и математика удержания знаний начинает склоняться в пользу in-house или augmentation. ### Когда лучше всего подходит staff augmentation? Flutter staff augmentation — это когда вы встраиваете одного или нескольких проверенных Flutter-инженеров прямо в свою существующую команду: они работают в вашем репозитории, ваших спринтах и вашем Slack, под вашим управлением, но наняты и предоставлены внешним партнёром. Вы расширяете команду, которая у вас уже есть, а не отдаёте проект на аутсорс. Это правильный вариант, когда инженерная функция у вас *работает* — есть тимлид, кодбейз, процесс, — и не хватает только Flutter-мощности. Может, нужно успеть к дате запуска, закрыть декрет или добавить мобильную разработку команде, сильной в бэкенде. Augmentation даёт вам senior-инженеров по Flutter за дни вместо месяцев, которые уходят на in-house-найм, а код и контекст остаются в вашем репозитории, а не у вендора. Это ещё и самая гибкая модель с большим отрывом. Масштабируйтесь с одного инженера до четырёх на время аврала, а потом обратно — с помесячным шагом, без выходных пособий и рекрутингового цикла. Эта гибкость и есть весь продукт. Честное ограничение: augmentation работает, только если вы реально можете управлять инженерами. Если у вас нет того, кто будет владеть архитектурой, ревьюить пул-реквесты и вести планирование, augmentation-инженеры начнут дрейфовать — и вы получите проблемы уровня агентства по цене augmentation. Если это про вас — нанимайте агентство. Это наше основное направление — [расширение команды](https://ru.nerdy.pro/services/team-augmentation), и это же та модель, от которой мы вас *отговорим*, если у вас нет управленческого ресурса, чтобы она заработала. ### Как быстро каждая модель доведёт вас до релиза? Скорость до первого коммита выстраивается в чёткий порядок, и часто именно он решает. - **Staff augmentation: дни — ~2 недели.** Проверенный инженер, который уже знает Flutter, входит в вашу существующую среду. Ramp-up — это ваш кодбейз и домен, а не язык и не тулинг. - **Агентство: 1–3 недели до старта, дальше непрерывно.** Команда уже собрана, так что задержки на найм нет — лид-тайм — это скоуп и договоры, после чего целая команда движется разом. - **In-house: 2–4 месяца до реального кода, иногда больше.** Поиск, собеседования, сроки уведомления, потом ramp-up. Для senior Flutter-инженеров поиск идёт дольше — пул реальный, но конкурентный. Если ваше ограничение — дата, этот порядок важнее почасовой ставки. Самый дешёвый инженер, который выходит через три месяца, не дешевле того, кто дороже, но выходит на следующей неделе, — если задержка стоит вам окна запуска. ### Каковы скрытые издержки и режимы отказа у каждой модели? У каждой модели есть режим отказа, которого нет в смете. Вот те, что реально кусаются. **Скрытые издержки in-house.** Рекрутинг медленный и дорогой — 15–25% от годовой зарплаты в гонорарах плюс недели времени ваших senior-инженеров на собеседования. Дальше — **bus factor**: если всё Flutter-знание на одном человеке и он уходит, у вас кризис, а не неудобство. А ошибочный найм — самая дорогая ошибка из всех: месяцы зарплаты, потерянный темп и поиск, который придётся вести заново. **Скрытые издержки агентства.** Главные две — **lock-in** и **обрыв знаний**. Если агентство владеет кодбейзом, CI/CD и всем tribal knowledge, уйти от него больно — и это заложено в саму модель. Защитите себя контрактом: код в *ваших* репозиториях с первого дня, задокументированная передача, никакой критичной инфраструктуры, доступ к которой есть только у них. Второй режим отказа — рассинхрон сметы и объёма: 18 млн ₽ за MVP, которому цена 2,7 млн ₽. Так работают конторы, которые умеют продавать только по одной цене. Если смета не совпадает с объёмом, агентство либо не понимает объём, либо не хочет его понимать. **Скрытые издержки staff augmentation.** Нагрузка, которую нельзя отдать наружу, — **управление**. Augmentation-инженерам нужны онбординг, код-ревью и направление, как любому члену команды, — пропустите это, и качество просядет раньше, чем вы заметите. Есть и реальная стоимость ramp-up, пока они изучают ваш домен, плюс налог на коммуникацию через далёкий часовой пояс. Augmentation — это мощность, а не автопилот; вы меняете нагрузку найма на нагрузку управления. ### Как проверить Flutter-разработчика или команду? Собеседуете ли вы in-house-кандидата, агентство или augmentation-инженера — сигнал один и тот же. Просите доказательства, а не прилагательные. - **Реально выпущенные приложения.** Просите ссылки на App Store и Google Play, а не скриншоты, — и скачивайте их. Ощущаются ли они быстрыми? Корректно ли обрабатывают офлайн, ошибки и медленную сеть? «Выпущено и живёт» важнее вылизанной презентации портфолио. - **След на pub.dev и в open-source.** Опубликованные пакеты, значимые контрибьюции или ответы в issue говорят о том, кто работает внутри экосистемы, а не только потребляет её. [Наш — публичный](https://ru.nerdy.pro/open-source), если нужна точка отсчёта, как выглядит «поддерживаемое». - **Свободное владение state management.** У них должно быть *обоснованное* мнение о Riverpod vs. BLoC vs. Provider — а не «использую X, потому что привык». Догма — жёлтый флаг; понимание компромиссов — зелёный. - **CI/CD и дисциплина релизов.** Умеют настроить автоматические сборки, подпись и выкладку в сторы? Используют ли feature flags и поэтапные раскатки? Выпуск — это навык, отдельный от кодинга, и именно там junior-команды тихо теряют время. - **Тестирование.** Спросите, что они тестируют и почему. «Мы тестируем всё» — такой же тревожный сигнал, как «мы не тестируем». Вам нужно суждение о том, где тесты окупаются. - **Опыт с platform channels и нативом.** Реальным приложениям рано или поздно нужен нативный код — iOS-SDK, причуда с разрешениями на Android, нативный модуль. Тот, кто никогда не выходил за пределы Dart-слоя, застрянет на первой же границе с платформой. Всё ещё выбираете между Flutter и чем-то ещё, прежде чем нанимать под это? Мы разбираем это в постах [«Flutter vs React Native в 2026 году»](https://ru.nerdy.pro/blog/flutter-vs-react-native-2026) и [«Flutter против нативной разработки в 2026 году»](https://ru.nerdy.pro/blog/flutter-vs-native-2026). ### Фреймворк принятия решения: четыре вопроса Отвечайте по порядку. Первое подходящее «да» направляет вас к модели. 1. **Это приложение — ядро вашего бизнеса на 3+ года вперёд, и вы можете подождать 2–4 месяца, пока укомплектуете команду?** → **Нанимайте in-house.** Вам нужно знание внутри компании, и вы можете позволить себе медленный старт. 2. **Нужно выпустить очерченный продукт к дедлайну при малом количестве внутренних инженеров для управления?** → **Нанимайте агентство.** Покупайте команду и управленческие накладные расходы, а не только код. 3. **У вас уже есть работающая инженерная команда, которой нужно просто больше Flutter-мощности — и есть кому ею управлять?** → **Используйте staff augmentation.** Расширьте команду, которая у вас есть. 4. **Не уверены, стоит ли вообще это строить?** → **Начните с MVP, собранного агентством.** Это самый быстрый путь к реальному ответу, и вы не берёте на себя обязательств по найму раньше, чем поймёте, что он нужен. Если подходят два ответа, тай-брейк — управленческий ресурс: и augmentation, и in-house предполагают, что вы способны руководить инженерами. Если нет, честным выбором будет агентство. ### FAQ #### Сколько стоит нанять Flutter-разработчика? В 2026 году смешанные ставки агентств составляют примерно 12 000–20 000 ₽/час в США, 8 000–14 400 в Западной Европе, 4 000–7 600 в Восточной Европе и 2 000–4 400 в Южной и Юго-Восточной Азии. Стоимость senior in-house — примерно 800 000–1 200 000 ₽ в месяц и выше (США), 440 000–736 000 ₽ (Западная Европа) и 232 000–464 000 ₽ (Восточная Европа), плюс 25–40% на полную стоимость. Staff augmentation обычно на 10–30% дешевле полной ставки агентства, потому что вы покупаете инженера, а не всю проектную команду. #### Что дешевле — нанимать in-house или агентство? Зависит от горизонта. Агентство дешевле на старте — нет гонораров рекрутеров, зарплат до выхода кода и долгосрочных обязательств. In-house дешевле в пересчёте на час, когда вы уже наняли, поэтому выигрывает на годах непрерывной работы. Для разовой разработки агентство почти всегда дешевле по совокупности; для ключевого продукта на годы обычно выигрывает in-house. #### Что такое Flutter staff augmentation? Flutter staff augmentation — это когда вы встраиваете одного или нескольких проверенных Flutter-инженеров прямо в свою существующую команду. Они работают в вашем репозитории, ваших спринтах и под вашим управлением, но наняты и предоставлены внешним партнёром. Это способ добавить Flutter-мощность за дни без времени на рекрутинг и долгосрочных обязательств полноценного найма, сохраняя код и знание внутри вашей команды. #### Сколько времени занимает онбординг Flutter-разработчика? Augmentation-инженер, который уже знает Flutter, обычно продуктивен в вашем кодбейзе за срок от нескольких дней до двух недель — ramp-up это ваш домен, а не язык. Команда агентства стартует за 1–3 недели. Новый in-house-найм занимает 2–4 месяца от начала поиска до выхода реального кода из-за поиска, собеседований и сроков уведомления. #### Как понять, что Flutter-разработчик хорош? Просите живые ссылки на App Store и Google Play и реально пользуйтесь приложениями — тестируйте на медленной сети и офлайн. Ищите след на pub.dev или в open-source, обоснованное мнение о state management (Riverpod vs. BLoC vs. Provider) и реальный опыт CI/CD и релизов. Самый сильный сигнал — работа с platform channels: тот, кто интегрировал нативный код iOS/Android, прошёл через сложные части реальных приложений. #### In-house или аутсорс Flutter — что выбрать стартапу? Большинству ранних стартапов стоит отдать первую разработку агентству, а затем нанимать in-house, когда продукт начнёт набирать обороты и прояснится roadmap. Аутсорс быстрее выводит на рынок и откладывает стоимость и риск найма до того, как вы поймёте, что именно вам нужно. Переходите на in-house, когда приложение становится ядром бизнеса и вы будете непрерывно выпускать релизы годами. #### Можно ли получить выделенную Flutter-команду, не нанимая сотрудников? Да. Выделенная Flutter-команда через агентство или staff augmentation даёт инженеров, которые работают только над вашим продуктом, без трудовых обязательств, времени на рекрутинг и расходов на соцпакет при штатном найме. При augmentation команда работает внутри ваших процессов и репозитория; при агентстве — как управляемая единица. Обе модели позволяют масштабировать команду вверх или вниз на границах контракта. ### Какая модель подходит вам? Не уверены, какая из трёх правильная? Это нормальное состояние — ответ зависит от ваших сроков, вашей существующей команды и того, насколько это приложение важно для бизнеса. Мы с удовольствием честно всё обсудим, даже когда ответ — «нанимайте in-house» или «вам нужно агентство, а не мы». Если окажется, что это augmentation, — это наш конёк: мы размещаем проверенных Flutter-инженеров в вашей команде, в вашем репозитории и ваших спринтах, а код и знание остаются вашими. Если это очерченный продукт, который вы предпочли бы передать, — это наша практика [разработки приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development); какую работу она даёт, видно на проектах [Arcana](https://ru.nerdy.pro/portfolio/arcana), [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) и [YouMi](https://ru.nerdy.pro/portfolio/youmi). В любом случае [расскажите, что вы строите](https://ru.nerdy.pro/contact), и мы дадим прямую рекомендацию по модели — а не продажу самой дорогой из них. --- *Илья Никсан — основатель и ведущий разработчик [Nerdy Production](https://ru.nerdy.pro/), Flutter-first агентства, которое делает приложения в финтехе, здравоохранении и ритейле и размещает Flutter-инженеров в командах клиентов.* ## Как мы разрабатываем с AI: архитектура и гибкость вместо набора кода https://ru.nerdy.pro/blog/building-with-ai **Коротко.** Мы используем AI для написания большой части нашего кода, и это осознанный выбор, а не способ срезать угол. Причина проста: агент быстро производит код и плохо владеет системой целиком. Поэтому набор кода мы отдаём ему, а инженеров поднимаем выше по стеку — к архитектуре, к гибкости, к системным решениям, от которых зависит, будет ли кодовую базу всё так же приятно менять через год. В 2026 году индустрия пришла к тому же: [84% разработчиков теперь используют AI каждый день](https://medium.com/@umarhussainkhokhar1234/the-developers-world-in-june-2026-everything-that-s-changing-right-now-1de29f6d695e), а выигрышный паттерн звучит как «генерируй агрессивно, проверяй строго». Загвоздка в том, что AI оптимизирует каждый файл локально, а *кодовую базу глобально не оптимизирует никто* — именно поэтому [кодовые базы, которые мы проверяем](https://ru.nerdy.pro/blog/ai-code-audit-findings), ломаются одними и теми же одиннадцатью способами. Наш ответ: оставить человека на той части, которую AI не видит, — на архитектуре. Вот как мы работаем и почему. --- ### Работа изменилась, и мы изменились вместе с ней Бо́льшую часть истории разработки узким местом был набор кода. Превратить ясную идею в работающий код — это была медленная и дорогая часть, поэтому именно туда уходили инженерные усилия и по ней измерялся уровень специалиста. Это узкое место исчезло. Как описывает [обзор мира разработки за июнь 2026 года](https://medium.com/@umarhussainkhokhar1234/the-developers-world-in-june-2026-everything-that-s-changing-right-now-1de29f6d695e), AI-инструменты прошли путь от диковинки до базовой нормы: 84% разработчиков обращаются к ним ежедневно, TypeScript обогнал Python как самый используемый язык отчасти потому, что типизированный код держит AI в рамках, а самые продуктивные инженеры больше не пишут код руками — они *оркестрируют агентов*, которые это делают. «10x-инженер» превратился в управляющего машинами. Мы этому не сопротивлялись, а перестроились вокруг этого. Если агент производит работающую фичу за минуты, то редкий и ценный человеческий навык — это уже не «можешь ли ты написать эту функцию». Это «должна ли эта функция вообще существовать, где ей место, что произойдёт, когда поменяются требования, и как она поведёт себя, когда в неё одновременно придут десять тысяч человек». Это вопросы архитектуры и гибкости. Именно в них AI слабее всего — и именно они решают, выживет продукт или умрёт. База 2026 года в четырёх цифрах: - **84% используют AI ежедневно.** AI-инструменты теперь норма, а не опциональное дополнение. - **46% не доверяют, 3% доверяют полностью.** Разработчиков, не доверяющих выводу AI, гораздо больше, чем доверяющих, — поэтому проверка обязательна. - **Мультиагентность стала нормой.** Самые продуктивные инженеры направляют флоты агентов, а не пишут больше кода руками. - **TypeScript — №1.** Типизированные языки обогнали Python отчасти потому, что держат сгенерированный код в рамках. --- ### Что мы отдаём AI, а что оставляем себе Понятнее всего описать наш процесс, разделив работу надвое: то, что мы делегируем машинам, и то, что отказываемся отдавать. **AI мы отдаём набор кода.** Бойлерплейт, CRUD-эндпоинты, десятая вариация формы, склейка для перекладывания данных, черновики тестов, миграции, нудные механические преобразования, которые раньше съедали полдня, — агенты по-настоящему хороши во всём этом. Модель напишет правдоподобную реализацию быстрее, чем человек успеет её описать. Так пусть и пишет. В 2026 году нет никакой доблести в том, чтобы вручную набивать постраничный список. **Архитектуру мы оставляем себе.** Где живёт состояние? Что является единственным источником правды для этой предметной области? Каким границам позволено знать о каких других? Как это масштабируется с одного инстанса до двенадцати? Каков режим отказа, когда у платёжного провайдера таймаут? Как эта форма согнётся, когда клиент через три месяца попросит то, чего, как он клянётся, он никогда не попросит? Ни у одного из этих вопросов нет ответа «кратчайший путь к работающему коду» — а кратчайший путь к работающему коду — это единственное, что оптимизирует агент. Это разделение не компромисс. Это наиболее эффективное использование обеих сторон. Машина делает то, в чём быстра; люди делают то, что пока хорошо умеют только люди. Гуляющая по индустрии формулировка — «Codex для нажатий клавиш, Claude Code для коммитов» — на самом деле про *высоту*: чем ближе вы к решению, от которого зависит вся система, тем больше человек нужен в контуре. --- ### Почему архитектура должна оставаться за человеком Вот неудобная правда, которую мы видим каждую неделю, и она — двигатель всего, о чём мы только что говорили. Мы проводим [аудиты кода](https://ru.nerdy.pro/services/ai-code-audit) для постоянного потока приложений, собранных на AI. Кодовые базы дико разные, а вот [находки — почти никогда](https://ru.nerdy.pro/blog/ai-code-audit-findings): секреты закоммичены прямо в репозиторий, нет валидации ввода, аутентификация никогда не проверяет, кто спрашивает, нет тестов, один и тот же хелпер продублирован четыре раза с небольшими расхождениями, два экрана в одном проекте написаны так, будто их делали две разные команды, сложное состояние свалилось в callback hell, код рассчитан на одну машину и ломается, как только запускается на двух. Одиннадцать проблем, снова и снова. Каждая из них — *системное* свойство. Ни одна не может появиться из одного хорошего промпта, потому что ни один промпт не видит систему целиком. У модели, генерирующей код, узкое окно контекста и нет памяти о решении, принятом три файла назад. Она каждый раз делает локально оптимальный выбор — а **сумма локально оптимальных выборов — это кодовая база, которая работает сегодня и сопротивляется любому изменению завтра.** Дрейф — не баг конкретной генерации; это неизбежная форма генерации без глобального владельца. Поэтому роль человека не сжалась, когда AI начал писать код. Она *сместилась* — от производства строк к гарантии свойств, которые существуют только поверх строк. Связная архитектура, единственный источник правды, состояние, смоделированное как состояние, а не куча гоняющихся друг за другом коллбэков, понимание реального окружения деплоя, безопасность, включённая по умолчанию. Это и есть соединительная ткань. В ней разница между демо и продуктом, и оставленный сам по себе агент пропустит её всю. Мы ему этого не позволяем. --- ### Vibe and verify: как мы держим AI-код безопасным В данных есть реальный разрыв доверия, и мы относимся к нему серьёзно: по индустрии 46% разработчиков активно не доверяют выводу AI и лишь 3% доверяют ему полностью. Зрелая реакция — не перестать использовать AI, а **относиться ко всему, что он производит, как к недоверенному стороннему коду, пока не доказано обратное.** Генерируй агрессивно; проверяй строго. «Vibe and verify». На практике это значит, что каждая написанная агентом строка проходит тот же контроль, что и пул-реквест от подрядчика: - **Её читает человек, а не пробегает глазами.** Ловушка доверия к AI-коду в том, что он *выглядит* законченным, поэтому те 20%, которые не закончены, незаметны. Мы читаем код, высматривая то, в чём модели стабильно ошибаются, — отсутствующая авторизация за экраном логина, ввод, подставленный прямо в запрос, эндпоинт, возвращающий весь объект пользователя, когда интерфейсу нужно было имя. - **Ей пишут тесты, целенаправленно.** Сгенерировать фичу и сгенерировать к ней тесты — два разных запроса, и второй сам по себе случается редко. Мы добиваемся, чтобы он случился: smoke-тест на то, что приложение поднимается, реальное покрытие вокруг денег и авторизации, и регрессионный тест на каждый исправленный баг. - **Её меряют по архитектуре, а не только по «запускается ли».** Фичу, которая работает, но изобретает собственный паттерн состояния, или дублирует существующий хелпер, или тянется через границу, о которой не должна знать, отправляют на доработку — даже если её счастливый путь проходит. Это та же дисциплина, которую мы описали для основателей, выводящих AI-прототипы в продакшен, — [что ломается между демо и продакшеном](https://ru.nerdy.pro/blog/ai-prototype-to-production). Короткая формула, к которой мы постоянно возвращаемся: **AI проходит 80% пути за 5% времени — а последние 20% и есть весь продукт.** Эти 80% мы отдаём AI. А 20% оставляем себе. --- ### Оркестрировать агентов, а не писать больше кода Ещё один сдвиг, о котором говорит ландшафт 2026 года, — мультиагентная разработка: вместо одного ассистента специализированные агенты работают параллельно — один набрасывает фичу, другой пишет тесты, третий проверяет безопасность, четвёртый занимается механической миграцией — а инженер всем этим дирижирует. Именно так наши инженеры теперь и проводят день. Меньше времени внутри одной функции, больше — за решением о том, что агенты должны построить, в каком порядке, относительно каких интерфейсов, и за проверкой того, что вернувшееся ложится в систему. Это меньше похоже на набор кода и больше — на техническое руководство очень быстрой и очень буквальной командой, которой нужны точные указания и внимательная проверка. Рычаг огромен, но только если у того, кто держит оркестр, в голове есть партитура — архитектура. Направьте флот агентов на кодовую базу без хребта — и получите одиннадцать аудиторских находок, только быстрее. > **Рычаг работает в обе стороны** > > Агенты усиливают любое заданное направление. Направленные на ясную архитектуру, они ускоряют рост сильной кодовой базы. Направленные в пустоту, они ускоряют запутывание уже запутанной. Человек, задающий направление, — это не накладные расходы поверх AI, а то, что решает, станет ли AI ускорителем или лавиной. --- ### Гибкость — это и есть вся суть Если описать одним словом то, что мы оптимизируем, это не скорость — скорость теперь дёшева. Это **гибкость:** способность выдержать изменение требований без переписывания. Именно здесь подход «сначала архитектура» окупается заметнее всего. AI-кодовые базы, которые мы проверяем, быстро *стартуют* и мучительно *меняются* — они окостеневают, и не потому, что код хорош и ценен, а потому, что никто не решается его трогать без тестов, без связной структуры, не зная, что ещё сломается. Это противоположность гибкости. Продукт, который нельзя менять, мёртв в тот день, когда сдвинулся рынок. Когда архитектурой владеет человек, а набором кода — AI, вы получаете обе половины: скорость генерации *и* систему, которая гнётся. Приходит новое требование — и нужно обновить один источник правды, а не разыскивать четыре копии; расширить одну модель состояния, а не распутывать лабиринт коллбэков; сдвинуть одну ясную границу, а не гадать, какой из двух экранов делает это «правильно». Ровно так мы сделали [Arcana](https://ru.nerdy.pro/portfolio/arcana), потоковый AI-чат-клиент, — именно неблагодарная работа над архитектурой, стримингом и моделью состояния превратила быстрый прототип в то, что мы могли развивать дальше, а не переписывать заново. В этом весь тезис: **мы используем AI, чтобы наши инженеры перестали тратить лучшие часы на набор кода и тратили их на решения, которые держат софт гибким.** Код теперь дешёвая часть. Архитектура — это продукт. --- ### Частые вопросы #### Снижает ли качество то, что код пишет AI? Нет, потому что мы никогда не считаем вывод AI готовым. Мы следуем паттерну, к которому индустрия пришла в 2026 году: генерируй агрессивно, затем проверяй строго. Каждая написанная агентом строка проверяется человеком как пул-реквест подрядчика, получает реальные тесты вокруг важных частей и сверяется с архитектурой системы перед выпуском. AI берёт на себя набор кода; люди гарантируют качество. При таком подходе результат быстрее и как минимум не менее надёжен, чем полностью написанный руками код, потому что инженеры тратят внимание на проектирование, а не на бойлерплейт. #### Если код пишет AI, чем тогда занимаются ваши инженеры? Они отвечают за всё, в чём AI слаб, а это большая часть того, от чего зависит долговечность софта: архитектура, где живёт состояние, единственный источник правды для каждой области, как система масштабируется по инстансам, режимы отказа, безопасность и гибкость к будущим изменениям. Они также оркестрируют агентов — решают, что строить, в каком порядке, относительно каких интерфейсов, — и проверяют то, что вернулось. Человеческая работа сместилась выше по стеку: от производства строк к гарантии системных свойств, которые ни один отдельный промпт создать не может. #### Почему бы просто не дать AI собрать всё от начала до конца? Потому что модель оптимизирует каждый файл по кратчайшему пути к работающему коду, а кодовую базу целиком не оптимизирует никто. Результат предсказуем: закоммиченные секреты, нет валидации ввода, аутентификация никогда не проверяет, кто спрашивает, нет тестов, дублированная логика, нет единой архитектуры, состояние свалилось в callback hell, и код ломается, как только запускается больше чем на одном инстансе. Эти же одиннадцать проблем мы видим почти в каждой проверяемой AI-кодовой базе. Это системные сбои, которые не могут возникнуть по одному промпту за раз, поэтому, чтобы AI был безопасен, архитектурой должен владеть человек. #### Что разработка с AI означает для стоимости и скорости моего проекта? Это значит, что вы платите за суждение, а не за набор кода. Генерация теперь дешёвая и быстрая часть, поэтому реальное время уходит на архитектуру, проверку и гибкость к будущим изменениям — именно на ту работу, которая защищает ваши вложения. На практике вы получаете прототипы и фичи кардинально быстрее, не наследуя при этом окостеневшую, неизменяемую кодовую базу, которую обычно оставляет чистая AI-генерация. Вы сохраняете скорость и сохраняете возможность позже сменить направление. #### Мою AI-кодовую базу можно спасти или нужно переписывать? Почти всегда можно спасти и почти никогда не нужно переписывать. Фичи работают, продукт реален; не хватает соединительной ткани — тестов, связной архитектуры, дедупликации логики, нормальной модели состояния, понимания окружения и безопасности. Всё это добавляется на месте гораздо быстрее и дешевле, чем переписывание с нуля. Этот точечный ремонт и есть то, что даёт аудит AI-кода: мы сохраняем фундамент, который AI сделал правильно, и чиним системные части, которые он не осилил. ### Делайте быстро, оставайтесь гибкими Команды, выигрывающие в 2026 году, — это не те, кто отказался от AI, и не те, кто пустил его без присмотра. Это те, кто посадил машину на набор кода, а человека — на архитектуру. Так мы строим каждый проект: AI ради скорости, инженеры ради решений, которые держат софт достаточно гибким, чтобы пережить собственный успех. Если у вас есть приложение на AI и вы не уверены, сможет ли оно развиваться дальше, прогоните его через [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit) или [закажите бесплатную оценку](https://ru.nerdy.pro/contact). Мы скажем, где архитектура крепкая, где она вот-вот выстрелит вам в ногу и что нужно, чтобы это починить, — с фиксированной оценкой по объёму, а не наугад. ## Что находит аудит AI-кода: 11 проблем почти в каждом проекте на AI https://ru.nerdy.pro/blog/ai-code-audit-findings **Коротко.** Мы делаем много аудитов приложений, собранных в Cursor, Claude Code, Bolt, Lovable и в долгих сессиях с ChatGPT. Кодовые базы разные, а вот *находки* — почти всегда одни и те же. Одиннадцать проблем всплывают снова и снова, примерно в порядке убывания вреда: **захардкоженные секреты и учётные данные**, **отсутствие валидации ввода** (поверхность для инъекций), **аутентификация, которая формально есть, но не проверяет запрос**, **нулевое покрытие тестами**, **отсутствие обработки ошибок на несчастливом пути**, **N+1-запросы и производительность, отданная на волю случая**, **устаревшие зависимости с непропатченными известными CVE**, **дублирование переменных и функций**, **нет единой архитектуры**, **сложное асинхронное состояние, скатившееся в callback hell вместо стримов**, и **код, написанный без учёта окружения развёртывания**. Ничего экзотического. Всё это предсказуемо — и всё чинится без переписывания с нуля. Это инженерное дополнение к нашему [гайду для основателей о выводе AI-прототипа в продакшен](https://ru.nerdy.pro/blog/ai-prototype-to-production); а если хотите передать это нам — именно этим занимается [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit). --- ### Что такое аудит — и что не аудит Аудит — это не переписывание с нуля и не приговор тому, стоило ли вообще собирать это на AI. Это структурированное чтение работающей кодовой базы, отвечающее на один вопрос: что произойдёт, когда её впервые встретит реальный злоумышленник, реальный всплеск трафика или реальное изменение через полгода? Находки ниже — не гипотетические категории из чек-листа: каждая из них — то, что мы находили, обезличивали и чинили в реальном проекте. Мы ссылаемся на пример отчёта об аудите и на [сервис аудита AI-кода](https://ru.nerdy.pro/services/ai-code-audit) на протяжении всего материала, потому что именно там происходит исправление; этот пост — доказательство того, зачем оно нужно. ### Почему находки повторяются AI-инструменты для написания кода оптимизируют ровно одно: кратчайший путь к коду, который *запускается*. Это правда полезно — идея превращается в рабочий прототип за часы. Но «работает в демо» и «поддерживаемо и безопасно в продакшене» — разные цели, и разрыв между ними удивительно одинаков от проекта к проекту. Причина структурная. Модель, генерирующая код, видит узкое окно контекста и не помнит решений, которые приняла три файла назад. Она не может протестировать то, что только что написала, у неё нет представления ни о вашем запасе денег, ни о ваших требованиях к безопасности, и нет стимула удерживать кодовую базу связной во времени. Поэтому она каждый раз делает локально оптимальный выбор — а сумма локально оптимальных выборов даёт код, который работает сегодня и сопротивляется любому изменению завтра. После достаточного числа аудитов сбои сводятся к одним и тем же одиннадцати категориям. Вот они. --- ### 1. Захардкоженные секреты и учётные данные Это находит каждый аудит, и это ровно тот запах кода, что стоит реальных денег: `.env`-файлы, закоммиченные в репозиторий, API-ключи и учётные данные баз данных, захардкоженные прямо в коде, токены сторонних сервисов, вшитые в клиентские бандлы, которые уходят в каждый браузер, загружающий приложение. Один утёкший ключ OpenAI или Stripe может за часы намотать тысячи долларов на несанкционированных списаниях — или прямо отдать злоумышленнику ваше хранилище данных. **Почему AI так делает.** Захардкодить ключ — это работает сразу; настроить менеджер секретов или подстановку переменных окружения — нет, а у модели нет причины предпочитать более медленный путь, если быстрый тоже «запускается». Кратчайший путь к рабочей фиче почти никогда не безопасный. **Как мы это чиним.** Секреты переезжают из кода в нормальное управление окружением. Всё, что хоть раз попало в git, считается уже скомпрометированным и ротируется, а не просто удаляется — `git rm` без ротации оставляет старый ключ действительным в каждом клоне и в каждой истории коммитов. Это часть аудита, по которой компромиссов нет: утёкший ключ — это ЧП, и чинится оно первым. ### 2. Нет валидации ввода — поверхность для инъекций SQL-инъекции, XSS и промпт-инъекции встречаются в AI-коде повсеместно, и корень проблемы всегда один и тот же: пользовательский ввод попадает прямиком в запрос, шаблон или промпт LLM без всякой валидации и санитизации по пути. Один уязвимый эндпоинт может скомпрометировать всю базу данных или позволить злоумышленнику манипулировать тем, что делают ваши собственные AI-фичи. **Почему AI так делает.** Валидация — это второй запрос, которого модели никто не задавал. Она генерирует код, удовлетворяющий счастливому пути из промпта — «возьми сообщение пользователя и сохрани его» — а счастливый путь никогда не упоминает, что нужно отклонить. **Как мы это чиним.** На каждой границе, где в систему входит непроверенный ввод, появляется явная валидация и параметризованные запросы или шаблоны вместо конкатенации строк. Для AI-фич это отдельно означает, что построение промпта само по себе — граница, подверженная инъекциям, а не только слой базы данных. ### 3. Аутентификация, которая формально есть, но не проверяет запрос AI-приложения часто реализуют аутентификацию на поверхностном уровне — экран логина есть, и он работает — но бэкенд на самом деле никогда не проверяет права на конкретный запрос. API-эндпоинты принимают всё, что до них долетает. Административные маршруты доступны без проверки роли. Пользователь A может увидеть данные пользователя B, просто подменив ID в URL — небезопасная прямая ссылка на объект, живущая в продакшене. **Почему AI так делает.** «Добавь страницу логина» и «проверь, что именно этому запросу разрешено трогать именно эту запись» — разные задачи, и модель решает ту, о которой её спросили. Аутентификация видна в демо; пробелы в авторизации невидимы, пока их кто-то не проэксплуатирует. **Как мы это чиним.** Мы проверяем каждый эндпоинт на то, кому реально разрешено его вызывать, а не на то, что подразумевает экран логина, и добавляем недостающие проверки авторизации на уровне запроса — проверки владения записью, проверки роли на административных маршрутах и rate limiting на всё, что можно закидать скриптом. ### 4. Нет тестов — каждый деплой становится лотереей Самая частая находка: **тестов нет вообще.** Не тонкий набор, не флакающие тесты — ноль. Приложение проверяли кликами по нему, и это вся страховочная сетка. Это незаметно ровно до того момента, когда становится катастрофой. Без тестов нельзя узнать, сломало ли изменение что-то ещё, иначе как выкатив его и дождавшись жалобы пользователя. Каждый деплой превращается в ручной регресс, который никто на самом деле не проводит, поэтому рефакторинг становится страшным, обновления зависимостей пропускаются, а кодовая база костенеет — не потому что код плох, а потому что никто не решается его трогать. **Почему AI так делает.** Сгенерировать фичу и сгенерировать тесты к ней — это два разных запроса, и второй никто не сделал. Модель охотно напишет тесты, если попросить, но предоставленная сама себе она выдаёт счастливый путь и останавливается. **Как мы это чиним.** Мы не гонимся за 100% покрытием в первый день. Мы добавляем тонкий слой там, где он окупается сильнее всего: smoke-тест, что приложение стартует, тесты вокруг логики с деньгами и авторизацией, и регрессионный тест на каждый баг, который чиним по ходу аудита. Уже это превращает деплой из лотереи в рутину. ### 5. Нет обработки ошибок на несчастливом пути Приложение работает ровно так, как в демо — пока сеть не отваливается, сторонний API не отвечает по таймауту, а пользователь не делает ничего неожиданного. В момент, когда что-то из этого случается, симптомы уродливые: необработанное отклонение промиса роняет весь запрос, упавший вызов API оставляет UI навечно висеть на спиннере, исключение показывает пользователю сырой стектрейс вместо сообщения, которое хоть что-то для него значит. **Почему AI так делает.** Счастливый путь — это то, что описал промпт и что прогнало демо. Обработка ошибок — это защитный код для ситуаций, которые модели никто не просил представить, и он добавляет строки, не делая демо ни капли более впечатляющим, — поэтому его пропускают первым при неявном лимите времени. **Как мы это чиним.** Мы проходим по каждому внешнему вызову — API, база данных, файловая система — и добавляем ветку отказа: ретраи с backoff там, где ретрай помогает, фолбэк или понятное состояние ошибки там, где не помогает, и логирование, которое говорит, что реально произошло, а не общее «что-то пошло не так». Цель в том, чтобы сбой стороннего сервиса лишь частично ухудшал работу приложения, а не укладывал его целиком. ### 6. N+1-запросы и производительность, отданная на волю случая Экран списка, который мгновенно загружается с десятью строками в разработке, ползёт с десятью тысячами в продакшене. Классическая причина — N+1-запрос: один запрос за списком, затем отдельный запрос на каждую строку за связанными данными, так что экран, который должен стоить одного обращения к базе, стоит сотен. Отсутствующие индексы, неограниченные выборки без пагинации и загрузка целых объектов, когда отображается одно-два поля — обычные спутники. Наш [разбор баз данных и индексов](https://ru.nerdy.pro/blog/databases-and-indexes) объясняет механику того, почему это медленно и как выглядит здоровый план запроса. **Почему AI так делает.** Паттерн N+1 — самый очевидный способ написать цикл, и он даёт корректный результат — у модели нет обратной связи, которая сказала бы, что количество запросов важно, пока кто-то не измерит это на реальном объёме данных, чего демо с горсткой строк никогда не делает. **Как мы это чиним.** Мы профилируем реальные паттерны запросов при реалистичном объёме данных, схлопываем цепочки N+1 в джойны или батчевую загрузку, добавляем недостающие индексы и ставим пагинацию или лимиты на всё, что возвращает неограниченный набор. Обычно это самое результативное исправление производительности за весь аудит: один плохой экран списка способен съедать большую часть времени загрузки страницы. ### 7. Устаревшие зависимости и дрейф CVE `npm audit` или его аналог выдаёт стену известных уязвимостей в тот момент, когда кто-то наконец его запускает — потому что раньше никто не запускал. Пакеты закреплены на той версии, что была актуальна, когда AI-инструмент разворачивал проект, транзитивные зависимости, которые никто напрямую не выбирал, несут свои собственные CVE, и нет процесса, который бы сообщил, когда выходит патч. **Почему AI так делает.** Модель выбирает пакет, решающий текущую задачу, и идёт дальше; сопровождать ваш проект она не будет, и ничто не заставит её вернуться к этому выбору позже. Гигиена зависимостей — это часть сопровождения, а в генерации фичи нет ничего, что запускало бы сопровождение. **Как мы это чиним.** Мы запускаем сканирование уязвимостей зависимостей, патчим или заменяем всё с известным эксплойтом и настраиваем процесс — хотя бы простое плановое сканирование — чтобы всё это снова тихо не поехало сразу после окончания аудита. ### 8. Дублирование переменных и функций Откройте AI-кодовую базу и поищите один и тот же хелпер форматирования дат. Часто вы найдёте его три-четыре раза — каждый чуть отличается, потому что каждый сгенерирован изолированно под экран, которому он понадобился. То же с правилами валидации, API-клиентами, денежной арифметикой и константами конфигурации. Дублирование не просто уродливо; это бомба замедленного действия для корректности. Когда логику нужно изменить — новое правило налога, исправленный баг округления, обновлённый эндпоинт — придётся найти каждую копию. Одну вы пропустите. И вот уже две части приложения расходятся в том, в чём должны были совпадать, и это расхождение — следующий инцидент в продакшене. **Почему AI так делает.** Модель редко ищет в существующем коде хелпер, который могла бы переиспользовать. С её точки зрения дешевле перегенерировать функцию инлайн, чем найти и импортировать ту, что уже есть. Каждая генерация локально разумна; в сумме получается дрейф. **Как мы это чиним.** Мы находим кластеры почти идентичного кода, выносим единый источник правды и пускаем через него все точки вызова. Это одна из самых результативных чисток в большинстве аудитов: она сжимает кодовую базу и убирает целые классы багов «починили здесь, но не там». ### 9. Нет единой архитектуры Когда видишь такое впервые — это настоящий шок. Два экрана в *одном проекте* написаны так, будто их делали две разные команды: один тянет данные в компоненте, другой — через сервисный слой; один хранит состояние одним способом, следующий — совершенно иначе; именование, структура папок и обработка ошибок меняются от фичи к фиче. Нет хребта. Кодовая база без единой архитектуры — это та, где каждый файл, который вы открываете, оказывается сюрпризом. Онбординг разработчика занимает недели, потому что здесь нет паттерна, который можно выучить, — есть только сотня частных случаев, которые надо запомнить. Хуже того: когда паттерны конфликтуют, именно на стыках между ними плодятся баги. **Почему AI так делает.** У модели нет устойчивой картины «как устроено это приложение». Каждый запрос — чистый старт, поэтому она хватается за любой паттерн, который подходит под этот конкретный запрос. За жизнь проекта это даёт лоскутное одеяло — каждый кусок разумен по отдельности, целое бессвязно. **Как мы это чиним.** Мы выбираем одну архитектуру, подходящую проекту — не догматичную, а *подходящую* — и инкрементально сводим к ней кодовую базу, чтобы разработчик, разобравшийся в одной фиче, мог предсказать, как работает следующая. ### 10. Состояния на стримах избегаются — прямиком в callback hell Это технически самый интересный сбой и тот, что тихо ломает самые сложные фичи. Код, сгенерированный AI, склонен **избегать стримовых и реактивных моделей состояния** в пользу императивных колбэков. Вместо того чтобы смоделировать «это значение меняется во времени, и UI на него реагирует», он навешивает колбэк, который дёргает другой колбэк, который ставит флаг, который запускает третий — и получается callback hell. На простых экранах вы этого почти не замечаете. Но как только состояние действительно сложное — многошаговая форма с кросс-валидацией полей, дашборд с живыми обновлениями, что угодно с дебаунсом, ретраями, отменой или оптимистичными апдейтами — подход на колбэках разваливается. Классический симптом — форма, которая *почти* работает: она валидирует, но ошибка пропадает не в тот момент; она отправляет, но двойной тап шлёт её дважды. **Почему AI так делает.** Императивные колбэки — самый частый паттерн в обучающих данных модели и самый простой для генерации по кусочку. Реактивные и стримовые модели требуют держать в голове весь конечный автомат сразу — ровно то, в чём генератор с ограниченным контекстом слабее всего. **Как мы это чиним.** Мы выявляем фичи со сложным состоянием и перестраиваем их слой состояния правильно — как стримы или реактивную модель, подходящую стеку, — чтобы UI стал функцией состояния, а не кучей колбэков, гоняющихся друг за другом. ### 11. Нет представления об окружении развёртывания Модель пишет код так, будто он будет работать одним процессом на одной машине — потому что изнутри промпта это единственное окружение, которое она видит. Она не знает, сколько инстансов будет запущено, какие управляемые сервисы уже есть и как маршрутизируется трафик. Поэтому она по умолчанию берёт простейшую топологию — и этот дефолт тихо ломается, как только приложение разворачивают по-настоящему. Симптомы всегда одни и те же. Состояние, живущее в памяти процесса — кеш, сессии, счётчики rate-limit — отлично работает на одном инстансе и незаметно расходится, как только за балансировщиком поднимается вторая реплика. Фоновые задачи срабатывают на *каждом* инстансе вместо одного, поэтому письмо уходит трижды. **Почему AI так делает.** У неё нет картины вашей инфраструктуры. Она не знает, что у вас уже есть Redis, очередь сообщений и объектное хранилище — поэтому переизобретает их в памяти. Топология деплоя — это ровно тот контекст, который промпт не может вместить. **Как мы это чиним.** Мы составляем карту реального деплоя и переносим общее состояние туда, где ему место: кеш, сессии и локи в Redis или базу, файлы в объектное хранилище, периодические задачи на нормальный планировщик или очередь. В итоге код масштабируется горизонтально. --- ### Как мы их находим Каждая находка выше начинается с автоматического прохода и заканчивается человеком, читающим код. Автоматизация — статический анализ, сканирование зависимостей и секретов, профилирование расходов на API — это то, что делает AI-ускоренный аудит быстрым: она закрывает категории, механически поддающиеся обнаружению (находки 1, 4, 6 и 7 выше всплывают почти сразу), так что время наших инженеров концентрируется на категориях, требующих суждения — логике авторизации, архитектуре и том, действительно ли конкретный путь ошибки важен для вашего продукта. Ни одна половина не работает сама по себе: одна только автоматизация упускает всё, что требует понимания, для чего вообще нужен код, а одно только ручное ревью не масштабируется на реальную кодовую базу за неделю. Полный разбор процесса — на [странице сервиса аудита AI-кода](https://ru.nerdy.pro/services/ai-code-audit). ### Что вы получаете в отчёте Находки не приходят сырым списком. Каждая ранжирована по критичности — критическая, высокая, средняя, низкая — с понятным объяснением риска, proof of concept где применимо, и конкретной рекомендацией по исправлению — той же формы, что и каждая находка выше. Если хотите увидеть формат до того, как на что-то решиться, [запросите обезличенный пример отчёта об аудите](https://ru.nerdy.pro/contact?intent=ai-code-audit) — ту же структуру отчёта, что получается в реальном проекте, без данных, идентифицирующих клиента. ### Что стоит за всеми этими паттернами Сделайте шаг назад — и у одиннадцати находок обнаружится один общий корень: **AI оптимизирует каждую генерацию локально, а кодовую базу глобально не оптимизирует никто.** Управление секретами, валидация ввода, авторизация, тесты, обработка ошибок, производительность запросов, гигиена зависимостей, дедупликация, архитектура, моделирование состояния и учёт окружения развёртывания — всё это свойства *всей системы*. Они не могут возникнуть по одному промпту за раз, потому что ни один промпт не видит целого. Именно этот разрыв закрывает человеческое ревью. Обнадёживает то, что ничего из этого не значит, будто фундамент, собранный на AI, пропал зря. Фичи работают; продукт настоящий. Не хватает соединительной ткани — и добавить её куда быстрее, чем переписывать с нуля. --- ### Частые вопросы #### Что на самом деле находит аудит AI-кода? В десятках кодовых баз, написанных AI, почти каждый раз повторяются одиннадцать проблем: захардкоженные секреты и учётные данные, отсутствие валидации ввода, аутентификация, которая формально есть, но не проверяет запрос, ноль автоматических тестов, отсутствие обработки ошибок на несчастливом пути, N+1-запросы и производительность, отданная на волю случая, устаревшие зависимости с известными CVE, сильное дублирование переменных и функций, нет единой архитектуры по проекту, сложное асинхронное состояние реализовано как callback hell вместо стримов, и код написан без учёта окружения развёртывания. Конкретный код от проекта к проекту разный, но эти одиннадцать категорий всплывают снова и снова. #### Почему AI-код оставляет секреты и API-ключи открытыми? Захардкодить ключ — это работает сразу, а настроить менеджер секретов или подстановку переменных окружения — нет, и у модели нет причины предпочитать более медленный, правильный путь, когда быстрый тоже запускается. В результате — .env-файлы в репозитории, ключи захардкожены в коде, токены вшиты в клиентские бандлы. Всё, что хоть раз попало в git, нужно считать уже скомпрометированным и ротировать, а не просто удалять — старое значение остаётся действительным в каждом клоне и в каждой истории коммитов. #### Почему AI-код не валидирует пользовательский ввод? Валидация — это второй запрос, который модели явно никто не задавал. Она генерирует код, удовлетворяющий счастливому пути из промпта, а счастливый путь никогда не упоминает, что нужно отклонить, поэтому пользовательский ввод часто попадает прямиком в запрос, шаблон или промпт LLM без санитизации между ними. Это прямая причина SQL-инъекций, XSS и промпт-инъекций, которые всплывают почти в каждой проверенной нами AI-кодовой базе. #### Почему в коде от AI нет тестов? Потому что написать фичу и написать тесты к ней — это два разных запроса, и второй обычно не делают. AI-инструменты заточены под кратчайший путь к коду, который запускается, а это счастливый путь без набора тестов. Модель напишет тесты, если попросить, но сама по себе она выдаёт фичу и останавливается, оставляя каждый будущий деплой без страховки. #### Почему в приложениях на AI так много дублирования кода? Модель редко ищет в существующем коде хелпер, который могла бы переиспользовать. С её точки зрения дешевле перегенерировать функцию инлайн под нужный экран, чем найти и импортировать ту, что уже есть. Каждая генерация локально разумна, но в итоге одна и та же логика скопирована несколько раз с мелкими различиями, и это становится проблемой корректности в момент, когда логику нужно изменить. #### Почему сложные формы от AI часто работают неправильно? Код от AI склонен избегать стримовых и реактивных моделей состояния в пользу императивных колбэков. Для простых экранов это нормально, но сложное состояние вроде многошаговых форм с кросс-валидацией, дебаунсом или оптимистичными апдейтами сваливается в callback hell. Классический симптом — форма, которая почти работает: ошибка пропадает не в тот момент, двойной тап отправляет дважды. Моделирование состояния как стрима это чинит. #### Почему AI-код падает под реальной нагрузкой? Самая частая причина — паттерн N+1: один запрос за списком, затем отдельный запрос на каждую строку за связанными данными, так что экран, который должен стоить одного обращения к базе, стоит сотен. В разработке с горсткой строк выглядит корректно и всплывает только на реальном объёме данных, потому что ничто в процессе генерации не измеряет количество запросов. Отсутствующие индексы и неограниченные выборки без пагинации — обычные спутники. #### Почему код от AI ломается, когда работает более чем на одном инстансе? Потому что у модели нет картины того, как приложение развёрнуто. Она пишет код для одного процесса на одной машине, поэтому держит состояние в памяти и пишет файлы на локальный диск. На одном инстансе это работает, но ломается, как только приложение параллелится за балансировщиком: кеши в памяти, сессии и счётчики rate-limit расходятся между репликами, а фоновые задачи срабатывают на каждом инстансе вместо одного. Чинится переносом общего состояния в подходящие сервисы, чтобы приложение масштабировалось горизонтально. #### Нужно ли переписывать приложение на AI, чтобы починить эти проблемы? Нет. Все одиннадцать находок чинятся на месте. Секреты убираются и ротируются, ввод валидируется на каждой границе, проверки авторизации ставятся на каждый нужный эндпоинт, тесты добавляются там, где окупаются сильнее всего, пути отказа получают нормальную обработку ошибок, N+1-запросы схлопываются в батчевую загрузку, зависимости патчатся, дублированная логика выносится в единый источник правды, кодовая база сводится к одной согласованной архитектуре, фичи со сложным состоянием получают нормальный реактивный слой, а общее состояние переносится в подходящие сервисы. Это сохраняет рабочий фундамент, который дал AI, и чинит только недостающую соединительную ткань, что куда быстрее и дешевле переписывания. ### Получите находки по своей кодовой базе Если у вас приложение на AI и вы узнаёте хотя бы одну из этих одиннадцати проблем — вы не отстаёте, вы ровно там, где оказывается почти любая AI-кодовая база. Решение не в переписывании; это сфокусированный аудит, который добавляет соединительную ткань, которую AI не смог. Прогоните свою кодовую базу через [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit), [запросите пример отчёта об аудите](https://ru.nerdy.pro/contact?intent=ai-code-audit), чтобы сначала увидеть формат, или [закажите бесплатную оценку](https://ru.nerdy.pro/contact) — мы скажем, какая из одиннадцати проблем несёт для вас самый большой риск, что нужно, чтобы её починить, и дадим фиксированную смету, а не догадку. ## Как довести AI-прототип до продакшена: что ломается и как это починить https://ru.nerdy.pro/blog/ai-prototype-to-production **Коротко.** AI-инструмент соберёт вам рабочий прототип за выходные, но «работает в демо» и «безопасно запускать в продакшене» — это две разные вещи. В почти каждом приложении, собранном AI, всплывают одни и те же три проблемы: **дыры в безопасности** (открытые ключи, отсутствие авторизации, инъекции), **неконтролируемые расходы на API** (перерасход в 5–20 раз из-за вызовов без кеша, без батчинга и без лимитов) и **утечки данных** (чувствительная информация в логах, избыточные ответы API, передача данных сторонним сервисам). Прототип не выбрасывают — его проверяют аудитом, чинят эти три категории и выпускают. Этот пост — план действий: что ломается, почему и пошаговый путь от прототипа до продакшена. А если хотите передать это нам — именно этим и занимается [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit). Если нужен только процесс — переходите к [тому, как это выпустить](#%D0%BA%D0%B0%D0%BA-%D0%B4%D0%BE%D0%B2%D0%B5%D1%81%D1%82%D0%B8-ai-%D0%BF%D1%80%D0%BE%D1%82%D0%BE%D1%82%D0%B8%D0%BF-%D0%B4%D0%BE-%D0%BF%D1%80%D0%BE%D0%B4%D0%B0%D0%BA%D1%88%D0%B5%D0%BD%D0%B0). --- ### Прототип, который работает в демо и ломается в продакшене Вы сделали что-то настоящее. Cursor, Bolt, Lovable или долгая сессия с ChatGPT превратили идею в работающее приложение за дни, а не месяцы. Выглядит отлично. Работает на вашей машине. Вы показываете его ранним пользователям или инвесторам — и обратная связь хорошая. Поэтому вы вешаете его на домен, переключаете в продакшен и начинаете отправлять туда реальных людей. Вот тут и начинаются проблемы — и не потому, что вы сделали что-то не так. AI-агенты невероятно хорошо пишут код, который *работает*. И плохо — код, *готовый к продакшену*, потому что это разные цели. «Работает» означает, что отрабатывает счастливый путь. «Готов к продакшену» означает, что код выдерживает атаку, всплеск трафика, кривой ввод и биллинговый цикл — а ничего из этого в демо не бывает. Это не маргинальная проблема. [Veracode протестировала 100+ LLM](https://www.veracode.com/blog/genai-code-security-report/) и обнаружила, что 45% сгенерированного кода не проходит проверки безопасности. [Анализ Apiiro за 2025 год](https://apiiro.com/blog/4x-velocity-10x-vulnerabilities-ai-coding-assistants-are-shipping-more-risks/) показал, что разработчики с AI раскрывают учётные данные почти вдвое чаще. [Исследователи из Стэнфорда](https://ee.stanford.edu/dan-boneh-and-team-find-relying-ai-more-likely-make-your-code-buggier) выяснили, что разработчики с AI-ассистентами пишут *менее* безопасный код, будучи при этом *более* уверенными в его безопасности. Именно этот разрыв в уверенности и опасен: демо выглядит законченным, поэтому те 20%, что не закончены, остаются невидимыми, пока не обойдутся вам дорого. Хорошая новость в том, что разрыв предсказуем. После аудита десятков сгенерированных AI кодовых баз сбои сводятся к трём категориям — тем самым трём, которые прототипу просто незачем было закрывать. --- ### Три вещи, которые ломаются #### 1. Дыры в безопасности AI-инструменты оптимизируются под кратчайший путь к работающей функции, а кратчайший путь почти никогда не бывает безопасным. - **Открытые ключи и секреты.** API-ключи, учётные данные баз данных и токены сервисов зашиваются в исходники, попадают в клиентские бандлы или коммитятся в `.env`-файлы. Один утёкший сторонний ключ может за часы обернуться тысячами долларов несанкционированных списаний — или отдать атакующему ваше хранилище данных. - **Аутентификация без авторизации.** Экран логина есть, поэтому *кажется*, что безопасно. Но бэкенд часто не проверяет, кому что вообще разрешено. Эндпоинты принимают любой запрос; поменяйте ID в URL — и вы читаете чужие записи. Это — нарушенная авторизация на уровне объектов — самый частый дефект, который мы находим, и он не зря стоит первым в [списке OWASP API Security](https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/). - **Инъекции.** SQL-инъекции, XSS и prompt injection в AI-коде повсюду, потому что модели охотно подставляют пользовательский ввод прямо в запросы, HTML или промпты LLM. Один неочищенный эндпоинт может раскрыть всю базу или позволить атакующему управлять вашим AI. - **Небезопасное хранение.** Персональные данные в открытом виде, токены сессий в `localStorage`, пароли, захешированные через MD5 или не захешированные вовсе. AI выбирает простейшую реализацию, которая редко бывает безопасной. (Мы написали полный разбор того, [как данные должны на самом деле шифроваться при хранении и передаче](https://ru.nerdy.pro/blog/encryption-explained) — именно на неё мы отправляем клиентов.) #### 2. Неконтролируемые расходы на API Эта проблема не взламывает продукт, а тихо его разоряет. - **Избыточные вызовы.** Приложения, собранные AI, дёргают один и тот же платный эндпоинт — геокодинг, курсы валют, LLM, API верификации — снова и снова ради данных, которые у них уже есть. Ни кеша, ни мемоизации. Мы регулярно находим приложения, где 60–80% трат на API — чистые потери. - **Нет бюджетов и лимитов.** Ни лимита на пользователя, ни дневного потолка, ни предохранителя. Один увлечённый пользователь, скрейпер или бот может сжечь весь месячный бюджет за полдня. У AI нет понятия о ваших тарифах или runway, поэтому он никогда не ставит ограничители. - **Нет батчинга.** Отдельные запросы в цикле вместо батч-эндпоинта, который предлагает провайдер. На масштабе демо это незаметно; на масштабе продакшена — пятизначный счёт в месяц. [Стоимость разработки приложения](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026) — это одно, а стоимость *эксплуатации* неоптимизированного — тот сюрприз, который застаёт основателей врасплох. #### 3. Утечки данных Категория, превращающая тихий успех в инцидент с комплаенсом. - **Логирование чувствительных данных.** AI обожает многословное логирование. Email, пароли, платёжные данные и персональная информация оседают в логах приложения и трекерах ошибок, часто хранятся бессрочно и доступны любому, у кого есть доступ к дашборду. - **Избыточные ответы API.** Эндпоинты возвращают весь объект пользователя — хешированные пароли, внутренние ID, метаданные — когда фронтенду нужно было только отображаемое имя. GraphQL-эндпоинты без ограничения глубины, позволяющие атакующему пройтись по всей вашей модели данных. - **Передача данных сторонним сервисам.** Аналитика, мониторинг и трекеры ошибок подключаются без мысли о том, что в них утекает. Поведение пользователей и персональные данные покидают вашу систему без согласия — ровно то, под что писались штрафы GDPR. --- ### Как довести AI-прототип до продакшена Вы не переписываете заново — это выбрасывает те 80%, что AI сделал правильно. Вы проверяете прототип аудитом, чините три категории выше в порядке приоритета и выпускаете. Вот путь, по которому мы работаем. #### Шаг 1 — Заморозить и определить объём Первый порыв после удачного демо — добавить ещё функций. Сдержитесь. Добавление кода к непроверенной базе лишь умножает поверхность, которую придётся чинить. Зафиксируйте объём, выдайте доступ на чтение и решите, что покрывает проверка — полный проход или сфокусированный на той из областей, что пугает больше всего. В продакшене пока ничего не меняется. #### Шаг 2 — Автоматическое сканирование Статический анализ, сканирование зависимостей и секретов, профилирование расходов быстро ловят очевидные проблемы: закоммиченный API-ключ, пакет с известным CVE, эндпоинт, который дёргают 300 раз в минуту. Это дешёвый слой с широким охватом. #### Шаг 3 — Ручная экспертная проверка Дорогие проблемы прячутся там, куда инструменты не заглядывают: эндпоинт, возвращающий правильные данные, но никогда не проверяющий, *кто спрашивает*; цикл начисления оплаты, корректный, но без кеша; сторонняя интеграция, тихо отправляющая PII наружу. Человек читает архитектуру, потоки авторизации и пути данных. Именно этот шаг отличает [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit) от линтера. #### Шаг 4 — Приоритизированные исправления Находки сортируются по критичности и чинятся по порядку. Сначала критические дыры в безопасности — утёкший ключ или обход авторизации — это ЧП. Затем контроль расходов, потому что каждый день при перерасходе в 5–20 раз — это реальные деньги. Затем приватность и комплаенс. Вы просматриваете и одобряете каждое изменение перед слиянием; с вашим кодом ничего не происходит без вашего согласия. #### Шаг 5 — Выпуск с уверенностью Перепроверьте по каждой находке, убедитесь, что патчи держатся, и разверните. Тот же продукт, тот же фундамент, собранный AI, — теперь укреплённый. В этом и весь смысл: вы сохраняете скорость, которую дал AI, и добавляете надёжность, которую он дать не смог. > Мы прогоняли ровно это на реальных проектах. [Arcana](https://ru.nerdy.pro/portfolio/arcana), клиент стримингового AI-чата, прошла путь от прототипа до выпущенного приложения продакшен-уровня — и именно незаметная работа по укреплению стриминга, авторизации и обработки данных превратила демо в то, на что люди действительно могут положиться. --- ### Сколько это стоит и сколько занимает Сфокусированный проход по безопасности небольшого приложения из одного сервиса занимает **1–2 недели**; полный аудит готовности к продакшену — безопасность, расходы, данные, производительность, инфраструктура — для типичного многосервисного приложения это **2–4 недели**. Это на порядок быстрее и дешевле, чем 3–6 месяцев на переписывание с нуля, потому что вы сохраняете всё, что AI сделал правильно, и чините только то, что он сделал не так. Полезная ментальная модель: **AI проводит вас на 80% пути за 5% времени.** Последние 20% — безопасность, контроль расходов, приватность данных, обработка ошибок — это и есть вся разница между демо и продуктом. Аудит закрывает эти 20%, не выбрасывая фундамент. Полные тарифы и цены — на странице [аудита AI-кода](https://ru.nerdy.pro/services/ai-code-audit#%D1%86%D0%B5%D0%BD%D1%8B-%D0%BD%D0%B0-%D0%B0%D1%83%D0%B4%D0%B8%D1%82-ai-%D0%BA%D0%BE%D0%B4%D0%B0). --- ### Частые вопросы #### Можно ли вывести прототип из Cursor, Bolt или Lovable в продакшен как есть? Редко без изменений. Прототип почти наверняка работает на счастливом пути, но AI-инструменты систематически пропускают то, что важно только в продакшене: проверки авторизации, контроль расходов на API, безопасное хранение данных и работу с приватностью. Решение — не переписывание, а аудит, который находит эти пробелы и чинит их, обычно за одну–четыре недели, сохраняя код, который AI уже сделал правильно. #### Что такое аудит AI-кода? Аудит AI-кода — это структурированный разбор сгенерированной AI кодовой базы, который находит и чинит проблемы, систематически пропускаемые AI-инструментами: уязвимости безопасности, неконтролируемые расходы на API и утечки данных. Он сочетает автоматическое сканирование (секреты, зависимости, статический анализ) с ручной экспертной проверкой архитектуры, аутентификации и потоков данных, а затем выдаёт приоритизированные исправления — или внедряет их за вас. #### Почему в AI-коде так много проблем с безопасностью? AI-модели оптимизируются под кратчайший путь к работающему коду, а этот путь почти никогда не бывает безопасным. Они зашивают секреты, делают аутентификацию без настоящей авторизации, подставляют пользовательский ввод в запросы и промпты и хранят данные в открытом виде. Независимые исследования Veracode, Apiiro и Стэнфорда показали, что AI-код измеримо менее безопасен — при этом разработчики увереннее в его безопасности. #### Насколько сильно AI-прототип может перерасходовать на API? Обычно в 5–20 раз больше необходимого. Частые причины — вызовы одного и того же платного эндпоинта без кеширования, отдельные запросы в цикле вместо батчинга и отсутствие лимитов на пользователя или дневных бюджетных потолков. Мы видели, как месячные счета за API падали с тысяч долларов до нескольких сотен после добавления этих мер. #### Нужно ли переписывать приложение, чтобы сделать его готовым к продакшену? Нет, и не стоит. Переписывание выбрасывает те 80%, что AI сделал правильно, и стоит трёх–шести месяцев. Аудит сохраняет этот фундамент и чинит только те 20%, что ломаются в продакшене — безопасность, расходы, приватность и обработку ошибок — за одну–четыре недели. Вы выпускаете тот же продукт — только укреплённый. #### Не приведёт ли аудит к простою приложения? Нет. Аудит просматривает исходный код, а в стейджинг-окружении запускает только проверки без записи данных, так что ваше живое приложение продолжает работать как обычно. Если найдена критическая уязвимость, создающая непосредственный риск, вас уведомляют сразу, чтобы вы решили, как реагировать. ### Выпустите то, что уже построили Вы сделали трудную часть — нашли то, что стоит строить, и заставили это работать. Не позволяйте невидимым 20% превратить настоящий успех во взлом, неожиданный счёт или проблему с комплаенсом. Прогоните прототип через [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit), почините три вещи, которые ломаются, и уверенно показывайте его реальным пользователям. Если у вас есть собранное AI-приложение, которое страшно разворачивать, [запишитесь на бесплатную оценку](https://ru.nerdy.pro/contact). Мы скажем, какая из трёх областей — ваш главный риск, что потребуется для исправления, и дадим смету с фиксированным объёмом работ — а не догадку. ## Flutter против нативной разработки (iOS/Android) в 2026 году: когда кросс-платформа выигрывает, а когда нет https://ru.nerdy.pro/blog/flutter-vs-native-2026 Мы — Flutter-агентство, поэтому от нас можно было бы ожидать «Flutter, всегда». Это не так. Мы выпускали и то, и другое, и не раз советовали клиентам нативную разработку, когда это был правильный выбор. Это честная версия сравнения — что реально различается в 2026 году, с реальными цифрами и правилом принятия решения, которое можно применить за пять минут. Если вы выбираете между Flutter и React Native — это другой вопрос, мы разбираем его в посте [Flutter vs React Native в 2026 году](https://ru.nerdy.pro/blog/flutter-vs-react-native-2026). Этот пост — про Flutter против **нативной** разработки (Swift/SwiftUI на iOS, Kotlin/Jetpack Compose на Android). ### Короткий ответ Для подавляющего большинства приложений в 2026 году **Flutter — более выгодное бизнес-решение**: одна кодовая база под iOS, Android и web, стоимость разработки примерно на **30–40% ниже**, чем у двух нативных приложений, и одна команда для поддержки. Нативная разработка выигрывает в более узком наборе случаев — в приложениях, где платформа *и есть* продукт: тяжёлый AR/VR, сложный on-device ML, глубокая интеграция с ОС, игры или приложения, где доступ к новейшим возможностям ОС в день релиза — конкурентное требование. Правило принятия решения: **если ваше конкурентное преимущество — это продукт и скорость его выпуска, выбирайте Flutter. Если преимущество в том, чтобы выжать максимум из железа или самой ОС, выбирайте нативную разработку.** ### Flutter против нативной разработки: краткий обзор | Параметр | Flutter | Нативная (iOS + Android) | | --- | --- | --- | | Кодовых баз для разработки и поддержки | 1 | 2 | | Типичная стоимость разработки | Базовая | ~в 1,5–1,8 раза выше | | Время до выпуска на обеих платформах | Самое быстрое | Самое медленное (две команды, два графика) | | Производительность в рантайме | Отличная для ~95% приложений | Максимально возможная | | Соответствие нативному UX/UI | Близко к нативному, полностью кастомное | Полностью нативный вид из коробки | | Доступ к новейшим возможностям ОС | Отставание на часы–недели (через плагины) | В день релиза | | Команда и найм | Одна Flutter-команда | iOS-команда + Android-команда | | Лучше всего для | Большинства продуктовых приложений, MVP, e-commerce, финтеха, контента, внутренних инструментов | Игр, AR/VR, тяжёлого on-device ML, системных утилит | ### Какова реальная разница в стоимости? Разработка двух нативных приложений означает две кодовые базы, часто две команды (iOS- и Android-специалисты редко пересекаются) и два набора багов, которые нужно чинить на каждую фичу. Flutter сводит это к одному. В наших проектах Flutter обходится на **30–40% дешевле**, чем эквивалентные два нативных приложения, — и разрыв *увеличивается* в течение жизни приложения, потому что каждая будущая фича и каждое исправление пишутся один раз, а не дважды. Откуда именно берутся эти 40% (и где основатели их теряют) мы разобрали в посте [Как на самом деле выглядит снижение стоимости мобильной разработки на 40%](https://ru.nerdy.pro/blog/mobile-cost-reduction-case-studies). Ориентировочные цены по уровням сложности — в посте [Стоимость разработки приложения на Flutter в 2026 году](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026). Преимущество в стоимости минимально для приложения под одну платформу (если iOS — единственная платформа, которая вам когда-либо понадобится, нативный Swift конкурирует на равных) и максимально для всего, что должно работать на iOS, Android и web одновременно. ### Достаточно ли хороша производительность Flutter? Примерно для 95% приложений — да, и пользователи не заметят разницы. Flutter компилируется в нативный ARM-код и отрисовывает свой UI собственным движком (той же линии Skia/:term{slug="impeller"}, что лежит в основе Chrome и Android), поэтому это не webview и он не упирается в мост, как старые кросс-платформенные инструменты. Где у нативной разработки всё ещё есть измеримое преимущество: стабильные 60–120fps под тяжёлой графической нагрузкой, обработка камеры/видео в реальном времени, инференс крупных on-device ML-моделей и AR/VR. Если основной цикл вашего приложения — это что-то из перечисленного, прямой доступ нативного кода к Metal, Core ML или стеку камеры стоит дополнительных затрат. Для CRUD-приложения, маркетплейса, банковского приложения, приложения доставки или контентного приложения это преимущество незаметно. ### Когда стоит выбрать нативную разработку? Будьте честны с собой — вот случаи, когда мы бы посоветовали идти в нативную разработку: - **Игры и приложения с тяжёлой графикой.** Используйте игровой движок или нативную разработку, а не Flutter. - **AR/VR-приложения**, которые глубоко опираются на ARKit/ARCore. - **Тяжёлый on-device ML** (крупные модели, инференс в реальном времени), где важен доступ к Core ML / NNAPI. - **Системные утилиты** — виджеты-как-продукт, глубокая интеграция с Apple Watch / Wear OS, CarPlay/Android Auto, системные расширения. - **Вам нужно поддержать совершенно новую возможность ОС в день релиза Apple или Google** — как конкурентное требование. - **Только одна платформа навсегда** — если у вас действительно никогда не будет ничего, кроме iOS, нативный Swift убирает слой абстракции, и кросс-платформенного выигрыша, который его компенсировал бы, нет. ### Когда Flutter — очевидный победитель? - **Вам нужны iOS и Android (и, возможно, web) из одного бюджета и силами одной команды.** - **Вы делаете MVP** и хотите быстро проверить гипотезу, не финансируя две параллельные разработки. - **E-commerce, финтех, доставка еды, контент, бронирование, внутренние/B2B-инструменты** — категории, где живёт большинство приложений. - **Вы мигрируете с Telegram-бота или web-приложения** и хотите быстро получить полноценное приложение (см. [Из Telegram-бота в приложение за 6 недель](https://ru.nerdy.pro/blog/telegram-bot-to-app-6-weeks)). - **Вам нужна консистентность дизайна** между платформами и полный контроль над кастомным брендированным UI. ### Выглядит и ощущается ли Flutter-приложение нативным? Да — когда оно сделано хорошо. Flutter может отрисовывать виджеты Material (Android) и Cupertino (iOS), так что приложение следует гайдлайнам каждой платформы, а поскольку он рисует собственные пиксели, у вас есть полная свобода для кастомного брендированного UI. Проблема не во Flutter — проблема в команде, которая делает один и тот же обезличенный интерфейс на обеих платформах и игнорирует жесты платформы, паттерны навигации и системные шрифты. Это проблема мастерства, а не ограничение фреймворка. ### Что насчёт долгосрочной поддержки и рисков платформы? Поддержка — это место, где одна кодовая база окупается сильнее всего: одно место для исправления багов, одно дерево зависимостей, один CI-пайплайн, один набор миграций под обновления ОС вместо двух. Эта накопительная экономия обычно больше, чем разница в стоимости первоначальной разработки. Справедливый контраргумент — это **риск зависимости**: Flutter полагается на Google, а в платформенных возможностях — на плагины от сообщества. Послужной список Google неоднозначен (Firebase Dynamic Links закрыли в 2025 году, о чём мы писали в посте [Отложенный диплинкинг](https://ru.nerdy.pro/blog/deferred-deep-linking-app-clips-install-referrer)). Но сам Flutter сегодня — зрелый, широко используемый open-source-фреймворк с глубокой экосистемой пакетов, и разрыв в плагинах для массовых возможностей фактически закрылся. Риск реален, но для типичных приложений невелик. ### Найм и последствия для команды Нативная разработка означает, что нанимать — или брать на подряд — придётся два разных набора навыков: инженеров Swift/SwiftUI и инженеров Kotlin/Compose. Они не заменяют друг друга, и фича не готова, пока её не выпустят обе команды. Flutter требует одной команды, пишущей на Dart, — её быстрее координировать и дешевле масштабировать. Компромисс: пул сильных senior-специалистов по Flutter меньше, чем пулы iOS или Android по отдельности, поэтому выбор команды, которая действительно знает фреймворк, важнее. ### Схема принятия решения: ответьте на четыре вопроса 1. **Ваша ключевая фича — про выжимание максимума из железа или ОС** (графика, AR, on-device ML, системная интеграция)? Если да → склоняйтесь к нативной разработке. Если нет → Flutter. 2. **Нужны ли вам и iOS, и Android?** Если да → преимущество Flutter велико. Если платформа действительно навсегда останется одна → нативная разработка конкурирует на равных. 3. **Как быстро вам нужно быть на рынке?** Быстрее → Flutter. Время некритично, а производительность критична → можете позволить себе нативную разработку. 4. **Каков ваш бюджет на поддержку в ближайшие 2–3 года?** Жёстче → одна кодовая база Flutter выигрывает по стоимости владения. Если три или четыре ответа указывают на Flutter — решение принято. ### Честный итог Нативная разработка даёт лучшее *возможное* приложение. Flutter даёт лучшее приложение *для большинства бизнесов* — потому что «лучшее возможное» редко оправдывает переплату в ~1,5–1,8 раза и содержание двух команд, когда пользователи не видят разницы. Выбирайте нативную разработку, когда платформа и есть продукт. Выбирайте Flutter почти во всём остальном. ### FAQ #### Flutter лучше нативной разработки в 2026 году? Для большинства бизнес- и потребительских приложений — да: Flutter даёт близкую к нативной производительность и UX примерно на 30–40% дешевле с одной кодовой базой. Нативная разработка лучше только для приложений с тяжёлой графикой, AR/VR или интенсивной работой с железом и для системных утилит. #### Flutter так же быстр, как нативная разработка? Для ~95% приложений разница в производительности незаметна — Flutter компилируется в нативный код и отрисовывается собственным движком. Нативная разработка сохраняет преимущество при длительной тяжёлой графической нагрузке, работе с видео/камерой в реальном времени и крупном on-device ML. #### Насколько Flutter дешевле, чем разработка двух нативных приложений? Обычно на 30–40% дешевле в разработке, и разрыв растёт со временем, потому что каждая будущая фича и исправление пишутся один раз, а не дважды. #### Используют ли Flutter крупные компании? Да — Flutter используется в продакшене крупными потребительскими и финтех-приложениями по всему миру и является зрелым open-source-фреймворком при поддержке Google с глубокой экосистемой плагинов. #### Выглядит ли Flutter-приложение нативным на iOS и Android? Оно может отрисовывать UI, соответствующий платформе (Cupertino на iOS, Material на Android), и поддерживает полностью кастомный брендинг. «Ненативный» вид — признак спешки при разработке, а не ограничение фреймворка. #### Когда выбрать нативную разработку вместо Flutter? Выбирайте нативную разработку для игр, AR/VR, тяжёлого on-device ML, системных утилит, доступа к новейшим возможностям ОС в день релиза или если вы будете выпускать приложение только под одну платформу. --- *Nerdy Production — Flutter-агентство, создающее приложения в финтехе, здравоохранении и ритейле. Если вы выбираете между Flutter и нативной разработкой для конкретного проекта, [расскажите нам о нём](https://ru.nerdy.pro/contact) — мы дадим честную рекомендацию, даже если это «идите в нативную разработку». Смотрите также нашу услугу [разработки приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development).* ## Отложенный deep linking после Firebase Dynamic Links: App Clips + Play Install Referrer https://ru.nerdy.pro/blog/deferred-deep-linking-app-clips-install-referrer Пользователь нажимает на вашу ссылку — товар, которым поделились, приглашение, сброс пароля — а приложение ещё не установлено. Он попадает в App Store или Google Play, устанавливает приложение, открывает его и… оказывается на обычном главном экране. Контекст того, на что он нажал, потерян. Именно этот разрыв в пользовательском опыте призван устранить **отложенный deep linking**, и долгие годы большинство команд решало это через [Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links). Этого варианта больше нет. В статье разбираем, как мы построили систему отложенного deep linking, которая переживает установку — и при необходимости умеет дождаться бизнес-события вроде логина, прежде чем выполнить переход, — используя **iOS App Clips** и **Google Play Install Referrer API** вместо стороннего сервиса. ### Ключевые выводы - **Отложенный deep linking** сохраняет цель ссылки через установку приложения, чтобы первый запуск открывал нужный экран, а не главный. - **Firebase Dynamic Links объявлен устаревшим и отключён 25 августа 2025 года** — самое распространённое решение исчезло, и существующие ссылки больше не работают. - Популярные самодельные обходные пути — **фингерпринтинг устройства** и **сопоставление через буфер обмена** — ненадёжны, сомнительны с точки зрения приватности и не одобряются Apple. - На **iOS** лёгкий **App Clip** перехватывает ссылку ещё до того, как появится полное приложение, и передаёт её через общий контейнер **App Group** после установки. - На **Android** официальный **[Play Install Referrer API](https://developer.android.com/google/play/installreferrer)** детерминированно доставляет полезную нагрузку ссылки при первом запуске. - **Очередь отложенных ссылок** позволяет отложить переход до тех пор, пока не завершится установка *и* не наступит опциональное бизнес-событие (например, успешный логин). ### Что такое отложенный deep linking? **Deep link** открывает конкретный экран внутри приложения — например `myapp://product/42`. Deep link работает, только если приложение уже установлено. **Отложенный deep linking** снимает это условие: когда приложение *не* установлено, цель запоминается на протяжении визита в стор и установки, и приложение переходит к ней при первом запуске. Самое сложное — это разрыв. Между нажатием и первым запуском операционная система устанавливает новое приложение, которое ничего не знает о том, на что нажал пользователь. Пронести полезную нагрузку через этот разрыв — надёжно и без вторжения в приватность — и есть вся задача. ### Стандартного решения больше нет: Firebase Dynamic Links Большую часть последнего десятилетия ответом по умолчанию был [Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links). Он брал на себя редирект в стор, отложенную полезную нагрузку и разрешение при первом запуске для обеих платформ через один SDK. Google [объявил Dynamic Links устаревшим](https://firebase.google.com/support/dynamic-links-faq) и **полностью отключил сервис 25 августа 2025 года**. Ссылки перестали разрешаться, и готовой замены от Google нет. Платные платформы атрибуции вроде [Branch](https://www.branch.io) и AppsFlyer закрывают эту нишу коммерчески, но многие команды хотят владеть процессом без счёта за каждое событие и без отправки кликов пользователей третьей стороне. > **Firebase Dynamic Links больше не вариант** > > Если ваш отложенный deep linking всё ещё зависит от Firebase Dynamic Links, он перестал работать 25 августа 2025 года. Переход на [Universal Links](https://developer.apple.com/ios/universal-links/) и [Android App Links](https://developer.android.com/training/app-links) закрывает случай *установленного приложения* — но **не** отложенный случай (когда приложение ещё не установлено). Именно этот пробел и закрывает наш подход. ### Распространённые самодельные обходные пути — и их подводные камни Когда Dynamic Links отпадает, большинство руководств хватается за один из двух самодельных приёмов. Их стоит понимать именно потому, что они хрупкие. **Фингерпринтинг устройства.** Веб-страница записывает «отпечаток» (IP-адрес, размер экрана, версия ОС, локаль) в момент нажатия. При первом запуске приложение отправляет собственный отпечаток на сервер, который пытается сопоставить их в коротком временном окне. Подводные камни серьёзны: общие/операторские IP вызывают несовпадения, окно сопоставления короткое, точность падает в нагруженных сетях, а вероятностное сопоставление пользователей — ровно тот паттерн, против которого направлены правила приватности Apple. **Буфер обмена.** Веб-страница копирует полезную нагрузку в буфер обмена; приложение читает её при запуске. Начиная с iOS 14 это вызывает видимый баннер «вставлено из Safari», пользователь может очистить буфер, а любое копирование в промежутке уничтожает полезную нагрузку. | Подход | Платформа | Надёжность | Приватность | Статус | | --- | --- | --- | --- | --- | | Firebase Dynamic Links | iOS + Android | Высокая | Приемлемая | **Отключён в авг. 2025** | | Фингерпринтинг устройства | iOS + Android | Низкая–средняя | Плохая | Не рекомендуется | | Буфер обмена | iOS | Средняя | Показывает баннер вставки | Хрупкий | | **App Clip + App Group (наш)** | iOS | Высокая | Хорошая | Рекомендуется | | **Play Install Referrer API** | Android | Высокая | Хорошая | Официальный | ### Наш подход: App Clips на iOS, Install Referrer на Android Вместо того чтобы угадывать, кто пользователь, уже после установки, мы перехватываем ссылку *детерминированно* на каждой платформе нативным механизмом, а затем сходимся к единому шагу разрешения внутри приложения. #### iOS: перехват ссылки через App Clip [App Clip](https://developer.apple.com/documentation/app_clips) — это крошечная часть вашего приложения (до 15 МБ), которая почти мгновенно запускается по ссылке, App Clip Code или QR-коду — **без полной установки**. Это свойство нам и нужно: App Clip выполняется *до* того, как появится полное приложение, поэтому он видит исходную ссылку. Как это работает: 1. Ссылка открывает App Clip. iOS доставляет URL вызова через `NSUserActivity`. 2. App Clip записывает этот URL (и метку времени) в общий контейнер **[App Group](https://developer.apple.com/documentation/xcode/configuring-app-groups)**, который может читать и полное приложение. 3. App Clip показывает оверлей App Store с предложением установить полное приложение. 4. После установки полное приложение читает отложенную ссылку из того же контейнера App Group при первом запуске. ```swift // App Clip — перехват URL вызова в общий App Group func scene(_ scene: UIScene, continue activity: NSUserActivity) { guard activity.activityType == NSUserActivityTypeBrowsingWeb, let url = activity.webpageURL else { return } let shared = UserDefaults(suiteName: "group.pro.nerdy.deeplink") shared?.set(url.absoluteString, forKey: "pendingDeepLink") shared?.set(Date(), forKey: "pendingDeepLinkAt") } ``` ```swift // Полное приложение — читаем один раз, при первом запуске после установки let shared = UserDefaults(suiteName: "group.pro.nerdy.deeplink") if let link = shared?.string(forKey: "pendingDeepLink") { DeepLinkQueue.shared.enqueue(link) shared?.removeObject(forKey: "pendingDeepLink") } ``` Поскольку App Clip и полное приложение используют общий App Group, полезная нагрузка передаётся точно — без сопоставления отпечатков, без баннера буфера обмена, без вероятностных догадок. #### Android: Google Play Install Referrer API У Android есть чистый официальный ответ: [Play Install Referrer API](https://developer.android.com/google/play/installreferrer/library). Прикрепите свою полезную нагрузку к URL Play Store как параметр `referrer`, и Google доставит эту строку приложению при первом запуске. ```text https://play.google.com/store/apps/details?id=pro.nerdy.app&referrer=deeplink%3D%2Fproduct%2F42 ``` ```kotlin val client = InstallReferrerClient.newBuilder(context).build() client.startConnection(object : InstallReferrerStateListener { override fun onInstallReferrerSetupFinished(responseCode: Int) { if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) { val referrer = client.installReferrer.installReferrer // "deeplink=/product/42" DeepLinkQueue.enqueue(referrer) client.endConnection() } } override fun onInstallReferrerServiceDisconnected() {} }) ``` Это детерминированно — строка referrer переживает визит в стор и установку нетронутой, без серверного сопоставления. #### Сходимся в одном месте — и ждём логина Обе платформы теперь пишут в одну и ту же внутреннюю **очередь отложенных ссылок**. Именно это мы построили для нашей [платформы кэшбэка и лояльности](https://ru.nerdy.pro/portfolio/cashback-loyalty-platform), и в нашей [разработке приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development) мы делаем это через небольшой platform channel, который прокидывает нативную полезную нагрузку в Dart, а затем единый резолвер решает, *когда* действовать. «Когда» здесь важно. Deep link на аутентифицированный экран (`/orders/42`) упадёт при холодном старте, если пользователь ещё не залогинен. Поэтому резолвер не переходит сразу — он удерживает цель до тех пор, пока приложение не будет готово *и* не наступит требуемое бизнес-событие. ```dart class DeepLinkQueue { Uri? _pending; bool _authReady = false; void enqueue(Uri link) { _pending = link; _tryResolve(); } // Вызывается бизнес-слоем, например после успешного логина void onEvent(AppEvent event) { if (event == AppEvent.loggedIn) _authReady = true; _tryResolve(); } void _tryResolve() { final link = _pending; if (link == null || !_authReady) return; // ждём оба условия _pending = null; router.go(link.path); // безопасно: приложение установлено И пользователь залогинен } } ``` Результат — отложенный deep link, устойчивый к двум сценариям отказа, которые ломают большинство реализаций: приложение не установлено и целевой экран не готов. ### Когда этот подход уместен Этот паттерн хорош, когда вы контролируете и источник ссылки, и приложение, хотите нативной надёжности и заботитесь о приватности или стоимости вендора. Если нужен только случай *установленного приложения*, достаточно обычных [Universal Links](https://developer.apple.com/ios/universal-links/) и [App Links](https://developer.android.com/training/app-links). Если нужна кросс-канальная маркетинговая атрибуция с дашбордами, платная платформа вроде Branch может оправдать затраты. Но для продуктовых сценариев — приглашения, контент, которым делятся, онбординг, сброс пароля — App Clips плюс Install Referrer API дают детерминированный отложенный deep link, которым вы полностью владеете. Если вы мигрируете с Firebase Dynamic Links или строите это с нуля, [мы поможем вам это запустить](https://ru.nerdy.pro/contact). ### Часто задаваемые вопросы #### Что такое отложенный deep linking? Отложенный deep linking сохраняет цель ссылки через установку приложения. Когда пользователь нажимает ссылку без установленного приложения, цель запоминается, пользователь устанавливает приложение, и при первом запуске приложение переходит к исходной цели вместо обычного главного экрана. #### Почему Firebase Dynamic Links перестал работать? Google объявил Firebase Dynamic Links устаревшим и отключил сервис 25 августа 2025 года. Существующие ссылки перестали разрешаться, поэтому любому продукту, который на него полагался, нужна замена для отложенного deep linking. #### Как App Clips обеспечивают отложенный deep linking на iOS? App Clip мгновенно запускается по ссылке без полной установки, перехватывает URL вызова и записывает его в общий контейнер App Group. После того как пользователь устанавливает полное приложение, оно читает сохранённую ссылку при первом запуске и направляет пользователя к исходной цели. #### Надёжен ли Google Play Install Referrer API для отложенных deep link? Да. Install Referrer API — это официальный API Google, который доставляет строку referrer, прикреплённую к ссылке Play Store, приложению при первом запуске. Он детерминирован, в отличие от фингерпринтинга устройства, поэтому полезная нагрузка deep link приходит нетронутой. #### Можно ли отложить deep link до момента логина пользователя? Да. Сохраните разрешённую ссылку в очереди ожидания и выполняйте переход только после наступления требуемого бизнес-события, например успешного логина. Это позволяет направлять пользователей на аутентифицированные экраны, которые иначе упали бы при холодном старте. ## У вас Telegram-бот с 50 000 пользователей — вот как выпустить его отдельным приложением за 6 недель https://ru.nerdy.pro/blog/telegram-bot-to-app-6-weeks **Коротко.** Telegram-бот с реальной базой пользователей и бэкендом за ним можно превратить в выпущенное приложение для iOS и Android примерно за шесть недель — не потому что работа тривиальна, а потому что сложный продуктовый вопрос (нужно ли это кому-то?) уже решён. Этот пост — понедельный план: что и когда делается, куда на самом деле уходят шесть недель, как мигрировать 50 000 пользователей, не потеряв их, и что именно превращает шесть недель в четыре месяца. Если хотите сначала проверить себя — на [странице услуги «из бота в приложение»](https://ru.nerdy.pro/services/chatbot-to-app) есть чек-лист готовности. Если нужен только график — переходите к [плану на шесть недель](#%D0%BF%D0%BB%D0%B0%D0%BD-%D0%BD%D0%B0-%D1%88%D0%B5%D1%81%D1%82%D1%8C-%D0%BD%D0%B5%D0%B4%D0%B5%D0%BB%D1%8C-%D0%BF%D0%BE-%D0%BD%D0%B5%D0%B4%D0%B5%D0%BB%D1%8F%D0%BC). --- ### Исходная точка Посмотрите на своё положение со стороны — оно хорошее. Год-два назад вы запустили Telegram-бота, чтобы проверить идею. Сработало. Теперь у вас 50 000 пользователей, которые его открывают, используют и иногда платят. Бот делает что-то настоящее: фитнес-коуч, AI-ассистент, финансовый трекер, языковой репетитор, маркетплейс — категория для этого поста неважна. Важно, что у вас есть **product-market fit на платформе, которой вы не владеете**. И платформа начинает мешать. Пользователи просят то, чего бот не умеет. Вы не можете отправить push так, чтобы он не утонул в приглушённом канале. Вы не можете брать оплату так, как хотите. Изменение в [Telegram Bot API](https://core.telegram.org/bots/api) сломало флоу в прошлом квартале, и вы потратили выходные на тушение пожара. Вы успешны *вопреки* платформе и упираетесь в потолок. Поэтому вам нужно приложение. Вопрос, который задаёт каждый основатель в этой позиции, один и тот же: *сколько времени и как не потерять 50 000 пользователей, которые уже есть?* Честный ответ для большого класса ботов — **шесть недель до выпущенного приложения** плюс постепенная миграция, которая идёт параллельно и завершается, когда вы сами решите. Вот почему эта цифра реалистична и куда именно уходит время. > Разбор ниже — собирательный: это план, который мы по частям выполняли на реальных чат-ориентированных Flutter-проектах вроде [Arcana](https://ru.nerdy.pro/portfolio/arcana) (клиент стримингового AI-чата, выпущен за три недели) и [YouMi](https://ru.nerdy.pro/portfolio/youmi) (приложение для обмена сообщениями в реальном времени, стабилизированное за две). Ни один клиент не является «тем самым ботом с 50 000 пользователей», но каждый шаг здесь — то, что мы действительно делали. --- ### Почему шесть недель реалистичны (и когда нет) Шесть недель кажутся слишком амбициозным сроком, пока не вспомните, чего вы *не* делаете. Вы не придумываете продукт заново. Вы не проводите исследование пользователей, чтобы понять, что делать: ваш бот — это два года исследований. Вы не проектируете новую модель взаимодействия — разговорный флоу уже работает, и большую его часть вы сохраните. Вы не проверяете спрос — 50 000 человек уже его подтвердили. Остаётся **исполнение по известной спецификации** — и именно это сжимается. Из единой кодовой базы на [Flutter](https://flutter.dev) приложение выходит сразу на iOS и Android (мы писали, [почему одна кодовая база лучше двух нативных сборок](https://ru.nerdy.pro/blog/flutter-vs-react-native-2026)), так что вы не платите за одну и ту же работу дважды. Бэкенд по большей части существует. Список функций конечен и уже проверен в бою. Шесть недель реалистичны, когда: - Бот уже общается с **серверным API**, а не только с серверами Telegram. - Набор функций — это **чат плюс несколько экранов**, а не разросшийся супер-апп. - Есть **один человек, принимающий решения**: он может согласовать дизайн и объём без комитета. - Вы готовы выпустить сфокусированную v1 и добавить «длинный хвост» *после* запуска. Шесть недель *не* реалистичны, когда у бота нет отдельного бэкенда и вся логика живёт в обработчиках (добавьте 2–3 недели на выделение API), когда нужна дизайн-система с нуля или когда «паритет» втайне означает пятьдесят команд под edge-кейсы, каждой из которых нужен свой экран. К этому вернёмся в разделе [что срывает сроки](#%D1%87%D1%82%D0%BE-%D0%BF%D1%80%D0%B5%D0%B2%D1%80%D0%B0%D1%89%D0%B0%D0%B5%D1%82-%D1%88%D0%B5%D1%81%D1%82%D1%8C-%D0%BD%D0%B5%D0%B4%D0%B5%D0%BB%D1%8C-%D0%B2-%D1%87%D0%B5%D1%82%D1%8B%D1%80%D0%B5-%D0%BC%D0%B5%D1%81%D1%8F%D1%86%D0%B0). --- ### План на шесть недель, по неделям Вот график, по которому мы работаем. Недели пересекаются сильнее, чем подсказывает этот линейный список — дизайн начинается, пока завершается аудит, оформление для сторов стартует в первый день — но критический путь выглядит так. #### Неделя 1 — Аудит и маппинг функций Мы проходим бота команда за командой, сценарий за сценарием, и записываем всё, что он делает. Каждый `/command`, каждую инлайн-клавиатуру, каждый многошаговый диалог, каждую интеграцию, каждый платёжный путь. Это становится спецификацией — и это самый важный артефакт во всём проекте. Затем каждую возможность бота сопоставляем с эквивалентом в приложении и раскладываем по трём корзинам: - **Оставить чатом.** Разговорное ядро остаётся разговорным — это то, что знают ваши пользователи. - **Повысить до экрана.** То, что вы впихнули в чат, потому что бот не оставлял выбора (настройки, история, дашборды, профили), становится настоящими экранами. - **Вырезать из v1.** Команды, которые нужны 2% пользователей. Они идут в список «после запуска», а не в критический путь. Именно третья корзина решает, уложитесь ли вы в шесть недель. Результат первой недели — согласованный объём: конкретный список того, что войдёт в v1. #### Неделя 2 — Архитектура и вопрос об API Приложению нужно общаться с бэкендом. В лучшем случае бот уже это делает, и приложение просто вызывает те же эндпоинты. Мы проектируем клиентскую архитектуру вокруг вашего существующего API — аутентификация, модели данных, каналы реального времени — без переписывания бэкенда. Если у бота **нет отдельного бэкенда** (вся логика внутри обработчиков Telegram), здесь мы выделяем тонкий слой API, чтобы им могли пользоваться и бот, и приложение. Это единственная ситуация, которая действительно добавляет время, и мы предупреждаем об этом в первый день, а не обнаруживаем на четвёртой неделе. На этой же неделе фиксируются две подсистемы, которые незаметно топят чат-приложения: - **Обмен сообщениями в реальном времени** — правильный жизненный цикл [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API): переподключение, очередь сообщений, синхронизация состояния. Ошибка здесь — ровно то, что нас позвали чинить в [YouMi](https://ru.nerdy.pro/portfolio/youmi), где пользователей выбрасывало посреди разговора. Работа неэффектная, но именно она отличает приложение, которое работает, от приложения, которое работает, *пока ничего не идёт не так*. - **Push-уведомления** — [APNs](https://developer.apple.com/documentation/usernotifications) и [FCM](https://firebase.google.com/docs/cloud-messaging) подключаются рано, потому что уведомления — половина причины, по которой вы уходите из Telegram. #### Недели 3–4 — Строим ядро Самый плотный отрезок: собственно приложение. В единой кодовой базе на Flutter команда строит: - **Чат-интерфейс** — плавный скроллинг по тысячам сообщений, мультимедиа, стриминг ответов, если бот на AI (инкрементальный рендеринг Markdown, который мы сделали для [Arcana](https://ru.nerdy.pro/portfolio/arcana), живёт здесь). - **Аутентификацию** — телефон, email или вход через соцсети, привязанные к вашим существующим учётным записям, чтобы аккаунты перенеслись. - **«Повышенные» экраны** из первой недели — дашборды, история, настройки, профили. - **Push-уведомления** от начала до конца, по вашему расписанию и на ваших условиях. В конце каждой недели вы получаете демо-сборку. Не статусное письмо — устанавливаемую сборку на реальном устройстве. Это не обсуждается: еженедельные сборки — это способ поймать расползание объёма, пока его дёшево исправить. #### Неделя 5 — Инфраструктура миграции и подготовка к сторам Теперь часть, уникальная именно для *миграции*, а не сборки с нуля. Три вещи идут параллельно: - **Связывание аккаунтов.** Пользователь открывает приложение и подтверждает, что он тот же человек, что пользовался ботом: обычно это диплинк из бота в одно касание с подписанным токеном или совпадение по телефону/email. Его история и данные переезжают вместе с ним. - **Публикация в сторах.** Карточки App Store и Google Play, скриншоты, декларации о конфиденциальности, отправка на ревью. [Очередь ревью Apple](https://developer.apple.com/app-store/review/guidelines/) — единственная внешняя зависимость, которую вы не контролируете полностью, поэтому это начинается сейчас, а не на шестой неделе. - **Мост внутри бота.** Сам бот становится вашим лучшим каналом привлечения. Мы добавляем флоу анонса и диплинки, чтобы 50 000 пользователей узнали о приложении *внутри инструмента, который они и так открывают каждый день*. #### Неделя 6 — Запуск и начало миграции Приложение выходит. Но «запуск» не означает резкое переключение — и это та часть, которой основатели боятся больше всего, поэтому будем точны. **Вы не выключаете бота.** Бот продолжает работать. В день запуска он начинает подталкивать пользователей к приложению диплинком, но любой, кто его проигнорирует, продолжает пользоваться ботом ровно как раньше. Никого не отрезают, ничего не ломается, и ваши 50 000 пользователей не отваливаются из-за принудительной миграции. Миграция становится регулятором, который вы поворачиваете в следующие недели: баннер, потом мягкая подсказка, потом функции только в приложении, которые сами перетягивают людей. Многие продукты навсегда оставляют упрощённого бота как вторую точку входа. Темп задаёте вы; а то, что приложение уже живо с первого дня, — это и есть результат шестой недели. --- ### Как не потерять 50 000 пользователей Это заслуживает отдельного раздела, потому что это настоящий страх — а механика проста, стоит только её увидеть. 1. **Параллельная работа, а не переключение.** Бот и приложение сосуществуют. Не бывает момента, когда у пользователя остаётся единственный выбор: «скачай приложение или потеряй доступ». 2. **Бот — это канал миграции.** Вы не покупаете рекламу, чтобы найти этих пользователей — они и так общаются с вами каждый день. Диплинк в боте конвертит куда лучше любой холодной install-кампании, и он бесплатный. 3. **Аккаунты и история переносятся.** Токены-диплинки или совпадение по телефону/email означают, что пользователь входит и находит свои данные на месте. Приложение ощущается апгрейдом, а не стартом с нуля. 4. **Постепенные стимулы, а не принуждение.** Функции только в приложении, более удобные уведомления и более приятный опыт перетягивают пользователей. А жёсткий дедлайн часть из них выталкивает совсем. Если делать так, миграция — не рискованное событие, а кривая, которой вы управляете, и бот всё это время остаётся страховкой. --- ### Что превращает шесть недель в четыре месяца Честно о сценариях провала, потому что они предсказуемы: - **Нет бэкенда, с которым говорить.** Если вся логика живёт в обработчиках Telegram без API, выделение API добавляет 2–3 недели. Делать стоит — это фундамент для всего остального, — но это реальное время. - **«Паритет» значит всё.** Требование выпустить в v1 все 60 команд, включая те, которыми пользуются 2%, — самый частый убийца сроков. Выпустите ядро, хвост добавьте после запуска. - **Дизайн с чистого листа.** Уникальная дизайн-система, придуманная по ходу разработки, добавляет недели. Чистый, привычный дизайн, применённый к известному флоу, — нет. - **Решения комитетом.** Шесть недель предполагают, что кто-то может согласовать экран в день, когда его увидел. Если каждое решение неделю ждёт совещания, темп задаёт календарь, а не инженерия. - **Спасение, а не разработка с нуля.** Если есть недоделанное приложение, которое ломается, чинить унаследованные проблемы — совсем не то же самое, что начинать с чистого листа. Это ситуация [YouMi](https://ru.nerdy.pro/portfolio/youmi), и она оценивается отдельно. Ни одна из этих вещей не загадка. Мы вскрываем их на первой неделе — в этом весь смысл начинать с аудита, а не с диаграммы Ганта. --- ### Сколько это стоит Простая миграция в эти сроки — ключевые функции сохранены, чат-интерфейс, аутентификация, push, публикация в сторах и миграция пользователей — укладывается в диапазон **450 000–900 000 ₽** за четыре-шесть недель. Боты с большим набором функций — с платежами, офлайном и кастомными экранами — обходятся дороже и занимают больше времени; AI-боты со стримингом и рендерингом в реальном времени — ещё дороже. Полная разбивка по тарифам — на [странице услуги «из бота в приложение»](https://ru.nerdy.pro/services/chatbot-to-app), а [разбор ценообразования](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026) объясняет, как объём складывается в итоговую цифру. Что стоит усвоить: дорогая часть создания приложения — это поиск product-market fit, и **этот счёт вы уже оплатили** своим ботом. Миграция — дешёвая часть. Вы превращаете подтверждённый спрос в продукт, которым владеете — и это куда лучшая стартовая позиция, чем у основателя, который смотрит на пустой файл Figma и гадает, что строить. --- ### Частые вопросы #### Сколько времени занимает превращение Telegram-бота в мобильное приложение? Простой бот с рабочим бэкендом можно выпустить как приложение для iOS и Android примерно за шесть недель. Боты с большим набором функций — с платежами, офлайном или AI-стримингом — обычно занимают от двух до четырёх месяцев. Срок зависит в основном от того, есть ли у бота отдельный API бэкенда и какую часть функций нужно выпустить в первой версии. #### Потеряю ли я пользователей при переходе из бота в приложение? Нет, если мигрировать постепенно. Бот продолжает работать параллельно с приложением, поэтому ни один пользователь не остаётся отрезанным. Сам бот становится каналом миграции: диплинк внутри него конвертит куда лучше платной рекламы, а аккаунты и история переносятся, так что приложение ощущается апгрейдом, а не стартом с нуля. #### Нужно ли переписывать бэкенд, чтобы запустить приложение? Обычно нет. Если бот уже общается с API бэкенда, приложение вызывает те же эндпоинты без переписывания. Только когда вся логика живёт внутри обработчиков Telegram без отдельного бэкенда, нужно сначала выделить тонкий слой API — это добавляет примерно две-три недели. #### Сколько стоит превратить Telegram-бота в приложение? Простая миграция с ключевыми функциями, чатом, аутентификацией, push-уведомлениями и публикацией в сторах укладывается примерно в 400 000–800 000 ₽. Боты с большим набором функций и AI-боты стоят дороже. Поскольку бот уже доказал спрос, самая дорогая часть создания приложения уже оплачена. #### Может ли Telegram-бот продолжать работать после запуска приложения? Да. Большинство продуктов оставляют упрощённого бота работать постоянно как вторую точку входа. Запуск — не резкое переключение: бот со временем подталкивает пользователей к приложению, оставаясь полностью рабочим для тех, кто ещё не перешёл. #### Почему приложение на Flutter? Flutter выходит на iOS и Android из единой кодовой базы, что сокращает время и стоимость примерно на 40–50 процентов по сравнению с двумя нативными сборками, без потери производительности. Он особенно силён для чат-приложений, которым нужны плавный скроллинг, обновления в реальном времени и рендеринг мультимедиа. :: ### Начните с чек-листа Ещё до первой строчки кода прогоните бота по [чек-листу готовности к миграции](https://ru.nerdy.pro/services/chatbot-to-app#%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82-%D0%B3%D0%BE%D1%82%D0%BE%D0%B2%D0%BD%D0%BE%D1%81%D1%82%D0%B8-%D0%BA-%D0%BF%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%D1%83-%D0%B8%D0%B7-%D0%B1%D0%BE%D1%82%D0%B0-%D0%B2-%D0%BF%D1%80%D0%B8%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5) на странице услуги. Он честно покажет, насколько вы близки к чистой шестинедельной миграции — и где пробелы будут стоить вам времени. Если у вас бот с реальными пользователями и вы упираетесь в потолок платформы — [запишитесь на созвон](https://ru.nerdy.pro/contact). Мы проведём аудит вашего бота, скажем, какие недели будут лёгкими, а какие сложными, и дадим смету с фиксированным объёмом работ, а не догадку. ## Как на самом деле выглядит снижение стоимости мобильной разработки на 40% — цифры из 4 проектов Nerdy.pro https://ru.nerdy.pro/blog/mobile-cost-reduction-case-studies **Коротко.** «Снизили стоимость мобильной разработки на 40%» — это та самая цифра, которая попадает в питч-деки агентств и без разбивки не значит почти ничего. Этот пост — и есть разбивка. Берём четыре проекта из нашего портфолио — [Arcana](https://ru.nerdy.pro/portfolio/arcana), [YouMi](https://ru.nerdy.pro/portfolio/youmi), [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) и [Formtastic](https://ru.nerdy.pro/portfolio/formtastic) — и показываем, откуда в каждом из них взялась экономия. Где-то это был выбор фреймворка. Где-то — то, что *не пришлось* переписывать. Магии не было ни в одном случае. Если вам нужны рычаги стоимости без кейсов — переходите сразу к [пяти рычагам](#%D0%BF%D1%8F%D1%82%D1%8C-%D1%80%D1%8B%D1%87%D0%B0%D0%B3%D0%BE%D0%B2-%D0%BB%D1%8E%D0%B1%D0%BE%D0%B3%D0%BE-%D1%81%D0%BD%D0%B8%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F-%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8). --- ### Почему «на 40% дешевле» — это обычно ложь Когда агентство говорит, что снизило клиенту стоимость мобильной разработки на какой-то круглый процент, задайте один вопрос: **40% от чего и относительно чего?** - 40% от **часовой ставки** — это офшоринг, а не инженерия. - 40% от **численности команды** — это увольнения, а не оптимизация разработки. - 40% от **time-to-market** — это реальная инженерная победа, потому что она накапливается: каждая сэкономленная неделя TTM — это неделя выручки, неделя обратной связи, неделя преимущества над конкурентами. - 40% от **совокупной стоимости владения** за три года — самое редкое и самое ценное, потому что сюда входит счёт за поддержку, который никто не озвучивает заранее. Четыре кейса ниже — реальные, все из нашего портфолио, все проверяемые. Они иллюстрируют разные рычаги стоимости, и экономия в каждом случае берётся из разных мест. Ни один из них не сводится к «мы просто взяли инженеров подешевле». --- ### Кейс 1 — ExtraETF: снижение time-to-market на 40% **Проект:** [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) для Isarvest GmbH. Длительность: 6 месяцев. Стек: Flutter + Go. ExtraETF — финансовое приложение для анализа ETF и акций: стримы котировок в реальном времени, интерактивные графики, push-уведомления, OAuth, in-app подписки. Тот случай, когда возникает соблазн писать две нативные кодовые базы (Swift на iOS, Kotlin на Android), потому что финансовые приложения «должны ощущаться нативно». Мы этого не сделали. Написали один раз на Flutter и выпустили в оба стора за 6 месяцев с одинаковой функциональностью. Оценочное сокращение TTM по сравнению с параллельной нативной разработкой — **40%**. Откуда эти 40% берутся на самом деле? - **Одна мобильная кодовая база вместо двух.** Большинство нативных проектов идут двумя параллельными треками — iOS и Android. Даже при идеальной координации вы дважды пишете бизнес-логику, дважды чините один и тот же баг и выпускаете фичи, которые работают на одной платформе и ломаются на другой. Flutter убирает эти накладные расходы полностью. - **Нет бэклога расхождений между платформами.** Когда iOS выпускает фичу первым, Android-пользователи ждут. Когда Android догоняет, iOS уже выпустил ещё две. Бэклог платформенных расхождений — скрытый налог; на Flutter его нет. - **Работа с графиками сделана один раз.** ExtraETF — приложение с большим количеством графиков. Нативные библиотеки графиков на iOS и Android — это две совершенно разные истории; добиться одинакового внешнего вида и поведения — это недели работы. Flutter рисует каждый пиксель сам, поэтому код графиков работает идентично на обеих платформах. Цифра в 40% TTM — не маркетинговая, а арифметическая: она получается сама, когда вы перестаёте платить за одну и ту же работу дважды. Сервис на Go для стрима цен — это отдельная история; тот рычаг был про стоимость одного соединения на бэкенде при масштабировании, а не про стоимость мобильной разработки. **Рычаг стоимости:** одна кодовая база на iOS и Android. Проверяемо, повторяемо, не разовый трюк. --- ### Кейс 2 — Formtastic: консолидация двух нативных приложений в одно **Проект:** [Formtastic](https://ru.nerdy.pro/portfolio/formtastic) для Formtastic GmbH. Длительность: 1 год (полная эволюция платформы). Стек: Flutter + Nuxt + Django + Go. Formtastic — это *другая* форма снижения стоимости. У них уже было два нативных мобильных приложения в проде — iOS и Android — и стоимость была не в *разработке* мобилки, а в её **поддержке**. Две кодовые базы, две команды, два релизных цикла, два прогона QA на каждую фичу. Мы мигрировали оба приложения в одну Flutter-кодовую базу. Снижение стоимости мобильной разработки здесь — это не процент от сметы, а структурный сдвиг в том, сколько стоит эксплуатация продукта месяц за месяцем. Что изменилось конкретно: - **Релизные циклы синхронизировались.** Было: iOS вышел, Android — через неделю, если команде повезло. Стало: одна сборка, один релиз, оба стора одновременно. - **Паритет фич перестал быть проблемой координации.** Новая фича пишется один раз. Крайние случаи обрабатываются один раз. Сообщение «мы забыли добавить это на Android» в Slack пропало. - **Строчка расходов на поддержку сжалась.** Два нативных приложения требуют двух комплектов работ по апгрейду каждый год — новая версия iOS, новая версия Android, новые требования SDK, новые правила App Store, новые правила Play Store. У Flutter тоже есть стоимость апгрейда, но примерно вдвое меньше. Параллельно веб-фронтенд переехал с Django-шаблонов на отдельную Nuxt SPA, а часть hot-path сервисов перешла с Django на Go. У обоих решений был свой ROI, но мобильная консолидация была самой крупной статьёй экономии. **Рычаг стоимости:** сокращение поверхности поддержки. Две кодовые базы → одна. Две команды → одна. Большинство проектов по снижению стоимости, которые мы видим, совершают ошибку — пытаются делать ту же работу дешевле. Formtastic стал делать меньше работы при том же качестве. --- ### Кейс 3 — YouMi: цена отсутствия Flutter-экспертизы **Проект:** [YouMi](https://ru.nerdy.pro/portfolio/youmi) для YouMi LLC. Длительность: 2 недели. Стек: Flutter. YouMi — кейс, который основатели больше всего хотят проигнорировать, потому что он про стоимость *отсутствия* правильной команды с самого начала. У YouMi уже было Flutter-приложение в проде. И оно ломалось. Пользователей отключало посреди сессии с психологом. Сообщения в чате не доходили. Навигация между экранами была непредсказуемой. Продукт работал, когда ничего не ломалось, что в реальном продакшне не случается никогда. Корневых причин было две, обе довольно скучные: слой WebSocket без нормального управления жизненным циклом и стек навигации с утечками памяти. Обе проблемы Flutter-инженер, который выпустил несколько продакшн-приложений, решает на автопилоте. Обе проблемы Flutter-инженер, который *не* выпускал нескольких продакшн-приложений, будет ковырять месяцами. Мы взяли кодовую базу, починили обе подсистемы и выпустили стабильную сборку за **две недели**. Если смотреть на это в категориях стоимости: альтернативой было не «сделать дешевле». Альтернативой было «продолжать терять пользователей, пока продукт не умрёт». Посчитайте стоимость этого — отток, возвраты, ущерб бренду, моральное состояние основателя — и две недели работы перестают быть строкой в бюджете проекта. Это вопрос того, выживет ли проект вообще. Это и есть точка входа для Team Augmentation как услуги: один встроенный инженер, который умеет распознавать знакомые паттерны, предотвращает тот медленный провал продукта, у которого нет строки ни в одной смете. **Рычаг стоимости:** распознавание паттернов. Самая дешёвая версия любого мобильного проекта — та, которую не приходится спасать через полгода после старта. --- ### Кейс 4 — Arcana: скорость выхода на прототип на сложной задаче **Проект:** [Arcana](https://ru.nerdy.pro/portfolio/arcana) для клиента с AI-таро. Длительность: 3 недели. Стек: Flutter. Arcana — это то, как выглядит скорость выхода на прототип, когда техническая задача действительно сложная. Клиент владел AI-бэкендом. Им нужен был чат-клиент, который умеет: - Стримить ответы AI в реальном времени по Server-Sent Events. - Инкрементально рендерить Markdown-разметку по мере поступления токенов — жирный, курсив, заголовки, списки — без мерцания и скачков вёрстки. - Держать 60 fps на бюджетном Android-устройстве при прокрутке 5 000 сообщений. Это не CRUD-приложение. Это кастомный рендеринг-движок, прикрученный к кастомному сетевому слою. Мы выпустили это за 3 недели. Рычаг стоимости здесь тонкий. Мы не сэкономили клиенту деньги тем, что написали меньше кода — чтобы сделать это за 3 недели, потребовалось *больше* аккуратной инженерной работы, чем на типичный чат за 3 месяца. Мы сэкономили деньги тем, что сжали график. Каждая неделя, когда AI-продукт не был в руках пользователей, — это неделя трекшна у конкурента, вопросов от инвесторов и неопределённости в команде. Разработка за 3 недели позволила провалидировать продуктовую гипотезу до того, как арифметика runway стала неприятной. Скорость выхода на прототип — это рычаг стоимости, который не виден в почасовом расчёте. Он виден в том, *какие решения вы успеваете принять* и *сколько* решений вы успеваете принять до того, как закончатся деньги. **Рычаг стоимости:** время. Точнее, время на принятие решений, выкупленное обратно за счёт быстрого выпуска на сложной задаче. --- ### Пять рычагов любого снижения стоимости В этих четырёх проектах экономия пришла из одних и тех же пяти повторяемых рычагов. Ни один из них не экзотический. Но все они требуют агентства, готового честно говорить об объёме работ. #### 1. Одна кодовая база, а не две Самая дешёвая кросс-платформенная мобильная разработка — это та, которая реально работает на обеих платформах из одного источника, — на этом и строится наша [разработка приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development). Для продуктов с тяжёлым UI этот аргумент у Flutter убедительнее, чем у React Native (почему — мы разобрали в [Flutter vs React Native в 2026 году](https://ru.nerdy.pro/blog/flutter-vs-react-native-2026)), но важнее другое: **любой** реальный кросс-платформенный стек обыгрывает по стоимости разработку двух нативных приложений параллельно. Основатели обычно недооценивают, насколько большая часть нативной разработки — это дублирование. #### 2. Сокращение поверхности поддержки, а не срезание углов Паттерн Formtastic. Самая дешёвая фича — та, которую не нужно поддерживать в двух местах. Самый дешёвый бэкенд — тот, в котором меньше сервисов, а не больше. Экономия за счёт упрощения накапливается; экономия за счёт урезания QA — нет. #### 3. Распознавание паттернов вместо часов Паттерн YouMi. Сеньорный Flutter-инженер на знакомом классе задач кратно быстрее, чем двое мидлов на той же задаче. Часовая ставка выше; итоговая стоимость ниже. В этом и весь смысл Team Augmentation — встроить экспертизу, которая предотвращает дорогие сценарии отказа, а не просто руки, которые закрывают тикеты. #### 4. Скорость выхода на прототип на новых задачах Паттерн Arcana. Когда техническая задача для команды беспрецедентна, рычаг стоимости — не сокращение разработки, а сокращение времени между «у нас есть гипотеза» и «у нас есть данные». Три недели против трёх месяцев — это не 75% экономии на инженерии; это разница между продуктом, который вышел, и продуктом, у которого закончился runway. #### 5. Ниже стоимость поддержки в долгую Скрытая строка расходов. Любой выбор мобильного фреймворка — это решение на 3–5 лет. Счёт за апгрейд и поддержку на этой дистанции часто больше, чем первоначальная разработка. Мы расписывали разбивку стоимости по уровням в [Стоимости разработки Flutter-приложения в 2026 году](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026), но если коротко: выбирайте стек с самой низкой *будущей* стоимостью, а не с самой низкой *сегодняшней*. --- ### Что это значит для вашего проекта Если вы — основатель и читаете это с мобильным проектом в голове, вопрос не в том, «как мне получить 40% скидки». Вопрос — какой из пяти рычагов реально применим к вашей ситуации: - Если вы начинаете с нуля и планируете писать два нативных приложения — рычаг №1. - Если вы уже платите за поддержку двух нативных приложений — рычаг №2. - Если ваше текущее приложение ломается и у вас в штате нет сеньорного Flutter-инженера — рычаг №3. - Если вы гонитесь за конкурентом или бежите наперегонки с таймером runway — рычаг №4. - Если вы строите то, что собираетесь эксплуатировать ближайшие 3+ года — рычаг №5. В большинстве проектов работают один-два из них. В некоторых — три. Каждый из кейсов выше сильно опирался на свой рычаг — поэтому снаружи экономия выглядела по-разному, хотя механика под капотом одна и та же. --- ### Оцените свой проект Самый надёжный способ получить реальную цифру для конкретно вашего проекта — самый медленный: 30-минутный созвон, на котором мы смотрим на пять рычагов в контексте вашей ситуации. Без слайдов, без питча — только разговор, который даёт честную оценку. Если это полезно — [запишитесь на вводный созвон](https://ru.nerdy.pro/contact). Если хотите сначала разобраться в самой методике — [Стоимость разработки Flutter-приложения в 2026 году](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026) — это сопроводительный пост, в котором показано, как объём, бэкенд, интеграции, дизайн и комплаенс складываются в итоговую цифру. А если ваш продукт начинался как Telegram-бот, то статья [о выпуске приложения за шесть недель](https://ru.nerdy.pro/blog/telegram-bot-to-app-6-weeks) разбирает именно этот переход — сроки, стоимость и как не потерять пользователей. А если у вас уже есть Flutter-приложение в проде, которое плохо себя ведёт, — как было у YouMi, — это тот разговор, который мы бы хотели начать в первую очередь. Две недели сеньорной инженерии в нужный момент почти всегда дешевле, чем ещё два квартала медленного провала. ## Flutter vs React Native в 2026 году: честные инженерные компромиссы https://ru.nerdy.pro/blog/flutter-vs-react-native-2026 **Коротко.** В 2026 году и Flutter, и React Native — production-ready. Ни один из них «не побеждает». Правильный выбор определяется вашей командой, требованиями к UI, количеством платформ, на которые вы выходите (только мобильные? мобильные + web? desktop? embedded?) и тем, как устроен ваш процесс найма. В [Nerdy.pro](https://ru.nerdy.pro/) мы разрабатываем на Flutter, потому что экономика сходится для наших клиентов, — но мы также выпускали реальные проекты на React Native, и этот пост — наше честное сравнение того, где каждый из них оправдывает себя. Если вам нужен только ответ — переходите сразу к [короткому фреймворку принятия решения](#%D0%BA%D0%BE%D1%80%D0%BE%D1%82%D0%BA%D0%B8%D0%B9-%D1%84%D1%80%D0%B5%D0%B9%D0%BC%D0%B2%D0%BE%D1%80%D0%BA-%D0%BF%D1%80%D0%B8%D0%BD%D1%8F%D1%82%D0%B8%D1%8F-%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F). Если хотите инженерную глубину, которая за ним стоит, — читайте дальше. Сравниваете Flutter с **нативной** разработкой под iOS/Android (Swift, Kotlin), а не с React Native? Это отдельное решение — мы разбираем его в посте [Flutter против нативной разработки в 2026 году](https://ru.nerdy.pro/blog/flutter-vs-native-2026). --- ### Почему мы написали этот пост (и чем он отличается) Загуглите «flutter vs react native 2026» — и найдёте в основном одну и ту же статью, переписанную сотню раз: таблица-матрица фич, горсть устаревших тезисов о «JS-мосте» и рекомендация, подозрительно совпадающая с тем, какой фреймворк продаёт агентство автора. Мы — Flutter-first агентство. Мы не нейтральны. Но мы использовали React Native достаточно — на продакшн-приложениях с реальными пользователями — чтобы сказать вам, когда это правильный ответ. Этот пост существует потому, что наши клиенты заслуживают решения, а не продающей презентации, и потому, что честный ответ полезнее ответа в духе «наши против ваших». Там, где мы высказываем мнение, оно опирается на работу, которую мы реально выпустили. Четыре из этих приложений публичные: [Arcana](https://ru.nerdy.pro/portfolio/arcana), [YouMi](https://ru.nerdy.pro/portfolio/youmi), [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) и [Formtastic](https://ru.nerdy.pro/portfolio/formtastic). Мы будем ссылаться на них ниже. --- ### Что реально изменилось между 2023 и 2026 Большинство сравнительных постов до сих пор спорят о вещах, которые уже починены. Прежде чем что-то сравнивать, вот что нужно знать о текущем состоянии обоих фреймворков. **Новая архитектура React Native теперь по умолчанию, а не эксперимент.** Fabric (новый рендерер), TurboModules (новая система нативных модулей) и Bridgeless-режим вошли в React Native 0.76 как дефолты в конце 2024. Аргумент про «бутылочное горлышко JS-моста», который доминировал в дискуссиях 2020–2023, в значительной степени устарел. Современный RN вызывает нативный код синхронно через JSI, и накладные расходы на сериализацию, на которые раньше жаловались, исчезли в любом проекте, перешедшем на Новую архитектуру. Если ваши точки отсчёта — блог-пост Airbnb 2018 года или ранняя критика от Discord, выбросьте их. **Impeller во Flutter — больше не «новый» рендерер.** Impeller вытеснил Skia и стал рендерером по умолчанию на iOS в 2023 году, а на Android — в 2024-м. Он устранил подтормаживания из-за компиляции шейдеров, которые годами были самой заметной продакшн-проблемой Flutter. Если последний раз вы серьёзно смотрели на Flutter в 2022-м, ситуация с рендерингом изменилась существенно. **Expo фактически стал стандартным способом выпускать React Native.** Workflow «bare React Native CLI» всё ещё существует, но на практике новые RN-проекты — это Expo-проекты. Expo Router, EAS Build и prebuild-система Expo заметно приблизили ранее болезненный DX в RN к уровню Flutter. Сравнения, которые не упоминают Expo, сравнивают с версией RN, которую команды в 2026 году используют редко. **Flutter на web, desktop и embedded — реально, но с нюансами.** Flutter Web production-ready для внутренних инструментов, админ-панелей и app-like-опыта, но не для контентных сайтов и не для чего-либо SEO-зависимого. Flutter для desktop (macOS, Windows, Linux) стабилен. У Flutter embedded есть реальная экосистема (автомобили, киоски, бытовая техника). Web у React Native (через react-native-web, Solito или web-вывод Expo Router) тоже вполне реален, но остаётся community-led, а не частью ядра. **Ландшафт найма сократил разрыв.** В 2022-м «разработчиков на React в 10 раз больше, чем на Dart» было стандартным контраргументом против Flutter. Это и сейчас в целом верно, но пул Flutter-специалистов заметно повзрослел. Для senior-ролей — тех людей, кто реально задаёт архитектуру, — в обоих фреймворках есть достаточно квалифицированных инженеров. Теперь, когда базовые факты зафиксированы, — сравниваем. --- ### Восемь критериев, которые реально имеют значение #### 1. Точность UI и контроль над дизайном Flutter сам рисует каждый пиксель. React Native рендерит нативные view платформы (UIView на iOS, Android Views или Compose interop на Android). **Что это значит на практике:** - Если у вас кастомная дизайн-система — кастомные анимации, нестандартные элементы, pixel-perfect одинаковый вид на iOS и Android, — на Flutter это реализуется заметно быстрее. Это было решающим для [Arcana](https://ru.nerdy.pro/portfolio/arcana), где UI намеренно не подчиняется конвенциям ни одной из платформ. - Если ваш продукт должен ощущаться *нативно* — системные контекстные меню, нативные iOS-переходы навигации, точная семантика accessibility на платформе, — у React Native реальное преимущество. Банковское или медицинское приложение, которое пользователи ожидают видеть «как обычное iOS-приложение», проще сделать правильно на RN. **Честный вердикт:** RN выигрывает, если ценность бренда — ощущение нативности. Flutter выигрывает, если ценность бренда — консистентность дизайн-системы. Ни один не выигрывает, если у вас нет сильного мнения по этому вопросу. #### 2. Производительность и архитектура рендеринга При том что оба фреймворка работают на современных архитектурах (Impeller у Flutter, Fabric + Bridgeless у RN), сырая производительность рендеринга достаточно близка, чтобы **большинство команд не заметили разницы на типовом CRUD-приложении.** Где разрыв всё ещё проявляется: - **Высокочастотная анимация и кастомная отрисовка** — архитектура Flutter (рисование напрямую на GPU, без реконсиляции через дерево нативных view) всё ещё выигрывает. Если вы делаете приложение для рисования, продукт с тяжёлыми картами, библиотеку графиков или UI, близкий к игровому, — Flutter будет правильным инструментом. Мы использовали этот аргумент, когда выбирали Flutter для [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf), где насыщенный графиками инвестиционный UI был бы болезненным на RN. - **Время старта на слабых Android-устройствах** — у Flutter исторически был больший размер бинарника и более медленный холодный старт. К 2026 году это в основном решено, но на устройствах с ОЗУ менее 2 ГБ на развивающихся рынках RN с Hermes всё ещё грузится быстрее. - **Горячий путь вызовов нативных модулей** — JSI в RN сделал синхронные нативные вызовы дешёвыми. Для приложений, чья производительность определяется большим количеством мелких нативных вызовов (тяжёлая интеграция с железом, IoT, управление по BLE), RN заметно проще. **Честный вердикт:** Flutter — более безопасный выбор для UI с тяжёлой анимацией или сложной графикой. RN — более безопасный выбор, если у вас жёсткие ограничения по размеру бинарника или много обращений к нативным модулям. #### 3. Широта платформ Это как раз то, где Flutter в 2026 году просто впереди. - **Mobile (iOS + Android)** — паритет. - **Web** — Flutter Web — core; RN Web существует как community-проект (react-native-web). Если web для вас — полноценная платформа и вы не готовы поддерживать параллельную кодовую базу на React, то с Flutter проще. Если web — ваша *основная* платформа, а mobile — вторичен, то ни один из вариантов не подходит: вам нужен Next.js или Remix с нативными обёртками. - **Desktop (macOS, Windows, Linux)** — Flutter production-ready. RN-macOS и RN-Windows существуют и поддерживаются Microsoft, но отстают от Flutter по полноте экосистемы библиотек. - **Embedded (автомобили, киоски, бытовая техника)** — здесь у Flutter единственное реальное предложение. Toyota, BMW, Canonical и другие выпускают Flutter на embedded-железе. RN в этот сегмент не целится. **Честный вердикт:** Если нужно больше, чем iOS + Android, — Flutter. Если iOS + Android — это вся дорожная карта навсегда, — ни один из фреймворков не имеет преимущества по широте. #### 4. Экосистема и библиотеки Экосистема React Native больше и зрелее в абсолютных цифрах. Экосистема npm принадлежит JavaScript, и RN её наследует. Практические следствия: - **Покрытие SDK** — платёжные провайдеры, аналитика, сервисы авторизации, CRM-интеграции почти всегда поставляют RN SDK. Некоторые поставляют и Flutter SDK, но RN — более безопасный выбор, если ценность вашего приложения живёт в сторонних интеграциях. - **Библиотеки компонентов** — у RN больше готовых UI-китов (особенно связанных с web-экосистемой React). У Flutter библиотека компонентов лучше отобрана, но меньше по абсолютному количеству. - **Разброс качества пакетов** — у обеих экосистем есть проблемы с качеством в длинном хвосте. У pub.dev (Flutter) типизация и гарантии null-safety в целом лучше, чем у типичного npm-пакета. Если вы обжигались на заброшенных RN-библиотеках, это реальное соображение. **Честный вердикт:** RN выигрывает по широте экосистемы. Flutter выигрывает по её консистентности. #### 5. Найм, стоимость команды и скорость онбординга Здесь реально принимается большинство решений, даже если технические аргументы получают больше эфирного времени. - **Размер пула** — JavaScript/React-инженеров существует больше, чем Dart-инженеров. Это структурно и не изменится. - **Вход из web** — React-разработчик может начать контрибьютить в RN-кодовую базу за несколько дней. А для работы с Flutter-кодовой базой сначала придётся выучить Dart (это просто) и модель виджетов Flutter (тут нужно несколько недель, чтобы перестать с ней бороться). - **Senior-таланты** — для senior-ролей аргумент про размер пула ослабевает. Хорошие senior-инженеры в мобильной разработке — редкость в обеих экосистемах. - **Цены агентств** — по нашему опыту ценообразования проектов, Flutter- и RN-агентства называют цены в пределах 10% друг от друга при сравнимом объёме. Разница в стоимости почти полностью зависит от экосистемы (см. критерий 4) и специфики проекта, а не от внутренних свойств фреймворка. Детальный разбор того, как эти переменные превращаются в реальную стоимость проекта, — в нашем отдельном посте: [Стоимость разработки Flutter-приложения в 2026 году: реальные цифры из реальных проектов](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026). **Честный вердикт:** Если у вас уже есть React-команда, стоимость входа в RN близка к нулю. Не переучивайте их на Dart только чтобы использовать Flutter. Если вы нанимаете с нуля — выбор ближе, чем подсказывают аргументы про размер пула. #### 6. Developer experience и инструменты Близко, но с разным характером. - **Hot reload** — оба быстрые. Hot reload у Flutter по нашему опыту всё ещё чуть быстрее и надёжнее, особенно после изменений, затрагивающих состояние. - **Система сборки** — Expo (для RN) закрыл большую часть исторического разрыва. Greenfield-проект на Expo — реально приятный. Bare-RN-проект в первый день всё ещё более шероховатый, чем Flutter-проект. - **Язык** — Dart — маленький, предсказуемый язык. TypeScript мощнее, но сложнее. Это дело вкуса; ни один явно не лучше для выпуска приложений. - **Зрелость инструментов** — IDE-тулинг Flutter (через `flutter doctor`, DevTools и Dart analyzer) тесно интегрирован и обычно выступает источником истины. Тулинг RN больше зависит от того, какие слои вы выбрали (Metro, Hermes, Expo, Reanimated и так далее), — это мощнее, но требует больше осознанных решений. **Честный вердикт:** DX у Flutter более opinionated и чуть ровнее из коробки. DX у RN более настраиваемый и вознаграждает команды, которые знают, чего хотят. #### 7. Долгосрочная поддержка и боль апгрейдов Кроссплатформенный фреймворк — решение на 3–5 лет. Стоимость апгрейдов важнее, чем думает большинство основателей. - **Flutter** — ломающие изменения редки, и о них предупреждают заранее. Flutter-кодовая база двухлетней давности обновляется без сюрпризов за день работы. - **React Native** — исторически апгрейды RN были болезненными (Upgrade Helper прославился не с лучшей стороны). Миграция на Новую архитектуру, в основном завершённая к 2026 году, растянулась у крупных команд на несколько кварталов. Expo значительно сгладил путь апгрейда, но RN всё ещё требует большей дисциплины при апгрейдах, чем Flutter. **Честный вердикт:** Flutter — менее требовательный к поддержке выбор на горизонте 3 лет. Разрыв сокращается, если вы остаётесь на managed-workflow от Expo. #### 8. App Store review и граничные случаи платформенных правил Об этом говорят реже, но обходится дорого, когда случается. - **Apple 4.2.6 (правило про «коммерциализированный шаблон»)** — бьёт по обоим фреймворкам одинаково. Ни один фреймворк сам по себе не приводит к 4.2.6 — всё решает то, насколько приложения отличаются друг от друга. Актуально в основном, если вы запускаете white-label- или мультитенантную стратегию. - **Размер бинарника** — на Android RN-приложения обычно получаются меньше, чем Flutter-приложения. Обычно не решающее соображение, но реальное для приложений с агрессивными целями по install-conversion. - **Accessibility** — RN наследует нативное дерево accessibility платформы, что упрощает прохождение accessibility-аудитов в enterprise-продажах. У Flutter есть собственный слой accessibility — хороший, но требующий более явного внимания. **Честный вердикт:** Для enterprise-продуктов со строгими требованиями по accessibility или размеру бинарника RN стартует чуть впереди. Для всего остального — ничья. --- ### Выбирайте Flutter, если… - Вам нужна pixel-perfect консистентность UI на iOS и Android (а часто и на web + desktop). - Ваш продукт насыщен интерфейсом и анимацией или делает кастомную отрисовку (графики, канвасы, игры, инструменты рисования). - Вам нужно больше, чем mobile, — web, desktop или embedded в дорожной карте. - Вы цените низкую стоимость апгрейдов и поддержки на горизонте 3–5 лет. - У вас ещё нет React-команды. Именно поэтому большая часть проектов в нашем [портфолио](https://ru.nerdy.pro/portfolio) построена на Flutter — это основа нашей практики [разработки приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development). ### Выбирайте React Native, если… - У вас уже есть React- или JavaScript-команда и вы хотите минимизировать переобучение. - Ваш продукт должен ощущаться *нативно* — банкинг, медицина, системные утилиты или любой контекст, где пользователи ожидают соответствия платформенным конвенциям. - Ценность вашего приложения живёт в сторонних SDK (платежи, аналитика, нишевые интеграции) и вы хотите максимально широкую поверхность библиотек. - Вы хотите переиспользовать код вместе с React-веб-приложением, не поддерживая две кодовые базы. - Вы выпускаете в основном на Android с жёсткими KPI по install-conversion и размер бинарника — измеримое ограничение. --- ### Короткий фреймворк принятия решения Если нужен один абзац — вот он. **Выбирайте React Native, если ваша команда уже на React, продукт должен ощущаться нативно или ценность живёт в сторонних SDK. Выбирайте Flutter, если вы владеете собственной дизайн-системой, дорожная карта выходит за пределы iOS + Android или вы хотите минимальную стоимость поддержки на следующие три года.** Если обе половины этого предложения применимы — решающим становится состав команды: используйте то, на чём ваши senior-инженеры будут быстрее всего. --- ### Влияние на стоимость Выбор фреймворка влияет на стоимость, но обычно меньше, чем объём продукта, сложность дизайна и количество интеграций. Один и тот же MVP, хорошо сделанный на любом из фреймворков, уложится в ±10% от другого в нашем ценообразовании. Где разрыв по стоимости становится реальным — это длинный хвост: поддержка, боль апгрейдов и инженерное время на обёртки нативных SDK, у которых нет first-class-пакета для выбранного вами фреймворка. Мы написали отдельный пост, разбирающий это на цифрах наших собственных проектов: [Стоимость разработки Flutter-приложения в 2026 году: реальные цифры из реальных проектов](https://ru.nerdy.pro/blog/flutter-app-development-cost-2026). --- ### Как мы подходим к этому решению в Nerdy.pro Когда новый клиент спрашивает, стоит ли ему использовать Flutter или React Native, мы проводим 30-минутный звонок, где смотрим на: 1. **Кто сейчас в вашей команде и кто будет это поддерживать через два года?** 2. **На каких платформах нужно выпускаться — только mobile или mobile + web + desktop?** 3. **Какие три ваши крупнейшие сторонние интеграции и какие фреймворки они поддерживают?** 4. **Насколько нативно должен ощущаться продукт?** 5. **Какие у вас сроки и какой потолок по стоимости?** Если ответы сильно в пользу React Native, мы так и скажем и поможем найти хорошее RN-агентство. Если в пользу Flutter — оцениваем проект. Если ответы неоднозначны (а так бывает часто), — собираем небольшой proof-of-concept на том фреймворке, который команда с большей вероятностью будет поддерживать. Если хотите, чтобы мы проделали это упражнение с вашим продуктом, — [забронируйте discovery-звонок](https://ru.nerdy.pro/contact). Без слайдов и продающих презентаций — только технический разговор, чтобы принять правильное решение. Можно также посмотреть, как это мышление проявляется на практике, — полистайте [наше портфолио](https://ru.nerdy.pro/portfolio): [Arcana](https://ru.nerdy.pro/portfolio/arcana), [YouMi](https://ru.nerdy.pro/portfolio/youmi), [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) и [Formtastic](https://ru.nerdy.pro/portfolio/formtastic). Каждый из них выбрал Flutter по конкретной причине, которую мы с удовольствием разберём на звонке. --- ### FAQ #### React Native мёртв в 2026 году? Нет. Meta* продолжает в него инвестировать, Новая архитектура стала дефолтом в 2024-м, и Expo существенно улучшил developer experience. React Native растёт, а не затухает. Кто говорит иначе — тот вам что-то продаёт. #### Flutter всё ещё медленнее стартует на Android, чем нативно? Задержка холодного старта на слабых Android-устройствах — оставшаяся слабость Flutter. На среднем сегменте и флагманах время старта в хорошо сделанном приложении неотличимо от нативного. Если устройства с ОЗУ менее 2 ГБ на развивающихся рынках — ваш основной сегмент, — сделайте бенчмарк, прежде чем принимать решение. #### Стоит ли использовать Flutter для контентного сайта? Нет. Используйте Next.js, Remix или другой HTML-first-фреймворк. Flutter Web рисует на канвасе, что плохо для SEO и accessibility на контентных сайтах. Flutter Web отлично подходит для app-like-опыта (дашборды, админки, интерактивные инструменты), но не для блогов и маркетинговых сайтов. #### Можно ли мигрировать с React Native на Flutter (или наоборот)? Можно, но это фактически переписывание. Между двумя стеками нет значимых общих артефактов. Закладывайте это как greenfield-проект, а не миграцию, и спросите себя, решит ли смена фреймворка реальную проблему, прежде чем на неё решаться. В большинстве случаев боль текущего фреймворка не на уровне фреймворка — она архитектурная, и после переписывания на другом фреймворке вы получите те же архитектурные проблемы, только на другом языке. #### А как же Kotlin Multiplatform или Swift-based cross-platform? KMP — достойный третий вариант для команд, которые хотят нативный UI на каждой платформе при общей бизнес-логике. У него более узкий охват, чем у Flutter или RN, и он наиболее интересен организациям, у которых уже есть сильные Kotlin- и Swift-команды. Мы оценивали его для клиентов, но пока не выпускали на нём продакшн-работу. Полное сравнение теперь вынесено в отдельный пост: [Flutter vs Kotlin Multiplatform в 2026 году](https://ru.nerdy.pro/blog/flutter-vs-kotlin-multiplatform-2026). #### Имеет ли выбор значение для MVP? Меньше, чем вы думаете. Стоимость и срок типового MVP (8–16 недель) определяются решениями про объём, а не про фреймворк. Выбирайте тот фреймворк, на котором ваша команда выпустится быстрее всего, а к выбору вернётесь после product-market fit, когда начнутся настоящие инженерные решения. --- *Хотите, чтобы мы применили этот фреймворк к вашему продукту? [Забронируйте 30-минутный звонок](https://ru.nerdy.pro/contact) — и мы дадим честную рекомендацию, даже если она означает, что мы отправим вас к кому-то другому.* *Meta Platforms Inc. признана экстремистской организацией, её деятельность запрещена на территории РФ. ## Стоимость разработки Flutter-приложения в 2026 году: реальные цифры из реальных проектов https://ru.nerdy.pro/blog/flutter-app-development-cost-2026 Большинство статей на эту тему бесполезны. Вам дают диапазон «от 900 тыс. ₽ до 45 млн ₽» — и считают, что этого достаточно. Формально диапазон верный, но практически от него нет никакой пользы — это всё равно что сказать «дом стоит где-то 4,5 млн ₽ – 4,5 млрд ₽». Эта статья другая. Мы управляем Flutter-агентством, оценивали много проектов и расскажем реальные цифры: что на них влияет, где агентства накручивают сметы и что вы реально получаете на каждом ценовом уровне. Без диапазонов шириной с грузовой контейнер. Уровни ниже соответствуют тем, что указаны на странице услуги [разработка приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development). У первого из них есть отдельная страница — [разработка MVP](https://ru.nerdy.pro/services/flutter-app-development/mvp). ### Короткий ответ В 2026 году Flutter-приложение стоит: - **1 350 000–2 700 000 ₽** за MVP (2–3 месяца, небольшая команда, ограниченный объём) - **2 700 000–5 400 000 ₽** за полноценное бизнес-приложение (3–5 месяцев, кастомный дизайн, админ-панель) - **4 500 000–8 100 000 ₽** за e-commerce или ритейл-приложение (4–6 месяцев, платежи, склад, мультиролевой доступ) - **от 8 100 000 ₽** за enterprise или приложения с AI (6+ месяцев, compliance, кастомная инфраструктура, мультитенантность) Это ставки агентства для европейского клиента, работающего с компетентной европейской командой. Агентство из США умножит эти цифры примерно на 2–3 за сопоставимую работу. Фрилансер может назвать цену вдвое ниже, но сравнение некорректное — агентства берут на себя риски, которые фрилансеры перекладывают на клиента. ### Что на самом деле определяет стоимость Стоимость Flutter-приложения сводится к пяти рычагам. Всё остальное — детали. #### 1. Объём — самая большая переменная Большинство основателей сильно недооценивают объём. Вы описываете «простое приложение для трекинга тренировок» и в голове это пять экранов. На деле это: - Онбординг (3–4 экрана) - Аутентификация (email, Google, Apple — у каждой свои граничные случаи) - Основная функциональность (сами тренировки) - Настройки, профиль, удаление аккаунта (требование обоих сторов) - Платежи и подписки (если монетизировано) - Push-уведомления - Оффлайн-режим - Админ-панель - Интеграция аналитики - Листинги в сторах и процесс ревью Это «простое приложение» — 40+ экранов и минимум 3 месяца работы. Разрастание объёма — не сбой в процессе оценки: так происходит всякий раз, когда реальное приложение сталкивается с реальными пользователями. #### 2. Сложность бэкенда Flutter — это фронтенд. Стоимость примерно удваивается, когда нужен серьёзный бэкенд: кастомные API, real-time, сложные модели данных, админ-дашборды, интеграции со сторонними сервисами. MVP на Firebase дешёвый, потому что Firebase из коробки берёт на себя auth, базу, хранилище и push-уведомления. Как только нужны PostgreSQL, кастомная бизнес-логика, вебхуки или требования compliance — вы строите команду бэкенда параллельно с командой приложения. #### 3. Интеграции Каждая интеграция — это неделя работы, которую вы не запланировали. Платёжные процессоры, карты, аналитика, синхронизация с CRM, календарь, видео, чат, биометрия — каждая выглядит как «просто добавь SDK» в питче и превращается в двухнедельный дебаг граничных случаев на Android 10. Эмпирическое правило: каждая крупная интеграция добавляет к проекту 270–720 тыс. ₽. #### 4. Дизайн Кастомный, отполированный UI удваивает бюджет на дизайн по сравнению с использованием стандартной библиотеки компонентов. Кастомные анимации, иллюстрации, тёмная и светлая темы с реальным вниманием к деталям, соответствие требованиям доступности — всё это стоит реальных денег. И это разница между приложением, которое выглядит дешёвым, и тем, которое таким не выглядит. Хороший Flutter-дизайн стоит 540 тыс. ₽ – 1,8 млн ₽ в зависимости от уровня, для сложных приложений — больше. Экономить на дизайне — самый частый способ, которым основатели делают своё приложение хуже, чем оно могло бы быть. #### 5. Compliance Если вы в финтехе, здравоохранении или работаете с регулируемыми данными — добавьте 30–50% к каждой оценке выше. HIPAA, PCI-DSS, GDPR с реальными журналами аудита, SOC 2 — это не галочки, это архитектурные ограничения, которые затрагивают каждую часть кодовой базы. Приложение для здравоохранения — это не обычное приложение с политикой конфиденциальности. Это обычное приложение, построенное поверх инфраструктуры, которая предполагает, что аудиторы будут её читать. ### Что вы получаете на каждом уровне #### Уровень MVP: 1 350 000–2 700 000 ₽ Небольшая команда, 2–3 месяца. Вы получаете: - 5–8 ключевых функций, iOS и Android - Бэкенд на Firebase (auth, Firestore, возможно Cloud Functions) или лёгкий REST API - Базовый UI/UX-дизайн на основе библиотеки компонентов - Аутентификацию пользователей - Базовую аналитику - Деплой в сторы - Минимальное тестирование — баги вы найдёте в продакшене Этого достаточно, чтобы валидировать продуктовую идею и дать что-то пользователям в руки. Этого недостаточно, чтобы масштабироваться до 100 000 пользователей. Основатели, которые ожидают, что MVP-качество переживёт рост, — те самые, кто переписывает приложение через 18 месяцев. #### Уровень бизнес-приложения: 2 700 000–5 400 000 ₽ Полноценная команда — Flutter-разработчики, дизайнер, бэкенд-инженер на частичной занятости, QA. 3–5 месяцев. Вы получаете: - 15–20 функций для iOS, Android и Web - Кастомный UI/UX-дизайн - Кастомный бэкенд с продвинутыми API-интеграциями - Push-уведомления с сегментацией - Оффлайн-функциональность - Корректно настроенную аналитику и отчёты о сбоях - Админ-дашборд - Проверку доступности - Ассеты для оптимизации в сторах Так выглядит реальное бизнес-приложение. Большинство стартапов с привлечённым финансированием находятся на этом уровне. #### Уровень e-commerce: 4 500 000–8 100 000 ₽ Выделено отдельно, потому что у e-commerce-приложений общий набор сложных задач: платежи, склад, мультиролевой доступ. 4–6 месяцев. Вы получаете всё с уровня бизнес-приложения, плюс: - Полный каталог товаров с поиском и фильтрами - Корзину и оформление заказа - Интеграцию платёжного шлюза (Stripe, локальные процессоры или и то и другое) - Отслеживание заказов - Отзывы и рейтинги - Управление складом - Панели администратора и поставщиков (если магазин мультивендорный) Причина, по которой e-commerce стоит дороже бизнес-приложения сопоставимого объёма, — не экраны, а граничные случаи. Неудачные платежи, возвраты, частичные отгрузки, брошенные корзины, расчёт налогов, борьба с фродом. Каждый из них — это функция, которая отнимает немало времени, если делать её как следует. #### Уровень enterprise или AI: от 8 100 000 ₽ Полная команда — несколько Flutter-инженеров, выделенная команда бэкенда, дизайнер, QA, DevOps, project manager. 6+ месяцев, часто на постоянной основе. Вы получаете: - Сложные процессы на всех платформах - Интеграцию AI/ML (оркестрация LLM, on-device инференс, кастомные пайплайны моделей) - Enterprise-безопасность и работу по compliance - Кастомную инфраструктуру бэкенда, мультитенантную архитектуру - Ролевой доступ, админ-порталы - Глубокие интеграции со сторонними системами (ERP, CRM, hardware SDK, кастомные протоколы) - Работу над производительностью (60fps на слабых устройствах, холодный старт менее 2 секунд) - Релизные поезда, feature flags, поэтапная раскатка - Выделенную поддержку, SLA после запуска Проект дороже 13,5 млн ₽ — это, как правило, уже не проект, а выделенная команда. ### Где агентства накручивают сметы Несколько паттернов, о которых стоит знать, потому что они почти универсальны. **Завышенные часовые ставки.** Разработчик, обходящийся агентству в 3,6 тыс. ₽/час, выставляется вам в счёте по 8,1 тыс. ₽/час. Это нормально и правильно — агентствам нужна маржа, чтобы покрывать продажи, менеджмент, налоги и простои между проектами. Но когда смешанная ставка европейского агентства превышает 13,5 тыс. ₽/час, вы платите за бренд, а не за компетенции. **Discovery как отдельная статья.** «Мы проведём 4-недельный discovery за 1,4 млн ₽, прежде чем сможем оценить проект». Иногда это оправдано, часто — способ собрать деньги до того, как кто-то на что-то реально подписался. Спросите, что конкретно будет в результате и можно ли использовать этот результат, если вы не пойдёте с ними дальше. **Буфер поверх буфера.** Хорошие агентства добавляют 20–30% буфера к оценкам, потому что софт непредсказуем. Плохие добавляют 50%+ и называют это «управлением рисками». Если оценка кажется завышенной — попросите построчную разбивку. Адекватные агентства её предоставят. **Enterprise-ценник за MVP-работу.** Некоторые конторы выкатывают 18 млн ₽ за то, что должно быть MVP за 2,7 млн ₽, потому что умеют продавать только в одном ценовом сегменте. Если оценка не соответствует объёму — агентство либо не понимает объём, либо не хочет его понимать. ### Как реально соотносятся региональные ставки в 2026 году Примерные смешанные ставки агентств, которые мы видим на рынке: - **Агентства из США:** 13,5–22,5 тыс. ₽/час - **Западная Европа (UK, Германия, Нидерланды):** 9–16,2 тыс. ₽/час - **Восточная Европа:** 4,5–8,6 тыс. ₽/час - **Южная и Юго-Восточная Азия:** 2,3–5 тыс. ₽/час (более широкий разброс по качеству) Разброс по качеству внутри каждого региона больше, чем разброс между регионами. Хорошее восточноевропейское агентство в работе с Flutter всегда обойдёт посредственное американское — у Flutter глубокие корни в европейском сообществе разработчиков, и часть самых скачиваемых пакетов на pub.dev сделана командами из этого региона. То, за что вы платите на верхних уровнях, — не обязательно более качественный код. Это более качественная коммуникация, более сильный проджект-менеджмент и сниженный риск иметь дело с конторой, которая исчезнет посреди проекта. Это реальные вещи, за которые имеет смысл платить, но не всегда то, что вам нужно. ### Фреймворк для бюджетирования вашего проекта Забудьте про таблицы. Ответьте на четыре вопроса: 1. **Что произойдёт, если приложение опоздает на 3 месяца?** Если ответ «мы упустим критическое окно» — закладывайте минимум уровень бизнес-приложения. Если ответ «мы предпочтём узнать прямо сейчас, если идея нерабочая» — стартуйте с MVP-уровня. 2. **Кто ваши первые 1 000 пользователей?** Если у вас есть список — можно валидировать через MVP. Если вы рассчитываете привлечь их маркетингом — нужно отполированное приложение, то есть уровень бизнес-приложения и выше. 3. **Подписочная или разовая бизнес-модель?** Подписочным бизнесам нужна нормальная инфраструктура подписок, а это дороже, чем кажется. Закладывайте 20–30% сверху на биллинг, entitlements и процессы поддержки клиентов. 4. **Каков ваш реальный технический риск?** To-do приложение — низкий риск. Видеозвонки — высокий. Работайте с агентством, у которого есть опыт в вашей категории риска: мы делали финтех в [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) и real-time AI-чат в [Arcana](https://ru.nerdy.pro/portfolio/arcana). 1,8 млн ₽, сэкономленные на неопытной команде, потом обойдутся в 7,2 млн ₽ на исправлениях. ### В заключение Самое дешёвое предложение почти никогда не лучшее. Как и самое дорогое. Вы ищете агентство, которое понимает ваш объём, говорит, сколько он реально будет стоить, и честно говорит, что в работу не войдёт. Размытые оценки с огромными диапазонами — тревожный признак: они означают, что агентство толком не разбиралось в вашем проекте. Уже проверили идею с помощью Telegram-бота? Расчёт стоимости тогда другой — типичную шестинедельную миграцию из бота в приложение и её стоимость мы разбираем в посте [«У вас Telegram-бот с 50 000 пользователей»](https://ru.nerdy.pro/blog/telegram-bot-to-app-6-weeks). Если вы пытаетесь понять, сколько должен стоить именно ваш проект, мы с удовольствием бесплатно дадим конкретную оценку. [Свяжитесь с нами](https://ru.nerdy.pro/contact) с брифом, и мы вернёмся со сметным предложением — построчно, со сроками и тем, что мы делать не будем. --- *Nerdy Production — Flutter-агентство, делающее приложения в финтехе, здравоохранении и ритейле. Наши [open-source Flutter-пакеты](https://ru.nerdy.pro/open-source) используются разработчиками по всему миру.* ## Как генерировать PDF на Rust с помощью Skia: полное руководство по крейту skia-safe https://ru.nerdy.pro/blog/skia-rust-pdf-rendering Программная генерация PDF — одна из тех задач, которые кажутся простыми, пока дело не доходит до реализации. Большинство библиотек либо дают низкоуровневый поток байтов, с которым приходится бороться, либо шаблонизатор высокого уровня, который ломается, как только требуется точный контроль над вёрсткой, типографикой или графикой. Но есть золотая середина — тот же графический движок, который рендерит каждую веб-страницу при печати в PDF в Chrome, каждый экран Android и каждый кадр приложения на Flutter: **Google Skia**. В этом руководстве разберём, как использовать Skia из Rust через крейт `skia-safe` для создания векторных PDF с полным контролем над текстом, фигурами, изображениями и многостраничной вёрсткой. ### Ключевые выводы - **Skia** — это open-source библиотека 2D-графики от Google, в активной разработке с 2005 года, с более чем 2 миллионами загрузок Rust-обёрток на [crates.io](https://crates.io/crates/skia-safe). - Крейт **skia-safe** (v0.93) предоставляет безопасные, идиоматичные Rust-обёртки с паттерном typestate, который предотвращает создание некорректных PDF на этапе компиляции. - Skia создаёт **настоящие векторные PDF** с выделяемым текстом, а не растеризованные изображения — тот же подход, который Chrome использует для печати в PDF. - Поддерживает **архивное соответствие PDF/A**, **теговые/доступные PDF** (PDF/UA), **подмножества шрифтов** через HarfBuzz и **многостраничные документы** с разными размерами страниц. - Предсобранные бинарники поставляются по умолчанию, поэтому первая сборка занимает **менее одной минуты** вместо 20–40 минут из исходников. ### Что такое Skia и зачем использовать её для генерации PDF на Rust Skia — это open-source библиотека 2D-графики, которую поддерживает Google. Она в активной разработке с 2005 года — один из самых проверенных временем движков рендеринга. Когда вы печатаете веб-страницу в PDF в Google Chrome, PDF-бэкенд Skia транслирует отрисованную страницу в PDF-файл. Когда Android отрисовывает свой интерфейс — за это отвечает Skia. Flutter использует Skia в качестве движка рендеринга для кроссплатформенных приложений. Чем Skia привлекательна для программной генерации PDF на Rust: - **Настоящий векторный вывод.** Текст остаётся выделяемым, фигуры сохраняют чёткость при любом масштабе, а размеры файлов остаются разумными — потому что Skia транслирует свои операции рисования в нативные PDF-примитивы, а не растеризует пиксели. - **Полноценный 2D-графический API.** Кривые Безье, градиенты, аффинные преобразования, области отсечения, режимы наложения, настройки типографики — всё, что ожидается от профессиональной графической библиотеки, и всё это напрямую отображается в PDF. - **Теговые и доступные PDF.** Skia поддерживает деревья структурных элементов PDF и идентификаторы узлов, что позволяет создавать документы, соответствующие стандартам доступности PDF/UA. - **Архивное соответствие PDF/A.** Один флаг в метаданных включает вывод PDF/A-2b для долгосрочного хранения документов — обязательное требование для юридических, медицинских и государственных документов. - **Подмножества шрифтов.** При встраивании шрифтов Skia удаляет неиспользуемые глифы через HarfBuzz, сохраняя компактный размер файлов. ### Настройка крейта skia-safe для генерации PDF на Rust Крейт `skia-safe` предоставляет безопасные, идиоматичные Rust-обёртки для Skia. Он оборачивает C++-библиотеку через FFI и даёт API, естественный для Rust: с семантикой владения, гарантиями времени жизни и паттерном typestate, который отлавливает целые категории ошибок на этапе компиляции. Добавьте в проект: ```toml [dependencies] skia-safe = "0.93" ``` Фича `pdf` включена по умолчанию, дополнительных флагов не нужно. По умолчанию крейт скачивает предсобранные бинарники Skia для вашей платформы, поэтому первая сборка занимает меньше минуты, а не 20–40 минут, которые потребовались бы для компиляции Skia из исходников. ### Как сгенерировать PDF-файл на Rust с помощью Skia Основная концепция проста: создать документ, начать страницу, рисовать на холсте, завершить страницу, закрыть документ. Вот минимальный пример, создающий одностраничный PDF: ```rust use skia_safe::pdf; fn main() { let mut output = Vec::new(); // Создаём документ — US Letter (612 x 792 пункта) let mut document = pdf::new_document(&mut output, None) .begin_page((612, 792), None); let canvas = document.canvas(); // Рисуем залитый прямоугольник let mut paint = skia_safe::Paint::default(); paint.set_color(skia_safe::Color::from_rgb(41, 98, 255)); canvas.draw_rect( skia_safe::Rect::from_xywh(72.0, 72.0, 468.0, 100.0), &paint, ); // Рисуем текст let font = skia_safe::Font::default(); paint.set_color(skia_safe::Color::WHITE); canvas.draw_str("Hello from Skia + Rust", (90.0, 130.0), &font, &paint); // Финализируем document.end_page().close(); std::fs::write("hello.pdf", &output).unwrap(); } ``` Запустите, откройте `hello.pdf` — и вы увидите синий прямоугольник с белым текстом. Всё в виде векторной графики, полностью выделяемой и масштабируемой. ![Сгенерированный PDF в просмотрщике — синий прямоугольник с белым текстом, отрисованный как векторная графика на странице US Letter с помощью skia-safe на Rust](https://ru.nerdy.pro/blog/skia-rust-pdf-rendering.webp) ### Безопасность на этапе компиляции с паттерном typestate Одна из самых элегантных особенностей Rust-обёрток — паттерн typestate для типа `Document`. Документ переходит между двумя состояниями: `Open` (готов начать страницу) и `OnPage` (активно рисуем). ```rust // Document — можно начать страницу или закрыть документ let document = pdf::new_document(&mut output, None); // Document — можно обращаться к холсту или завершить страницу let page = document.begin_page((612, 792), None); let canvas = page.canvas(); // Обратно к Document — можно начать другую страницу или закрыть let document = page.end_page(); document.close(); ``` Если вы попытаетесь вызвать `canvas()` у документа без открытой страницы или попытаетесь `close()` документ при активной странице — код не скомпилируется. Это не проверка во время выполнения и не assertion — это гарантия на этапе компиляции, обеспечиваемая системой типов Rust. Вы не можете создать некорректный PDF, потому что компилятор это предотвращает. Сравните с библиотеками PDF на C++ или Python, где такие ошибки приводят к повреждённому выводу, незаметным сбоям или падениям во время выполнения. В Rust с `skia-safe` компилятор — ваш корректор. ### Добавление метаданных PDF для SEO и доступности Метаданные PDF — это то, что поисковые системы, системы управления документами и инструменты доступности используют для понимания вашего документа. Skia предоставляет их через структуру `Metadata`: ```rust use skia_safe::pdf::{self, Metadata}; let metadata = Metadata { title: "Финансовый отчёт Q4".to_string(), author: "Финансовый отдел".to_string(), subject: "Квартальные результаты и прогнозы".to_string(), creator: "Генератор отчётов v2.1".to_string(), ..Default::default() }; let mut output = Vec::new(); let document = pdf::new_document(&mut output, Some(&metadata)); ``` Для архивного соответствия включите PDF/A одним флагом: ```rust let metadata = Metadata { pdf_a: true, ..Default::default() }; ``` Это создаёт вывод, соответствующий PDF/A-2b — подходящий для юридических документов, медицинских записей и любого контента, который должен оставаться читаемым через десятилетия. ### Создание многостраничных PDF-документов на Rust Реальные документы редко бывают одностраничными. Skia естественно работает с многостраничными PDF — каждый вызов `begin_page` начинает новую страницу, причём страницы могут иметь разные размеры: ```rust let mut output = Vec::new(); let document = pdf::new_document(&mut output, None); // Страница 1 — A4, книжная let mut page = document.begin_page((595, 842), None); draw_cover_page(page.canvas()); let document = page.end_page(); // Страница 2 — A4, альбомная let mut page = document.begin_page((842, 595), None); draw_data_table(page.canvas()); let document = page.end_page(); // Страница 3 — снова книжная let mut page = document.begin_page((595, 842), None); draw_charts(page.canvas()); let document = page.end_page(); document.close(); ``` Смешанные ориентации и размеры страниц в одном документе — то, с чем многие PDF-библиотеки испытывают трудности, — работают из коробки. ### Рисование фигур, текста и путей в Skia PDF `Canvas` в Skia — это полноценная 2D-поверхность для рисования. Всё, что вы рисуете на ней, преобразуется в векторные PDF-примитивы. #### Фигуры и пути ```rust let canvas = page.canvas(); let mut paint = Paint::default(); paint.set_anti_alias(true); // Залитый прямоугольник paint.set_color(Color::from_rgb(59, 130, 246)); paint.set_style(skia_safe::PaintStyle::Fill); canvas.draw_rect(Rect::from_xywh(50.0, 50.0, 200.0, 100.0), &paint); // Круг с обводкой paint.set_style(skia_safe::PaintStyle::Stroke); paint.set_stroke_width(2.0); canvas.draw_circle((300.0, 100.0), 50.0, &paint); // Произвольный путь Безье let mut path = skia_safe::Path::new(); path.move_to((400.0, 50.0)); path.cubic_to((450.0, 20.0), (500.0, 80.0), (550.0, 50.0)); path.line_to((550.0, 150.0)); path.close(); paint.set_style(skia_safe::PaintStyle::Fill); canvas.draw_path(&path, &paint); ``` #### Рендеринг текста с выделяемым выводом ```rust let typeface = skia_safe::Typeface::from_name("Helvetica", skia_safe::FontStyle::bold()) .unwrap_or_else(skia_safe::Typeface::default); let font = skia_safe::Font::from_typeface(&typeface, 24.0); let mut paint = Paint::default(); paint.set_color(Color::BLACK); canvas.draw_str("Заголовок раздела", (72.0, 72.0), &font, &paint); // Текст поменьше для основного содержимого let body_font = skia_safe::Font::from_typeface(&typeface, 12.0); canvas.draw_str("Основной текст.", (72.0, 100.0), &body_font, &paint); ``` Текст в итоговом PDF выделяем и доступен для поиска — это не растеризованные изображения символов. #### Аффинные преобразования ```rust canvas.save(); canvas.translate((200.0, 300.0)); canvas.rotate(45.0, None); canvas.draw_rect(Rect::from_xywh(-50.0, -25.0, 100.0, 50.0), &paint); canvas.restore(); ``` Преобразования можно комбинировать, и они напрямую транслируются в матрицу преобразования PDF — без всякой растеризации. ### Встраивание изображений в PDF, сгенерированные на Rust Именно на встраивании изображений многие библиотеки без нужды раздувают размер файла. Skia подходит к этому разумно: ```rust let image_data = std::fs::read("photo.jpg").unwrap(); let data = skia_safe::Data::new_copy(&image_data); let image = skia_safe::Image::from_encoded(data).expect("Не удалось декодировать изображение"); canvas.draw_image(&image, (72.0, 200.0), None); ``` Когда поддержка JPEG-кодирования настроена в метаданных, Skia передаёт JPEG-данные напрямую в PDF без перекодирования — сохраняя качество и минимальный размер файла. Без этого изображения сжимаются deflate-алгоритмом, что может значительно увеличить размер файла для фотографий. Управление через метаданные: ```rust let metadata = Metadata { encoding_quality: 85, // Качество JPEG (101 = без потерь/deflate) ..Default::default() }; ``` ### Сравнение Rust-библиотек для генерации PDF: skia-safe vs printpdf vs krilla Экосистема Rust предлагает несколько подходов к генерации PDF. Сравнение: | Библиотека | Тип | Вектор | Подмножества шрифтов | PDF/A | Теговый PDF | Движок вёрстки | Зрелость | | --- | --- | --- | --- | --- | --- | --- | --- | | **skia-safe** | Обёртка Skia (C++ FFI) | Да | Да (HarfBuzz) | Да | Да | Нет (canvas API) | 20+ лет (Skia) | | **printpdf** | Чистый Rust | Да | Ограничено | Нет | Нет | Нет | Стабильный | | **genpdf** | Чистый Rust (на printpdf) | Да | Ограничено | Нет | Нет | Базовый | Не поддерживается | | **krilla** | Чистый Rust (на pdf-writer) | Да | Да (OpenType) | Да | Да | Нет | Новее | | **Headless Chrome** | Браузерный runtime | Да | Да | Нет | Нет | Полный HTML/CSS | Зрелый | **skia-safe** даёт полноценный 2D-графический API с более чем 20-летним опытом разработки в Google. Векторный вывод, подмножества шрифтов, теговые PDF, поддержка PDF/A. Компромисс — зависимость от C++, хотя с предсобранными бинарниками на практике она почти не мешает. **printpdf** — чистый Rust без C-зависимостей. Хорошо работает для простых документов, но требует ручного позиционирования каждого элемента. Нет движка вёрстки, ограниченная поддержка типографики. **genpdf** строит API вёрстки более высокого уровня поверх printpdf. Чистый Rust и проще в использовании для простых документов, но проект не поддерживается уже несколько лет. **krilla** — более новая библиотека на чистом Rust с современным API, отличной поддержкой OpenType и функциями вроде градиентов и режимов наложения. Хороший выбор, если вам важно обойтись без C-зависимостей, хотя она проверена меньше, чем Skia. **Headless Chrome / Puppeteer** даёт полный рендеринг HTML/CSS, но требует поставки браузерного runtime. Тяжеловесно, но полезно, когда нужно рендерить сложные веб-макеты. Выбор зависит от ваших ограничений. Если вам нужен точный контроль над графикой и типографикой, проверенная надёжность и вас устраивает C++-зависимость — Skia будет самым мощным вариантом. Если нужно решение на чистом Rust — стоит оценить krilla или printpdf. ### Известные ограничения PDF-бэкенда Skia PDF-бэкенд Skia покрывает почти всё, но у него есть границы, о которых стоит знать: **Поддержка режимов наложения.** Большинство стандартных PDF-режимов наложения работают (Multiply, Screen, Overlay и т.д.), но некоторые специфичные для Skia режимы, такие как `SrcATop`, `DstATop`, `Xor` и `Plus`, не имеют PDF-эквивалента. Если вы их используете, результат может отличаться от того, что вы видите на экране. **Растеризация как запасной вариант.** Функции без нативного PDF-представления — такие как перспективные преобразования текста — растеризуются с DPI, указанным в метаданных (`raster_dpi`, по умолчанию 72). Для печатного качества поднимите его до 300 и выше — ценой большего размера файлов. **Сборка из исходников.** Если вам нужно настроить возможности Skia сверх того, что дают предсобранные бинарники, сборка из исходников занимает 20–40 минут. Фича `binary-cache` (включена по умолчанию) позволяет этого избежать для стандартных конфигураций. ### Продакшен-архитектура для генерации PDF на Rust Для продакшен-систем, генерирующих множество документов — счетов, отчётов, сертификатов — чистая архитектура разделяет контент и рендеринг: ```rust struct DocumentContent { title: String, sections: Vec
, footer: String, } struct Section { heading: String, body: String, charts: Vec, } fn render_document(content: &DocumentContent) -> Vec { let mut output = Vec::new(); let metadata = Metadata { title: content.title.clone(), creator: "My Document Engine".to_string(), ..Default::default() }; let mut doc = pdf::new_document(&mut output, Some(&metadata)); for section in &content.sections { let mut page = doc.begin_page((595, 842), None); // A4 render_section(page.canvas(), section); doc = page.end_page(); } doc.close(); output } ``` Такое разделение означает, что ваша бизнес-логика никогда не касается PDF API напрямую. Контент поступает на вход, байты — на выход. Вы можете тестировать рендеринг независимо, заменить PDF-бэкенд без изменения бизнес-логики и распараллелить генерацию документов по потокам — поскольку каждый `Document` владеет своим выходным буфером. ### С чего начать генерацию PDF на Rust через Skia Добавьте `skia-safe` в ваш `Cargo.toml`, напишите несколько строк кода для рисования и откройте полученный PDF. Кривая обучения пологая, если вы использовали любой 2D-графический API — Canvas, Cairo, Core Graphics или даже HTML5 Canvas. Концепции переносятся напрямую. Крейт `skia-safe` находится на версии 0.93 с более чем [2 миллионами загрузок на crates.io](https://crates.io/crates/skia-safe). Кеш предсобранных бинарников означает, что первая сборка быстрая, а паттерн typestate гарантирует, что ваш первый PDF будет корректным. Для приложений, которым нужен надёжный, качественный PDF-вывод — будь то счета, отчёты, билеты, сертификаты или любой документ, где точность вёрстки имеет значение — Skia через Rust даёт вам тот же движок рендеринга, который работает на миллиардах устройств, с гарантиями безопасности, которыми славится Rust. Именно этот подход мы использовали при создании [dxpdf](https://ru.nerdy.pro/open-source/dxpdf) — нашего open-source конвертера DOCX в PDF, написанного на Rust и использующего Skia. Он конвертирует документы Word в высококачественные PDF за ~115 мс без необходимости установки Microsoft Office или LibreOffice. ### Часто задаваемые вопросы #### Какая Rust-библиотека лучше всего подходит для генерации PDF? Зависит от ваших требований. **skia-safe** — наиболее функционально полный вариант, предлагающий векторный вывод, подмножества шрифтов, теговые PDF и поддержку PDF/A через движок Google Skia. Для чистого Rust без C-зависимостей **krilla** и **printpdf** — сильные альтернативы. Для рендеринга HTML/CSS в PDF headless Chrome остаётся наиболее надёжным подходом. #### Skia создаёт векторные или растеризованные PDF? Skia создаёт **настоящие векторные PDF**. Текст выделяем и доступен для поиска, фигуры остаются чёткими при любом масштабе, а операции рисования транслируются в нативные PDF-примитивы. Растеризация происходит только как запасной вариант для функций, не имеющих нативного PDF-представления, таких как перспективные трансформации. #### Можно ли создавать доступные и PDF/A-совместимые документы с skia-safe? Да. Skia поддерживает деревья структурных элементов PDF и идентификаторы узлов для создания теговых, доступных PDF, соответствующих стандартам PDF/UA. Для архивного соответствия установка `pdf_a: true` в структуре `Metadata` создаёт вывод, соответствующий PDF/A-2b. #### Сколько времени занимает сборка skia-safe? С включённой по умолчанию фичей `binary-cache` первая сборка занимает **менее одной минуты**, поскольку для вашей платформы скачиваются предсобранные бинарники Skia. Без кеша (сборка Skia из C++-исходников) — рассчитывайте на 20–40 минут. #### skia-safe — это тот же движок, который Chrome использует для печати в PDF? Да. Когда вы используете функцию Chrome «Сохранить как PDF» или «Печать в PDF», браузер рендерит страницу через PDF-бэкенд Skia — тот же движок, который `skia-safe` предоставляет для Rust. Этот же движок рендеринга Android использует для отрисовки интерфейса, а Flutter — для кроссплатформенных приложений. #### Как skia-safe работает со шрифтами в PDF? Skia встраивает шрифты напрямую в PDF. С интеграцией HarfBuzz (включается через фичу `textlayout`) выполняется построение подмножеств шрифтов — удаление неиспользуемых глифов для поддержания компактного размера файлов. Шрифты TrueType используют кодирование по идентификаторам глифов для точного рендеринга. ## Шифрование простыми словами: от AES до постквантовой криптографии и сквозного шифрования https://ru.nerdy.pro/blog/encryption-explained Каждое отправленное сообщение, каждый платёж и каждый введённый пароль опирается на шифрование. Это невидимый слой, который обеспечивает конфиденциальность цифровой жизни — и он встроен практически в каждое приложение, от которого зависит ваш бизнес. В этой статье мы разберём, как разные виды шифрования защищают реальные приложения, почему такие технологии, как эллиптические кривые и постквантовые алгоритмы, важны для ваших продуктов, и что решение Instagram* отказаться от сквозного шифрования говорит о противостоянии между приватностью и регулированием. ### Что на самом деле делает шифрование Шифрование превращает читаемые данные (открытый текст) в нечитаемую форму (шифротекст) с помощью секретного значения — ключа. Только тот, кто обладает правильным ключом, может обратить процесс и восстановить исходные данные. Важнее всего два свойства: - **Конфиденциальность** — злоумышленник, перехвативший шифротекст, не узнает ничего об исходных данных. - **Целостность** — современные схемы шифрования также обнаруживают, был ли шифротекст изменён при передаче. Всё практическое шифрование делится на два семейства: симметричное и асимметричное. Реальные системы используют их вместе, и понимание причин помогает принимать обоснованные решения о приложениях, которые вы создаёте или используете. ### Симметричное шифрование: рабочая лошадка защиты данных В симметричном шифровании один и тот же ключ используется для шифрования и дешифрования. Представьте его как сейф с единственной комбинацией: любой, кто знает комбинацию, может его открыть, и именно секретность комбинации обеспечивает безопасность содержимого. #### Где вы сталкиваетесь с ним каждый день **AES (Advanced Encryption Standard)** — наиболее широко используемый симметричный шифр в мире. Он принят NIST в 2001 году, поддерживает ключи длиной 128, 192 или 256 бит и аппаратно ускоряется практически во всех современных процессорах. Вот где AES защищает ваши данные на практике: - **Облачное хранилище.** Когда вы загружаете файлы в AWS S3, Google Cloud Storage или Azure Blob Storage, они шифруются при хранении с помощью AES-256. Даже если кто-то получит физический доступ к оборудованию, данные будут нечитаемы без ключа. - **Шифрование баз данных.** PostgreSQL, MongoDB и MySQL предлагают шифрование на основе AES для защиты данных на диске. Это означает, что украденная резервная копия базы данных бесполезна без ключей шифрования. - **HTTPS / TLS.** Каждый раз, когда ваш браузер показывает значок замка, основную работу выполняет AES. TLS 1.3 использует AES-GCM (Galois/Counter Mode) в качестве основного шифра — он одновременно шифрует данные и проверяет их целостность, защищая всё: от учётных данных до вызовов API. - **Мобильные приложения.** И файловое шифрование Android, и Data Protection от Apple используют AES-256 для шифрования данных на устройстве. Когда пользователь блокирует телефон, ключи шифрования удаляются из памяти, делая сохранённые данные недоступными. - **Обработка платежей.** PCI DSS — стандарт безопасности для обработки данных кредитных карт — требует шифрования AES для данных держателей карт как при передаче, так и при хранении. **ChaCha20-Poly1305** — ещё один симметричный шифр, набирающий популярность. Он быстрее AES на устройствах без аппаратного ускорения AES — особенно на старых смартфонах и IoT-устройствах. Google выбрал его для TLS-соединений на Android, и это шифр по умолчанию в WireGuard VPN. #### Попробуйте сами: AES-шифрование в терминале Никакого специального ПО не нужно — OpenSSL предустановлен в macOS, Linux и большинстве серверных сред. Откройте терминал и попробуйте эти команды. Зашифровать сообщение с помощью AES-256: ```bash echo "I want to secure my app with AES" | openssl enc -aes-256-cbc -a -salt -pass pass:nerdy_pro ``` На выходе будет блок случайного на вид Base64-текста — ваш зашифрованный текст. Чтобы расшифровать его, возьмите этот вывод и выполните: ```bash echo "U2FsdGVkX181dnX8SWIhZTumWK0Uc7Kh947omVSNpH8lujB43L5OjlubfkGa3Y1dyxj5cfzyPZjhWiyGzIWdPw==" | openssl enc -aes-256-cbc -a -d -salt -pass pass:nerdy_pro ``` Вы получите исходное сообщение обратно. OpenSSL может показать предупреждение: `*** WARNING : deprecated key derivation used.` — это ожидаемо. Оно означает, что OpenSSL по умолчанию использует устаревший метод формирования ключа при использовании `-pass pass:`. В продакшене следует использовать `-pbkdf2` для более надёжного формирования ключей, но для демонстрации стандартный метод подходит. Теперь попробуйте изменить один символ в пароле и расшифровать снова — команда завершится с ошибкой. Это демонстрирует основное свойство симметричного шифрования: без точного ключа данные невосстановимы. Вы также можете шифровать целые файлы: ```bash # Зашифровать openssl enc -aes-256-cbc -salt -in report.pdf -out report.pdf.enc -pass pass:nerdy_pro # Расшифровать openssl enc -aes-256-cbc -d -in report.pdf.enc -out report-decrypted.pdf -pass pass:nerdy_pro ``` #### Проблема распределения ключей Симметричное шифрование быстрое и эффективное, но обе стороны должны иметь общий секретный ключ до начала обмена данными. Если ваше приложение обслуживает миллион пользователей, вы не сможете безопасно предварительно раздать миллион разных ключей. Эту проблему решает асимметричная криптография. ### Асимметричное шифрование: доверие между незнакомцами Асимметричное шифрование использует пару ключей: **открытый ключ**, который можно свободно распространять, и **закрытый ключ**, который должен оставаться в секрете. Данные, зашифрованные открытым ключом, могут быть расшифрованы только соответствующим закрытым ключом. #### RSA: основа интернет-безопасности RSA, опубликованный в 1977 году, стал первой практической криптосистемой с открытым ключом. Его безопасность основана на сложности факторизации произведения двух очень больших простых чисел — задачи, которую легко поставить, но практически невозможно решить с помощью современных технологий. Вот как RSA работает в реальных приложениях: - **SSL/TLS-сертификаты.** Когда вы посещаете сайт по HTTPS, сервер предъявляет сертификат, подписанный с помощью RSA (или ECC). Ваш браузер проверяет эту подпись, чтобы убедиться, что вы общаетесь с настоящим сервером, а не с самозванцем. Центры сертификации, такие как Let's Encrypt, выдают миллионы сертификатов с подписью RSA. - **Подпись кода.** Когда вы загружаете приложение из Apple App Store или устанавливаете Windows-приложение с цифровой подписью, подписи RSA подтверждают, что код не был изменён с момента публикации разработчиком. - **Шифрование электронной почты.** PGP/GPG и S/MIME используют RSA для шифрования писем и их подписи, обеспечивая как конфиденциальность, так и подлинность отправителя. - **SSH-доступ.** Системные администраторы используют пары ключей RSA для безопасного доступа к серверам без паролей — `ssh-keygen -t rsa` — одна из самых распространённых команд в любом DevOps-процессе. #### Попробуйте сами: RSA-шифрование в терминале Сгенерируйте пару ключей RSA: ```bash # Сгенерировать 2048-битный закрытый ключ openssl genrsa -out private.pem 2048 # Извлечь открытый ключ openssl rsa -in private.pem -pubout -out public.pem ``` Теперь зашифруйте сообщение открытым ключом и расшифруйте закрытым: ```bash # Зашифровать — это может сделать любой, у кого есть ваш открытый ключ echo "I want to secure my app with RSA" | openssl pkeyutl -encrypt -pubin -inkey public.pem -out message.enc # Расшифровать — это может сделать только владелец закрытого ключа openssl pkeyutl -decrypt -inkey private.pem -in message.enc ``` В этом и заключается основная идея: ваш деловой партнёр шифрует данные вашим открытым ключом, отправляет шифротекст по любому каналу (даже по электронной почте), и только вы можете его прочитать. Без закрытого ключа зашифрованный файл бесполезен. #### Гибридный подход На практике асимметричное шифрование слишком медленное для шифрования больших объёмов данных. Вместо этого реальные системы используют **гибридный подход**: с помощью RSA (или ECC) стороны безопасно обмениваются одноразовым симметричным ключом, а затем AES на высокой скорости шифрует основной объём данных. Именно так работают TLS, SSH и PGP под капотом. ### Криптография на эллиптических кривых: более высокая безопасность при меньших ключах Криптография на эллиптических кривых (ECC) обеспечивает тот же уровень безопасности, что и RSA, но со значительно меньшими ключами. Это напрямую даёт более быстрые соединения, компактные сертификаты, меньший расход трафика и лучшую производительность на мобильных и IoT-устройствах. #### Практическая разница | Уровень безопасности | Размер ключа RSA | Размер ключа ECC | | --- | --- | --- | | Стандартный (128 бит) | 3072 бита | 256 бит | | Высокий (192 бит) | 7680 бит | 384 бита | | Ультра (256 бит) | 15360 бит | 521 бит | 256-битный ключ ECC обеспечивает ту же защиту, что и 3072-битный ключ RSA — это **уменьшение размера ключа в 12 раз**. Для приложений, обрабатывающих тысячи соединений в секунду или работающих на устройствах с батарейным питанием, эта разница существенна. #### Где ECC используется сегодня - **TLS 1.3.** Последняя версия TLS использует исключительно обмен ключами на основе ECC (ECDHE). Обмен ключами RSA полностью удалён. Каждое современное HTTPS-соединение использует эллиптические кривые. - **Signal и WhatsApp.** Протокол Signal использует Curve25519 — конкретную эллиптическую кривую, разработанную для высокой производительности и устойчивости к ошибкам реализации — для обмена ключами. Тот же протокол защищает сообщения более двух миллиардов пользователей WhatsApp. - **WireGuard VPN.** WireGuard использует Curve25519 для всех обменов ключами, что способствует его репутации более простого и быстрого VPN по сравнению с OpenVPN или IPsec. - **SSH-ключи.** Ed25519 — схема подписи на эллиптических кривых — стала рекомендуемым типом ключей для SSH. Она создаёт более короткие ключи и более быстрые подписи, чем RSA: `ssh-keygen -t ed25519` генерирует ключ, который одновременно более безопасен и удобнее, чем прежний вариант по умолчанию — RSA. - **Криптовалюты.** Bitcoin и Ethereum используют эллиптическую кривую secp256k1 для подписи каждой транзакции. Когда вы отправляете криптовалюту, подпись ECC доказывает, что вы владеете кошельком, не раскрывая ваш закрытый ключ. - **Вход через Apple и Google.** Sign in with Apple использует ECC (P-256) для токенов идентификации. Google Cloud KMS поддерживает ключи ECC для подписи и верификации. #### Попробуйте сами: ключи ECC в терминале Сгенерируйте пару ключей на эллиптических кривых и сравните с RSA: ```bash # Сгенерировать закрытый ключ ECC (кривая P-256) openssl ecparam -genkey -name prime256v1 -noout -out ec-private.pem # Извлечь открытый ключ openssl ec -in ec-private.pem -pubout -out ec-public.pem # Сравнить размеры файлов — ключи ECC значительно меньше wc -c private.pem ec-private.pem ``` Вы увидите, что закрытый ключ ECC примерно в 4–5 раз меньше, чем у RSA, при эквивалентном уровне безопасности. На сервере, обрабатывающем тысячи TLS-рукопожатий в секунду, эта разница быстро накапливается. ### Цифровые подписи и контрольные суммы: доказательство подлинности и целостности Шифрование сохраняет данные в секрете, но остаются два не менее важных вопроса: **кто отправил эти данные?** и **были ли они изменены при передаче?** Цифровые подписи и контрольные суммы отвечают на эти вопросы — и они столь же фундаментальны для безопасности приложений, как и само шифрование. #### Контрольные суммы и CRC: обнаружение случайных повреждений Контрольная сумма — это короткое значение, вычисляемое из блока данных. Если хотя бы один бит данных изменится, контрольная сумма тоже изменится. Простейшая форма — **CRC (циклический избыточный код)** — быстрый алгоритм, предназначенный для обнаружения случайных ошибок при передаче или хранении данных. Вы используете CRC чаще, чем вам кажется: - **Загрузка файлов.** Когда вы скачиваете образ Linux или программный пакет, на сайте часто указывается контрольная сумма SHA-256 или MD5. Вы вычисляете контрольную сумму загруженного файла и сравниваете — если они совпадают, файл скачался без повреждений. - **Сетевые протоколы.** Ethernet-кадры, TCP-пакеты и ZIP-файлы содержат контрольные суммы CRC для обнаружения повреждений при передаче. - **Репликация баз данных.** Такие системы, как PostgreSQL, используют контрольные суммы для обнаружения повреждений данных на диске до того, как они распространятся на реплики. - **Git.** Каждый коммит, файл и дерево в Git-репозитории идентифицируется хешем SHA-1. Именно поэтому Git может обнаружить любую модификацию истории репозитория. Попробуйте в терминале: ```bash # Создать файл и вычислить его контрольную сумму SHA-256 echo "I want to secure my app with checksums" > document.txt shasum -a 256 document.txt # Измените хотя бы один символ, и контрольная сумма полностью изменится echo "I want to secure my app with Checksums" > document.txt shasum -a 256 document.txt ``` Две контрольные суммы будут совершенно разными — не слегка отличающимися, а совершенно непохожими друг на друга. Это свойство называется **лавинным эффектом**, и именно оно делает контрольные суммы надёжным инструментом обнаружения изменений. **Важное различие:** CRC и простые контрольные суммы обнаруживают *случайные* повреждения — они не защищают от *намеренной* подмены. Злоумышленник, изменивший файл, может просто пересчитать контрольную сумму. Для защиты от умышленной модификации нужны криптографические подписи. #### Цифровые подписи: доказательство авторства данных Цифровая подпись использует асимметричную криптографию в обратном порядке: вместо шифрования открытым ключом и расшифровки закрытым отправитель **подписывает** сообщение своим закрытым ключом, и любой, имеющий открытый ключ, может **проверить** подпись. Это даёт две гарантии: - **Аутентификация** — сообщение было создано владельцем закрытого ключа и никем другим. - **Целостность** — сообщение не было изменено после подписания. Цифровые подписи повсюду в продакшен-системах: - **Обновления ПО.** Когда ваш телефон устанавливает обновление iOS или Android, ОС проверяет цифровую подпись Apple или Google перед применением. Модифицированное обновление без валидной подписи отклоняется — это основная защита от вредоносных прошивок. - **Аутентификация API.** JWT (JSON Web Token), используемые для аутентификации в API, снабжены цифровой подписью. Когда ваш бэкенд получает JWT, он проверяет подпись, чтобы подтвердить, что токен был выдан вашим сервером аутентификации и не был изменён. - **Пакетные менеджеры.** npm, PyPI, Docker Hub и APT используют подписи для проверки того, что пакеты были опубликованы заявленными авторами. Атаки на цепочку поставок — вроде печально известного инцидента с event-stream — используют пробелы в этой проверке. - **Транзакции блокчейна.** Каждая транзакция Bitcoin или Ethereum подписана закрытым ключом отправителя. Сеть проверяет подпись перед принятием транзакции — именно так криптовалюта работает без центрального органа. - **Подписание документов.** PDF-документы, контракты и счета могут содержать цифровые подписи, которые подтверждают авторство и позволяют обнаружить изменения — в большинстве юрисдикций такие подписи имеют юридическую силу согласно eIDAS (ЕС) и ESIGN (США). Попробуйте подписать и проверить сообщение самостоятельно: ```bash # Создать документ echo "I want to secure my app with digital signatures" > message.txt # Подписать его вашим закрытым ключом RSA (из предыдущего примера) openssl dgst -sha256 -sign private.pem -out message.sig message.txt # Проверить подпись с помощью открытого ключа openssl dgst -sha256 -verify public.pem -signature message.sig message.txt # Вывод: Verified OK # Теперь изменим файл и проверим снова echo "I want to secure my app with Digital Signatures" > message.txt openssl dgst -sha256 -verify public.pem -signature message.sig message.txt # Вывод: Verification Failure ``` Изменённое сообщение не проходит проверку — подпись доказывает и кто создал документ, и что он не был изменён. Этот же механизм защищает каждый HTTPS-сертификат, каждый подписанный Git-коммит и каждое приложение, которое вы устанавливаете из магазина приложений. #### HMAC: подписи для симметричных систем Когда у обеих сторон есть общий секретный ключ (как во многих API-интеграциях), **HMAC (код аутентификации сообщений на основе хеша)** обеспечивает целостность и аутентификацию без накладных расходов криптографии с открытым ключом. Stripe, AWS и GitHub — все используют HMAC для подписи вебхуков, чтобы ваш сервер мог подтвердить их подлинность: ```bash # Вычислить HMAC-SHA256 сообщения echo -n "I want to secure my app with HMAC" | openssl dgst -sha256 -hmac "nerdy_pro" ``` Ваш сервер вычисляет тот же HMAC, используя свою копию общего секрета, и сравнивает его с подписью в заголовке запроса. Если они совпадают, вебхук подлинный. #### JWT: подписи на практике JSON Web Token (JWT) — одно из самых распространённых практических применений цифровых подписей. Если в вашем приложении есть аутентификация пользователей, велика вероятность, что JWT задействованы. JWT — это компактный, URL-безопасный токен из трёх частей, разделённых точками: заголовок, полезная нагрузка и подпись. ```text eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0Miwicm9sZSI6Im5lcmR5X2FkbWluIn0.oAi5ALawUM_KpWDkiJcfA8504oQ_Yjx7OhEM8nZxlIc ``` Заголовок указывает алгоритм подписи. Полезная нагрузка содержит фактические данные — ID пользователя, роль, время истечения. Подпись гарантирует, что ни то, ни другое не было изменено. Ваш сервер проверяет подпись при каждом запросе и доверяет полезной нагрузке только в случае успешной проверки. Попробуйте декодировать этот токен сами — вставьте его на [jwt.io](https://jwt.io/) и посмотрите, что внутри. Вы заметите, что можете прочитать полезную нагрузку, не зная секрета. Это сделано намеренно: JWT подписаны, а не зашифрованы. Подпись лишь доказывает, что токен не был изменён — она не скрывает содержимое. **Алгоритмы подписи** определяют модель безопасности: - **HS256 (HMAC-SHA256)** — симметричный. Один и тот же секретный ключ подписывает и проверяет токен. Прост в настройке, но каждый сервис, которому нужно проверять токены, должен иметь секрет — что становится риском по мере роста архитектуры. - **RS256 (RSA-SHA256)** — асимметричный. Сервер аутентификации подписывает закрытым ключом, а любой сервис проверяет открытым ключом. Открытый ключ можно свободно распространять без ущерба для безопасности. - **ES256 (ECDSA-P256)** — асимметричный, на основе эллиптических кривых. Та же модель доверия, что и RS256, но с меньшими ключами и более быстрыми подписями. Всё чаще рекомендуется для новых приложений. **Преимущества JWT:** - **Аутентификация без состояния.** Серверу не нужно обращаться к базе данных или хранилищу сессий при каждом запросе — токен сам содержит всю необходимую информацию для авторизации пользователя. Это упрощает горизонтальное масштабирование на несколько серверов. - **Межсервисное доверие.** В микросервисной архитектуре сервис аутентификации подписывает токен один раз, и каждый нижестоящий сервис может независимо проверить его с помощью открытого ключа. Общая база данных или сетевой вызов не требуются. - **Стандартность и переносимость.** JWT — это открытый стандарт (RFC 7519), поддерживаемый библиотеками на всех основных языках программирования. Токены можно передавать в HTTP-заголовках, параметрах URL или cookie. **Подводные камни:** - **Токены нельзя отозвать.** После подписания JWT действителен до истечения срока. Если аккаунт пользователя скомпрометирован, вы не можете аннулировать существующие токены без добавления серверного списка блокировки — что частично нивелирует преимущество отсутствия состояния. Короткое время жизни (15 минут) в сочетании с refresh-токенами смягчает эту проблему. - **Полезная нагрузка не зашифрована.** Полезная нагрузка в кодировке Base64 подписана, но не зашифрована — любой, кто перехватит токен, может прочитать его содержимое. Никогда не помещайте конфиденциальные данные (пароли, персональную информацию, API-ключи) в полезную нагрузку JWT. Если вам нужен зашифрованный токен, используйте JWE (JSON Web Encryption), хотя это добавляет сложности. - **Атаки подмены алгоритма.** Если сервер принимает несколько алгоритмов и не строго валидирует заголовок `alg`, злоумышленник может подделать токены — например, переключившись с RS256 на HS256 и подписав открытым ключом как HMAC-секретом. Всегда принудительно проверяйте ожидаемый алгоритм на стороне сервера. - **Раздутые токены.** Разработчики иногда помещают слишком много данных в полезную нагрузку — целые профили пользователей, списки прав, состояние сессии. JWT отправляются с каждым запросом, обычно в HTTP-заголовке. Большие токены увеличивают объём трафика, могут превысить ограничения на размер заголовков и замедлить каждый API-вызов. Держите полезную нагрузку минимальной: ID пользователя, роль, время истечения. Для большинства приложений JWT с подписью ES256 и коротким временем жизни обеспечивают хороший баланс безопасности, производительности и простоты. Если вам нужен отзыв токенов, сочетайте их с лёгкой серверной проверкой по списку отзыва или используйте непрозрачные refresh-токены, которые можно отозвать независимо. ### Квантовая угроза: реальный риск, требующий подготовки уже сейчас Квантовые компьютеры используют принципиально иную физику — кубиты, способные находиться в нескольких состояниях одновременно — для решения определённых задач экспоненциально быстрее любого классического компьютера. Для шифрования это имеет конкретные и хорошо изученные последствия. #### Что квантовые компьютеры сломают В 1994 году Питер Шор опубликовал [квантовый алгоритм](https://ieeexplore.ieee.org/document/365700), доказавший, что достаточно мощный квантовый компьютер сможет взломать RSA, ECC и алгоритм Диффи — Хеллмана — по сути, каждый асимметричный алгоритм, используемый сегодня. Это не теоретические предположения: это доказанный математический результат. Открытым остаётся лишь вопрос, когда квантовое оборудование станет достаточно мощным для выполнения алгоритма в масштабе. Симметричных шифров вроде AES это касается в меньшей степени. [Алгоритм Гровера](https://arxiv.org/abs/quant-ph/9605043) фактически вдвое снижает безопасность симметричных ключей — делая AES-128 недостаточным, но оставляя AES-256 с комфортным 128-битным запасом безопасности. #### «Собери сейчас, расшифруй потом» Именно эта угроза делает квантовые вычисления актуальной проблемой сегодня, а не в далёком будущем. Спецслужбы и продвинутые злоумышленники уже перехватывают и сохраняют зашифрованный трафик, планируя расшифровать его, когда квантовое оборудование станет достаточно зрелым. Эта стратегия хорошо задокументирована в [руководстве АНБ по постквантовой кибербезопасности](https://www.nsa.gov/Cybersecurity/Post-Quantum-Cybersecurity-Resources/). Для любого приложения, обрабатывающего данные с длительным сроком конфиденциальности — медицинские записи, финансовые данные, юридическая переписка, интеллектуальная собственность — это реальный риск уже сейчас, а не гипотетический. #### Постквантовая криптография: ответ индустрии В 2024 году [NIST утвердил первые постквантовые криптографические стандарты](https://www.nist.gov/news-events/news/2024/08/nist-releases-first-3-finalized-post-quantum-encryption-standards): - **ML-KEM (CRYSTALS-Kyber)** — механизм обмена ключами, заменяющий ECDH. Уже развёрнут в продакшене: Chrome и Cloudflare используют гибридный обмен ключами X25519 + ML-KEM по умолчанию. - **ML-DSA (CRYSTALS-Dilithium)** — схема цифровой подписи, заменяющая подписи RSA и ECDSA. - **SLH-DSA (SPHINCS+)** — резервная схема подписи на основе хеш-функций, обеспечивающая алгоритмическое разнообразие на случай, если схемы на решётках окажутся уязвимыми. #### Что это значит для ваших приложений Если вы создаёте или поддерживаете приложения сегодня, вот что имеет значение: 1. **Переходите на AES-256.** Это самый простой шаг, обеспечивающий квантово-устойчивое симметричное шифрование практически без потери производительности. 2. **Включите гибридный обмен ключами.** Современные библиотеки TLS (OpenSSL 3.x, BoringSSL) уже поддерживают X25519 + ML-KEM. Его включение защищает соединения как от классических, так и от квантовых атак. 3. **Проведите аудит криптографических зависимостей.** Знайте, где в вашем стеке используются RSA и ECC — это компоненты, которые в конечном итоге потребуют миграции. Если ваше приложение собрано быстро или с помощью AI-инструментов, [аудит AI-кода](https://ru.nerdy.pro/services/ai-code-audit) — самый быстрый способ найти, где спрятаны секреты и слабая криптография. 4. **Планируйте с учётом бо́льших ключей и подписей.** Постквантовые алгоритмы создают более крупные ключи, чем ECC. Проверьте, что ваши протоколы, сертификаты и хранилища могут их вместить. ### Сквозное шифрование: золотой стандарт приватных сообщений Сквозное шифрование (E2EE) означает, что сообщения шифруются на устройстве отправителя и могут быть расшифрованы только на устройстве получателя. Поставщик сервиса — будь то мессенджер, почтовая платформа или облачное хранилище — не может прочитать содержимое, даже по решению суда или в случае взлома. #### Как работает современное E2EE на практике Протокол Signal, используемый в Signal и WhatsApp, сочетает несколько методов шифрования в многоуровневую систему: 1. **Обмен ключами на эллиптических кривых (Curve25519)** — два устройства устанавливают общий секрет, не передавая его по сети. 2. **Алгоритм Double Ratchet** — генерирует новый ключ шифрования для каждого отдельного сообщения. Если злоумышленник каким-то образом получит один ключ, он не сможет расшифровать прошлые или будущие сообщения. Это свойство называется прямой секретностью (forward secrecy). 3. **AES-256 или ChaCha20** — шифрует фактическое содержимое сообщения на высокой скорости. 4. **Аутентификация HMAC** — гарантирует, что сообщения не могут быть изменены при передаче. В результате каждое сообщение имеет уникальный ключ, ключи никогда не используются повторно, а компрометация любого отдельного ключа локализована. Именно поэтому исследователи безопасности неизменно [рекомендуют протокол Signal](https://eprint.iacr.org/2016/1013.pdf) как эталон безопасности обмена сообщениями. #### Проблема групповых чатов E2EE в диалогах один на один — решённая задача. Групповые чаты значительно сложнее. Когда в группе 50, 200 или 1000 участников, протокол должен справляться с рядом дополнительных сложностей: - **Распределение ключей в масштабе.** Каждое сообщение должно быть зашифровано так, чтобы все текущие участники группы — и только текущие участники — могли его прочитать. Протокол Signal использует механизм Sender Keys: каждый участник делится симметричным ключом с группой, и сообщения шифруются один раз, а не по разу для каждого получателя. Это обеспечивает производительность групповых чатов даже при большом количестве участников. - **Изменение состава.** Когда кто-то присоединяется к группе или покидает её, ключи шифрования должны быть ротированы, чтобы новый участник не мог прочитать прошлые сообщения, а ушедший — будущие. WhatsApp и Signal делают это автоматически, но это добавляет задержку и накладные расходы — особенно в больших группах с частыми изменениями состава. - **Поддержка нескольких устройств.** Если у пользователя есть телефон, планшет и десктопный клиент, каждое устройство имеет собственные ключи шифрования. Протокол должен обеспечить расшифровку групповых сообщений на всех устройствах, сохраняя при этом прямую секретность. Протокол iMessage от Apple и архитектура Signal с поддержкой нескольких устройств подходят к этому компромиссу по-разному. Для компаний, создающих функции группового взаимодействия — командные чаты, каналы проектов, общие рабочие пространства — это не теоретические проблемы. Архитектурные решения, принятые на ранних этапах разработки, определяют, будет ли E2EE осуществимо в том масштабе, который в конечном итоге потребуется продукту. #### Отказ Instagram от E2EE: поучительная история В декабре 2023 года Meta* развернула сквозное шифрование по умолчанию в Messenger и начала распространять его на личные сообщения Instagram. Объём инженерной работы был огромным — команда Meta описала [многолетнюю перестройку инфраструктуры](https://engineering.fb.com/2024/02/22/security/end-to-end-encryption-messenger-update/), которая потребовалась, чтобы заново реализовать такие функции, как поиск, превью ссылок и обнаружение спама, без серверного доступа к содержимому сообщений. Затем, в марте 2026 года, Meta сделала шаг назад. Компания объявила, что **Instagram удалит E2EE из личных сообщений 8 мая 2026 года**. В отличие от WhatsApp, где E2EE включено по умолчанию для всех пользователей, зашифрованные сообщения Instagram были доступны только как подключаемая вручную опция и лишь в отдельных регионах — и Meta объяснила отключение низкой популярностью функции. Этот разворот значим по нескольким причинам: **Приватность как функция, а не гарантия.** E2EE в Instagram никогда по-настоящему не было «по умолчанию» — пользователям приходилось включать его для каждого чата, и оно было доступно только в определённых странах. Это разительно контрастирует с WhatsApp, где каждое сообщение зашифровано сквозным шифрованием с 2016 года без каких-либо действий пользователя. Урок: если шифрование не включено по умолчанию, уровень его использования останется низким, и этот низкий уровень станет оправданием для удаления. **Регуляторы берут своё.** Правительства США, Великобритании, ЕС и Австралии активно выступают против E2EE в мессенджерах, утверждая, что оно препятствует расследованиям по делам о сексуальной эксплуатации детей и терроризме. Online Safety Act Великобритании включает положения, которые могут потребовать от платформ сломать E2EE. Предложенный ЕС регламент «Chat Control» обязал бы сканировать сообщения на стороне клиента ещё до шифрования. Удаление E2EE из Instagram DM позволяет Meta сканировать сообщения на предмет материалов сексуального насилия над детьми (CSAM) и другого вредоносного контента — в соответствии с ожиданиями регуляторов. **Пользователи теряют контроль.** Meta предложила затронутым пользователям скачать свои зашифрованные сообщения и медиафайлы до 8 мая, поскольку зашифрованная история чатов не будет перенесена в стандартные незашифрованные чаты. Пользователям, желающим E2EE, предлагается перейти в WhatsApp. **Ландшафт E2EE сегодня.** Вот как выглядят крупные платформы после разворота Instagram: | Платформа | E2EE по умолчанию | Протокол | | --- | --- | --- | | Signal | Да (всегда) | Signal Protocol | | WhatsApp | Да (с 2016) | Signal Protocol | | iMessage | Да | Собственный протокол Apple | | Instagram DM | **Удалено** (май 2026) | Н/Д | | Telegram | **Нет** (только «Секретные чаты» по выбору) | MTProto | | Discord | Нет | Н/Д (шифрование только при передаче) | Разрыв между платформами, для которых шифрование — это основное архитектурное обязательство (Signal, WhatsApp, iMessage), и теми, где оно — опциональная функция (Instagram, Telegram, Discord), никогда не был столь очевиден. Для компаний, создающих функции обмена сообщениями, это ключевое проектное решение: E2EE должно быть фундаментальным, а не прикрученным — потому что опциональное шифрование можно отобрать. #### Чего не защищает E2EE Для любого приложения, реализующего или использующего E2EE, важно понимать его ограничения: - **Метаданные.** E2EE защищает содержимое, но не метаданные. Платформа по-прежнему знает, кто писал кому, когда, как часто и с какого IP-адреса. [Исследования EFF](https://www.eff.org/deeplinks/2013/06/why-metadata-matters) показали, что одних метаданных достаточно для выявления конфиденциальной информации о связях и моделях поведения. - **Компрометация конечного устройства.** Если само устройство скомпрометировано — вредоносным ПО, шпионскими программами вроде [Pegasus](https://citizenlab.ca/2021/07/hooking-candiru-another-mercenary-spyware-vendor-comes-into-focus/) или физическим доступом — злоумышленник читает сообщения после расшифровки. E2EE защищает данные при передаче и на сервере, но не на скомпрометированном устройстве. - **Облачные резервные копии.** Если резервные копии чатов хранятся без шифрования в iCloud или Google Drive, E2EE фактически обходится. WhatsApp предлагает зашифрованные резервные копии, но пользователи должны включить их вручную. - **Человеческий фактор.** Никакой криптографический протокол не помешает получателю сделать снимок экрана и не защитит от того, что его обманом заставят переслать переписку. ### Как всё это работает вместе в реальном приложении Когда мы создаём безопасные приложения для наших клиентов, шифрование — это не отдельная функция, а многоуровневая архитектура, где каждый тип шифрования выполняет свою роль: 1. **Криптография на эллиптических кривых** устанавливает доверие между сторонами и безопасно обменивается ключами — с меньшими и более быстрыми ключами, чем устаревшие подходы на основе RSA. 2. **Симметричное шифрование (AES-256)** выполняет высокоскоростное шифрование фактических данных — файлов, сообщений, записей баз данных, данных API. 3. **Цифровые подписи и контрольные суммы** доказывают подлинность и обнаруживают подмену — от подписанных API-токенов и обновлений ПО до верифицированных вебхуков. 4. **Сквозное шифрование** гарантирует, что даже оператор сервиса не может получить доступ к пользовательскому контенту — это критически важно для здравоохранения, юридических технологий, финансов и любого приложения, где конфиденциальность пользователей — это регуляторное или конкурентное требование. 5. **Готовность к постквантовой эре** защищает долгоживущие данные от будущих угроз — вопрос, которым дальновидные компании занимаются уже сейчас. Правильная стратегия шифрования зависит от того, что вы создаёте: мобильное приложение, обрабатывающее персональные данные, B2B-платформу для финансовых записей, функцию обмена сообщениями или IoT-систему. У каждого свои модели угроз и регуляторные требования, и архитектура шифрования должна им соответствовать. Выстроить эту архитектуру правильно — и найти места, где она незаметно ломается: секреты в системе контроля версий, персональные данные открытым текстом в логах — именно это мы проверяем в [аудите AI-кода](https://ru.nerdy.pro/services/ai-code-audit). Если вы запускаете приложение, работающее с чувствительными данными, и хотите свежий взгляд на его безопасность, [свяжитесь с нами](https://ru.nerdy.pro/contact). ### Источники - Shor, P. (1994). [Algorithms for quantum computation: discrete logarithms and factoring](https://ieeexplore.ieee.org/document/365700). Proceedings 35th Annual Symposium on Foundations of Computer Science. - Grover, L. (1996). [A fast quantum mechanical algorithm for database search](https://arxiv.org/abs/quant-ph/9605043). arXiv. - Cohn-Gordon, K. et al. (2016). [A formal security analysis of the Signal messaging protocol](https://eprint.iacr.org/2016/1013.pdf). Cryptology ePrint Archive. - NIST (2024). [Post-Quantum Encryption Standards](https://www.nist.gov/news-events/news/2024/08/nist-releases-first-3-finalized-post-quantum-encryption-standards). - NSA (2022). [Post-Quantum Cybersecurity Resources](https://www.nsa.gov/Cybersecurity/Post-Quantum-Cybersecurity-Resources/). - Meta Engineering (2024). [End-to-end encryption on Messenger](https://engineering.fb.com/2024/02/22/security/end-to-end-encryption-messenger-update/). - EFF (2013). [Why metadata matters](https://www.eff.org/deeplinks/2013/06/why-metadata-matters). - Citizen Lab (2021). [Hooking Candiru: another mercenary spyware vendor comes into focus](https://citizenlab.ca/2021/07/hooking-candiru-another-mercenary-spyware-vendor-comes-into-focus/). --- *Meta Platforms Inc. признана экстремистской организацией, её деятельность запрещена на территории Российской Федерации. Instagram и Facebook являются продуктами Meta Platforms Inc. ## Почему 0.1 + 0.2 ≠ 0.3 и при чём тут ваши деньги https://ru.nerdy.pro/blog/floating-point-and-money 14 марта — неофициальный праздник в мире программирования: день числа Пи (3.14). Это отличный повод поговорить о том, как компьютеры работают с дробными числами, почему результаты арифметических операций иногда выглядят неожиданно, и к каким последствиям это может привести в реальных проектах — особенно там, где речь идёт о деньгах. ### Эксперимент, который удивляет каждого новичка Откройте интерактивную консоль любого языка программирования и выполните простейшее выражение: ```text 0.1 + 0.2 ``` Dart, JavaScript, Python, Go, C++, Ruby — результат будет одинаковым: `0.30000000000000004`. Не `0.3`, как подсказывает здравый смысл, а число с длинным хвостом из нулей и четвёркой на конце. Это не баг конкретного языка и не ошибка в вашем коде. Это фундаментальное свойство того, как современные процессоры представляют дробные числа в памяти. ### Как компьютер хранит дробные числа Вся информация в компьютере хранится в двоичном виде — последовательностях нулей и единиц. С целыми числами это работает безупречно: `5` в двоичной системе записывается как `101`, `10` — как `1010`. Каждое целое число имеет точное двоичное представление. С дробными числами ситуация принципиально иная. Чтобы перевести десятичную дробь в двоичную, число последовательно умножают на 2 и каждый раз выделяют целую часть. Посмотрим, что произойдёт с числом 0.1: ```text 0.1 × 2 = 0.2 → 0 0.2 × 2 = 0.4 → 0 ← начало цикла 0.4 × 2 = 0.8 → 0 0.8 × 2 = 1.6 → 1 0.6 × 2 = 1.2 → 1 ← конец цикла 0.2 × 2 = 0.4 → 0 ← цикл повторяется ... ``` В результате получается `0.0(0011)` — бесконечная периодическая двоичная дробь. Ситуация аналогична тому, как в десятичной системе дробь `1/3` превращается в бесконечное `0.3333...` — записать её точно в конечном числе знаков невозможно. Число 0.2 сталкивается с той же проблемой. Оба числа, с которыми мы выполняем сложение, уже на этапе записи в память содержат погрешность. ### Стандарт IEEE 754: компромисс между точностью и производительностью Подавляющее большинство современных процессоров используют стандарт IEEE 754 для работы с числами с плавающей точкой. Тип `double` (64-битное число двойной точности), который используется по умолчанию в большинстве языков программирования, устроен следующим образом: - **1 бит** отводится под знак числа (положительное или отрицательное) - **11 бит** — под экспоненту (порядок числа) - **52 бита** — под мантиссу (значащие цифры) 52 бита мантиссы — это примерно 15–17 значащих десятичных цифр. Когда двоичное представление дроби оказывается бесконечным, оно обрезается на 52-м бите. Именно в этот момент возникает погрешность представления. При сложении двух чисел, каждое из которых уже содержит погрешность, ошибки накапливаются. Так `0.1 + 0.2` превращается в `0.30000000000000004`. ### Когда погрешность допустима Справедливости ради, для большинства задач эта погрешность совершенно незначительна. Речь идёт об ошибке порядка 10⁻¹⁶ — это одна квадриллионная. В следующих областях `double` работает прекрасно: - **Компьютерная графика и рендеринг** — разница в шестнадцатом знаке после запятой невидима для человеческого глаза - **Физические симуляции в играх** — объекты ведут себя реалистично, погрешность не влияет на восприятие - **Научные вычисления** — в большинстве случаев результат округляется до нужной точности - **Статистика и машинное обучение** — модели по своей природе оперируют приближениями ### Когда погрешность становится критической: финансовые вычисления Ситуация кардинально меняется, когда речь заходит о деньгах. В финансовых системах каждая копейка должна быть учтена точно. Ошибка в шестнадцатом знаке после запятой может показаться несущественной для одной транзакции, но при обработке тысяч и миллионов операций эти микроскопические погрешности накапливаются и превращаются в реальные расхождения в балансах. Рассмотрим конкретный пример. Допустим, система обрабатывает 100 000 транзакций в день, и в каждой из них возникает погрешность в `0.000000000000004`. За день это `0.0000000004` — ничтожно мало. Но умножьте на год, добавьте более сложные операции с умножением и делением, где погрешности растут значительно быстрее, — и вы получите суммы, которые невозможно объяснить ни бухгалтеру, ни аудитору. ### Надёжные подходы к работе с денежными суммами #### Подход первый: минорные единицы как целое число Суть подхода проста: вместо того чтобы хранить сумму в рублях или долларах как дробное число, её хранят в наименьших единицах валюты (копейках, центах, пенни) как целое число. `19999` копеек — это всегда ровно 199 рублей и 99 копеек. Никаких округлений, никаких сюрпризов, никаких `0.000000004` в хвосте. Целочисленная арифметика в компьютерах абсолютно точна. Этот подход широко используется в платёжных системах. Stripe, например, оперирует суммами исключительно в минимальных единицах валюты. Отдельно стоит упомянуть альтернативный вариант для API: передача денежных сумм в виде строк. В этом случае интерфейс остаётся человекочитаемым (`"199.99"` понятнее, чем `19999`), а ответственность за парсинг и выбор подходящего числового типа ложится на клиентскую сторону. #### Подход второй: библиотеки произвольной точности (BigDecimal) Второй надёжный вариант — использование специализированных типов данных, которые хранят десятичные числа без преобразования в двоичную систему: - **Dart** — пакет `decimal` - **Java** — `java.math.BigDecimal` - **Python** — модуль `decimal` из стандартной библиотеки - **JavaScript** — предложение [Decimal](https://github.com/tc39/proposal-decimal) на стадии рассмотрения, а пока — библиотеки вроде `decimal.js` - **Go** — пакет `shopspring/decimal` Эти типы представляют числа в десятичном виде, полностью избегая двоичных приближений. Выражение `0.1 + 0.2 == 0.3` при использовании `BigDecimal` возвращает `true` — гарантированно. Платить за точность приходится производительностью: операции с `BigDecimal` значительно медленнее, чем с `double`. Однако в контексте финансовых вычислений, где корректность результата важнее скорости, это более чем приемлемый компромисс. ### Какой подход выбрать Оба подхода решают проблему, но подходят для разных ситуаций: | Критерий | Минорные единицы (`int`) | BigDecimal | | --- | --- | --- | | Производительность | Максимальная | Ниже | | Простота реализации | Высокая | Средняя (зависит от языка) | | Поддержка дробных единиц | Нет (только целые) | Да | | Мультивалютность | Требует знания количества знаков | Работает из коробки | Для большинства e-commerce и платёжных систем подход с минорными единицами оптимален. `BigDecimal` предпочтительнее в сценариях, где необходимы промежуточные дробные вычисления — например, при расчёте процентов, налогов или конвертации валют. ### Заключение Если в вашем проекте есть денежные суммы, следует придерживаться одного из двух правил: - Хранить и передавать суммы в минорных единицах как целое число (`int`) - Использовать типы с произвольной точностью (`BigDecimal` и его аналоги) Использование `double` для финансовых вычислений — это технический долг, который может не проявляться месяцами, но в определённый момент приведёт к расхождениям, причину которых будет крайне сложно диагностировать. Выбирайте инструменты, соответствующие задаче, и будьте внимательны к тому, как ваш язык программирования представляет числа в памяти. Это одна из тех вещей, о которых полезно знать до того, как она станет проблемой на продакшене. Мы сталкиваемся ровно с этими компромиссами, когда строим приложения, работающие с деньгами, — наш финтех-проект [ExtraETF](https://ru.nerdy.pro/portfolio/extraetf) и вся наша практика [разработки приложений на Flutter](https://ru.nerdy.pro/services/flutter-app-development) опираются на эти правила. С днём числа Пи! 🥧 ## HTTPS — это не сложно: получаем бесплатный сертификат https://ru.nerdy.pro/blog/https-free-ssl-certificate Зачем нужен HTTPS? Что такое центр сертификации? Какие бывают сертификаты и как их получить? Настраиваем nginx с Let's Encrypt. TLS — это лишь базовый уровень. Полную проверку безопасности приложения мы делаем в рамках услуги [аудита AI-кода](https://ru.nerdy.pro/services/ai-code-audit). ## Базы данных и индексы: объясняю на пальцах https://ru.nerdy.pro/blog/databases-and-indexes Рассказываю, как устроены базы данных, зачем нужны индексы и как вообще всё это работает. Производительность запросов и баз данных — одна из областей, которые мы разбираем в рамках [аудита AI-кода](https://ru.nerdy.pro/services/ai-code-audit). ## Glossary - [Встроенные покупки](https://ru.nerdy.pro/glossary#in-app-purchase): Продажа цифровых товаров и подписок через биллинг Apple и Google, который оба стора требуют для цифрового контента и с которого берут комиссию. Сложность никогда не в самой покупке, а в её восстановлении на новом устройстве и в честном учёте прав доступа. - [Диплинкинг](https://ru.nerdy.pro/glossary/deep-linking): Ссылка, которая открывает конкретный экран внутри установленного приложения, а не его главную или веб-страницу. Отложенный диплинкинг переживает установку: нажатие, прошедшее через магазин приложений, всё равно приводит на нужный экран. - [Мультиархитектурный образ](https://ru.nerdy.pro/glossary#multi-arch-image): Один тег образа, который разрешается в разные бинарники для каждой архитектуры процессора — обычно это пара linux/amd64 и linux/arm64, — так что один и тот же docker pull работает без изменений и на серверах Intel/AMD, и на ноутбуках с Apple Silicon. - [Мультитенантность](https://ru.nerdy.pro/glossary#multi-tenancy): Один развёрнутый бэкенд обслуживает много клиентов, и каждый видит только свои данные, конфигурацию и включённые функции, потому что контекст тенанта определяется для каждого запроса. Именно это делает линейку брендированных приложений одним продуктом, а не набором форков. - [Объектное хранилище](https://ru.nerdy.pro/glossary#object-storage): Хранилище, которое держит файл целиком под ключом, а не в дереве файловой системы, — Amazon S3 и множество сервисов, совместимых с его API. Дешёвое, практически безграничное, привычное место для сырых событий, бэкапов и медиа: пишется один раз, читается редко, хранится вечно. - [Ограничение частоты запросов](https://ru.nerdy.pro/glossary#rate-limiting): Предел на число запросов от одного клиента за заданный интервал. Именно он не даёт одному увлечённому пользователю, скраперу или боту потратить месячный бюджет платного API за вечер, и стоять он должен на вашей стороне интеграции. - [Платформенные каналы](https://ru.nerdy.pro/glossary#platform-channels): Мост, через который Flutter-приложение вызывает нативный код iOS и Android — Keychain и Keystore, биометрию, платёжные шторки, любой SDK без Dart-пакета. Работа рутинная, но настоящая, и первое место, где споткнётся инженер, не выходивший за пределы Dart. - [Постквантовая криптография](https://ru.nerdy.pro/glossary#post-quantum-cryptography): Алгоритмы шифрования, рассчитанные на устойчивость к будущему квантовому компьютеру; стандарты уже финализированы NIST. Мигрировать нужно до появления железа: трафик, записанный сегодня, расшифруют, когда такая машина появится. - [Поэтапная раскатка](https://ru.nerdy.pro/glossary#staged-rollout): Релиз сначала на небольшой процент пользователей и расширение только тогда, когда доля сессий без падений держится. Плохая сборка, пойманная на десяти процентах, — испорченный вечер; она же на ста процентах — испорченная неделя. - [Реестр контейнеров](https://ru.nerdy.pro/glossary#container-registry): Хранилище образов контейнеров, адресуемых по имени и тегу, — Docker Hub, GitHub Container Registry или собственный реестр облачного провайдера. Публикация сборки в реестр превращает «у меня работает» в образ, который любой может скачать и запустить без изменений. - [Серверный рендеринг](https://ru.nerdy.pro/glossary#server-side-rendering): Сборка страницы в готовый HTML на сервере, чтобы первый же ответ содержал контент, заголовки, метатеги и структурированные данные. Краулеры, превью ссылок и медленные устройства читают это, не выполняя JavaScript. - [Сквозное шифрование](https://ru.nerdy.pro/glossary#end-to-end-encryption): Шифрование, которое накладывается на устройстве отправителя и снимается только на устройстве получателя, так что сервис, передающий сообщение, не может его прочитать — ни по решению суда, ни после взлома. Оно защищает содержимое, но никогда — метаданные. - [Совокупная стоимость владения](https://ru.nerdy.pro/glossary#total-cost-of-ownership): Во что продукт обходится за всю жизнь, а не только на этапе разработки: поддержка, обновления, ежегодные миграции на новые ОС и правила сторов, вторая команда, чтобы две кодовые базы не расходились. Обычно больше сметы на разработку и почти никогда в неё не входит. - [Управление состоянием](https://ru.nerdy.pro/glossary#state-management): То, как приложение решает, где живёт значение, кому позволено его менять и какие части экрана перерисовываются, когда оно меняется. Во Flutter выбор между Riverpod, BLoC и Provider — одно из первых архитектурных решений и одно из самых трудных для пересмотра. - [Фиче-флаги](https://ru.nerdy.pro/glossary#feature-flags): Переключатели, которые включают и выключают функциональность из конфигурации, а не из релиза. Один бинарник ведёт себя по-разному для разных брендов, рынков и пользователей, а рискованную фичу можно погасить, не отправляя новую сборку на ревью. - [Числа с плавающей точкой](https://ru.nerdy.pro/glossary#floating-point): Двоичный формат IEEE 754, стоящий за double и float. Он не хранит 0.1 точно, поэтому 0.1 + 0.2 даёт 0.30000000000000004 — незаметно в графике и фатально в деньгах, которым место в целых минорных единицах или в decimal-типе. - [AOSP](https://ru.nerdy.pro/glossary#aosp): Android Open Source Project — Android без надстройки Google, который может форкнуть кто угодно. На его форках работают кассовые терминалы, киоски и автомобильные системы, а работа на этом уровне открывает части ОС, которых прикладной разработчик не видит. - [App Clips](https://ru.nerdy.pro/glossary#app-clips): Функция Apple, позволяющая запустить небольшую часть iOS-приложения — до 15 МБ — не устанавливая его целиком. Вызывается по QR-коду, NFC-метке или ссылке: для случаев, когда первое действие пользователя не должно требовать похода в App Store. - [BigQuery](https://ru.nerdy.pro/glossary#bigquery): Аналитическое хранилище Google Cloud. Вы запускаете SQL по миллиардам строк, и они сканируются за секунды, а данные при этом лежат отдельно от машин, выполняющих запросы. Именно это снимает отчётность и исследование данных с базы, обслуживающей живой трафик. - [CI/CD](https://ru.nerdy.pro/glossary#ci-cd): Автоматизация, которая собирает, тестирует и выкладывает каждое изменение без ручного запуска команд. В мобильной разработке это то, что превращает релиз в нажатие кнопки вместо полудня ожидания, пока освободится нужный человек. - [CRUD](https://ru.nerdy.pro/glossary#crud): Create, read, update, delete — четыре операции, стоящие почти за каждой формой и админкой. Собирательное название для рутинной половины приложения, которая управляет данными, в отличие от частей с реальной предметной логикой. - [Dev Container](https://ru.nerdy.pro/glossary#dev-container): Среда разработки, описанная один раз в devcontainer.json — ОС, инструменты и версии рантаймов, которые нужны проекту, — и открываемая одинаково внутри контейнера в редакторе любого участника, вместо инструкции по установке, которую каждый понимает по-своему. - [Elasticsearch](https://ru.nerdy.pro/glossary#elasticsearch): Поисковый и аналитический движок, который индексирует записи, чтобы их можно было интерактивно фильтровать и искать, а не сканировать. К нему обращаются, когда вопрос звучит как «покажи вот эти сессии с шестью фильтрами», а не «просуммируй колонку». - [Fan-out](https://ru.nerdy.pro/glossary#fan-out): Чтение источника один раз и доставка каждого обновления всем подписанным на него клиентам. Наивная версия пишет подписчикам в цикле и встаёт, как только один из сокетов начинает тормозить; рабочая — держит отдельный буфер на каждого клиента и отключает тех, кто не успевает. - [GDPR](https://ru.nerdy.pro/glossary#gdpr): Регламент ЕС о персональных данных людей в ЕС: законное основание для сбора, настоящее согласие на трекинг, право увидеть и удалить свои данные. Он следует за вашими пользователями, а не за вашими серверами: где зарегистрирована компания, значения не имеет. - [Golden-тест](https://ru.nerdy.pro/glossary#golden-test): Тест, который отрисовывает виджет и сравнивает результат попиксельно с эталонным изображением. Во Flutter это самый дешёвый способ узнать, не сломал ли редизайн пустое состояние на 320pt, в тёмной теме, при 200% масштабе текста. - [GraphQL](https://ru.nerdy.pro/glossary#graphql): Язык запросов к API, где клиент сам перечисляет нужные поля и получает один ответ ровно такой формы. Он снимает избыточную выдачу, к которой дрейфуют REST-эндпоинты, и добавляет собственный режим отказа: неограниченный запрос, обходящий всю модель данных. - [Headless CMS](https://ru.nerdy.pro/glossary#headless-cms): Система управления контентом с админкой и API, но без собственного фронтенда. Редакторы публикуют в одном месте, а сайт или приложение отрисовывает контент само — слой представления принадлежит вам, а не вендору CMS. - [HIPAA](https://ru.nerdy.pro/glossary#hipaa): Закон США о защищённой медицинской информации: как её можно хранить, передавать, логировать и раскрывать. Как и PCI-DSS, это архитектурное ограничение, которое выбирают в начале, а не документ, который дописывают перед запуском. - [Host Card Emulation](https://ru.nerdy.pro/glossary#host-card-emulation): Режим, в котором Android-телефон программно эмулирует бесконтактную карту по NFC, без аппаратного защищённого элемента. Так платит приложение-кошелёк на терминале: телефон говорит с ним по тому же протоколу EMV Contactless, что и пластиковая карта. - [Impeller](https://ru.nerdy.pro/glossary#impeller): Движок рендеринга, на котором Flutter работает сегодня: по умолчанию на iOS с 2023 года и на Android с 2024-го. Он компилирует шейдеры заранее, а не во время первой анимации, что убрало главную видимую проблему Flutter в проде — джанк на компиляции шейдеров. - [Install Referrer](https://ru.nerdy.pro/glossary#install-referrer): API Google Play, который передаёт только что установленному Android-приложению параметры кампании из ссылки, приведшей к установке. Android-половина отложенного диплинкинга и надёжный способ понять, откуда пришёл пользователь. - [JWT](https://ru.nerdy.pro/glossary#jwt): Подписанный токен, несущий свои данные внутри: сервер понимает, кому принадлежит запрос, не обращаясь к хранилищу сессий. Стандарт мобильной аутентификации. Подпись доказывает, что токен не меняли, но не скрывает его содержимое. - [Kotlin Multiplatform](https://ru.nerdy.pro/technologies/kmp): Переиспользование бизнес-логики на Kotlin между Android, iOS и сервером, при этом интерфейс на каждой платформе остаётся нативным. Альтернатива Flutter, когда UI обязан быть нативным, а правила за ним — нет. - [KYC](https://ru.nerdy.pro/glossary#kyc): Know Your Customer — проверки личности, которые регулируемый финансовый продукт проводит, прежде чем разрешить переводить деньги: съёмка документов, liveness, санкционный и антиотмывочный скрининг. На онбординг это влияет сильнее любого дизайн-решения. - [MVP](https://ru.nerdy.pro/glossary#mvp): Минимальная версия продукта, которую можно показать реальным пользователям и которая при этом отвечает на вопрос, ради которого его делали. Решение об объёме, а не о качестве: MVP всё равно обязан работать. - [OAuth](https://ru.nerdy.pro/glossary#oauth): Стандарт, на котором держатся «Вход через Apple», Google и остальные: пользователь авторизует ваше приложение у провайдера, которому уже доверяет, а приложение получает токен вместо пароля. Никто не заводит новый пароль, и вы его не храните. - [PCI-DSS](https://ru.nerdy.pro/glossary#pci-dss): Стандарт безопасности карточной индустрии, обязательный для всех, кто хранит, обрабатывает или передаёт данные карт. Большинство приложений намеренно остаются вне зоны его действия, отдавая ввод карты сертифицированному платёжному провайдеру. - [Product-market fit](https://ru.nerdy.pro/glossary#product-market-fit): Момент, когда продукт доказуемо нашёл своих людей: они им пользуются, возвращаются и платят. До него разработка отвечает на вопрос, после — на спрос. - [Prompt injection](https://ru.nerdy.pro/glossary#prompt-injection): Атака, при которой текст от пользователя языковая модель читает как инструкции, а не как данные, и уходит в обход собственных правил. Родственник SQL-инъекции в эпоху LLM: возникает везде, где пользовательский ввод склеивается в промпт. - [Pub/Sub](https://ru.nerdy.pro/glossary#pub-sub): Шаблон обмена сообщениями: производитель публикует событие, а любое число потребителей независимо читает его через стоящий между ними брокер. Производитель никогда их не ждёт — так путь запроса остаётся быстрым, а более медленная работа выполняется в фоне. - [REST](https://ru.nerdy.pro/glossary#rest): Общепринятый стиль HTTP-API: URL называет ресурс, а HTTP-метод говорит, что с ним сделать. Способ по умолчанию, которым приложение общается с бэкендом, и то, что ожидает найти большинство сторонних интеграций. - [SaaS](https://ru.nerdy.pro/glossary#saas): Софт, который продают как постоянную подписку на размещённый у поставщика продукт, а не как разовую лицензию для установки у себя. Поставщик держит серверы, непрерывно выкатывает обновления и берёт плату за пользователя или за объём. - [Server-Sent Events](https://ru.nerdy.pro/glossary#server-sent-events): Односторонний поток от сервера к клиенту поверх обычного HTTP-соединения. Проще, чем WebSocket, и достаточен там, где только серверу есть что сказать: прогресс операции, ответ AI, приходящий токен за токеном. - [Skia](https://ru.nerdy.pro/glossary#skia): Открытая 2D-графическая библиотека Google, которая рисует Chrome, Android и — до появления Impeller — каждый кадр Flutter. Она умеет выводить не только на экран, но и в PDF: именно это делает «печать в PDF» в Chrome. - [SOC 2](https://ru.nerdy.pro/glossary#soc-2): Отчёт внешнего аудитора о том, как компания обращается с данными клиентов — безопасность, доступность, конфиденциальность, — а не сертификат, который можно купить. Корпоративные заказчики его требуют, а архитектуру он ограничивает задолго до самого аудита. - [Staff augmentation](https://ru.nerdy.pro/glossary#staff-augmentation): Модель найма, при которой инженеры внешнего партнёра входят в вашу команду и работают под вашим управлением — в вашем репозитории, ваших спринтах, вашем процессе, — а не сдают отдельный проект. Вы покупаете ресурс; код и контекст остаются у вас. - [Time to market](https://ru.nerdy.pro/glossary#time-to-market): Время от решения строить продукт до момента, когда им пользуются настоящие люди. Большинство споров о стеке и объёме работ — на самом деле споры об этом числе: каждая сэкономленная неделя — это неделя выручки, обратной связи и позиции на рынке. - [TLS](https://ru.nerdy.pro/glossary#tls): Слой шифрования под HTTPS. Он подтверждает, что сервер именно тот, о ком говорит его сертификат, согласует свежий ключ на сессию и шифрует всё дальнейшее — сеть между вами видит шифртекст и не может незаметно его изменить. - [WebRTC](https://ru.nerdy.pro/glossary#webrtc): Браузерный и мобильный стандарт для передачи аудио, видео и данных напрямую между двумя устройствами; серверы участвуют только в том, чтобы их познакомить. На нём строят видеозвонок внутри приложения, когда не берут готовый SDK. - [WebSocket](https://ru.nerdy.pro/glossary#websocket): Протокол, который держит одно соединение между клиентом и сервером открытым, чтобы любая сторона могла отправить данные в любой момент, а не клиент спрашивал снова и снова. На нём работают живые котировки, чаты и статусы присутствия. - [White-label](https://ru.nerdy.pro/glossary/white-label): Один продукт, выпускаемый под многими брендами. White-label платформа собирает каждому клиенту готовое к публикации приложение со своим названием, оформлением и контентом из общей кодовой базы, а не форкает проект под заказчика. - [XMPP](https://ru.nerdy.pro/glossary#xmpp): Открытый федеративный протокол обмена сообщениями и давняя альтернатива тому, чтобы писать чат-бэкенд самому или арендовать чужой. Он покрывает статусы присутствия, индикаторы набора и передачу файлов, а зрелые клиентские библиотеки есть под любую платформу. ## Contact - Website: https://ru.nerdy.pro - Email: welcome@nerdy.pro - Phone: +78005508829 - Telegram: https://t.me/nerdypro - WhatsApp: https://wa.me/79269263946 - English site: https://nerdy.pro - Испанский сайт: https://es.nerdy.pro - Голландский сайт: https://nl.nerdy.pro