Как мы переводили страницу объявления на Beduin в мобильной версии Авито
- суббота, 22 августа 2026 г. в 00:00:06
Всем привет! Меня зовут Егор Савинцев, я Frontend Platform Engineer в Авито. Сейчас я работаю в команде архитектуры, а до этого в команде Phobos в рамках Платформизации переводил страницу объявления на Beduin — разрабатывал клиентскую архитектуру и переписывал блоки.
Сразу важное отступление.
Beduin — это Backend‑Driven UI. Если в нативе в коде клиентского приложения есть компоненты, бэкенд присылает им данные, и всё работает. В Beduin бэкенд присылает и данные, и сами компоненты в специальном виде. На клиенте у нас только рендерер — специальный компонент, который принимает JSON и рендерит то, что мы прислали.
В этой статье я расскажу, зачем вообще переписывать работающую страницу, как эволюционировала наша архитектура и с какими компромиссами между нативным кодом и BDUI нам пришлось столкнуться.
Статья будет полезна frontend‑ и mobile‑разработчикам, инженерам платформенных команд, архитекторам клиентских приложений и backend‑разработчикам, которые внедряют Backend‑Driven UI. В общем, всем, кто планирует переводить крупные экраны на BDUI и хочет заранее узнать, что может пойти не так. Спойлер: всё.
До Beduin страница объявления в Авито была обычным нативным React‑компонентом, внутри которого были другие компоненты с галереей, ценой, заголовком и информацией о продавце и остальными блоками. Параллельно ещё были такие же реализации на iOS и Android — тоже нативные.

Затем мы решили переписать всё на Beduin.
Дело в том, что когда мы выкатывали новый функционал, нам приходилось вносить правки в мобильной версии Авито (далее МАВ), iOS, Android, проходить весь цикл ревью, тестирование, разбираться с падениями и только потом запускать A/B‑тесты.
С Beduin мы можем поменять всё в одном месте и сразу раскатить на три платформы: МАВ, iOS и Android. Потом изменения поступают в продакшн, и можно запускать A/B‑тесты сразу везде.

🟢 Главное преимущество Beduin — единый UI. Beduin работает только с компонентами дизайн‑системы, а она одинаковая для MAB, iOS и Android. Поэтому если один раз написать компонент на Beduin, например кнопку, она одинаково отобразится на всех трёх платформах.
🟢 Ещё одно преимущество Beduin — не нужно релизить сам клиент. Достаточно один раз зарелизить рендерер, а потом передавать ему JSON: изменения отображаются сразу же, без обновления клиента.
🟢 Код на Beduin пишется один раз и в теории должен одинаково работать на всех трёх платформах.
🟢 Можно запускать A/B‑тесты без изменений в клиентах. Условия задаются на бэкенде, по ним меняется Beduin JSON, и A/B‑тест можно сразу запустить на всех трёх платформах.
🟢 И последнее преимущество — продуктовые вертикали Авито могут делать свои варианты JSON. Например, взять общую карточку объявления и собрать свою версию со своими доработками.
У Beduin есть и ограничения ↓
🔴 Самое большое ограничение — это порог входа. Когда разработчику показывают Beduin и говорят, что теперь нужно писать на нём, это пугает, потому что непонятно, как писать код, как его запускать и что с ним делать.
🔴 У Beduin пока нет такой поддержки нативных API, какие есть на нативных платформах. Например, пока нельзя полноценно использовать камеру, геолокацию и много чего ещё — у Beduin пока возможности ограничены.
🔴 В нативной реализации мы можем сами написать любую логику, она будет работать. В Beduin не всё то, что мы написали нативно, можно запустить. Мы ограничены и дизайн‑системой, и возможностями самого Beduin. Поэтому переход с нативной реализации на Beduin не всегда проходит легко.
🔴 У каждой платформы есть свои особенности: что‑то может работать на одной и не работать на другой. Всё это нужно учитывать, когда переходите на Beduin.

Мы переводили страницу объявления на Beduin два раза. Первый подход был через обёртки.
В библиотеке Beduin для MAB мы сделали специальный компонент, который принимает идентификатор нативного JS‑компонента и словарь. По идентификатору в словаре ищется нужный компонент и отображается на странице. Для этого на странице объявления мы изолировали все компоненты: убрали параметры и условия, чтобы каждый можно было вызывать независимо, например галерею или заголовок. Собрали для них словарь и сделали Beduin JSON, который обращается к этому словарю и отображает нужные нативные компоненты. То есть получилась обёртка, которая вызывает внутри себя нативный компонент.

Мы сделали это, чтобы продуктовые вертикали Авито могли собирать свои сценарии Beduin: менять компоненты местами, удалять ненужные и добавлять свои. Например, в одной вертикали сначала может идти заголовок, а потом цена, а в другой — наоборот. Если какой‑то вертикали не нужна галерея, её можно убрать.
Мы выбрали обёртки, а не сразу перевели всё на Beduin, чтобы сначала проверить, как Beduin справится с большой нагрузкой, которая есть на странице объявления, архитектурой React и SSR‑рендерингом. Мы не хотели сразу всё переписывать, потому что для этого нужно было бы остановить всю разработку и сидеть переписывать на Beduin. Поэтому решили сначала сделать обёртки над нативными компонентами, а потом постепенно заменять их на настоящие Beduin‑компоненты.
В итоге этого так и не случилось. (Об этом позже.)
После того как мы сделали версию с обёртками, нужно было проверить её на продакшене. Мы запускали A/B‑тесты, где сравнивали нативную версию и версию с обёртками по ряду показателей. Наша цель была убедиться, что версия с обёртками работает не хуже. Потому что если она работает хуже, например медленнее по перформансу или проседают какие‑то метрики покупателей, то надо искать причины и исправлять. Так и получилось: мы видели красные A/B‑тесты, пытались найти ошибки, например с гидрацией, фиксили их и запускали снова.
В один из запусков упал МАВ. Это было так: в четверг вышел новый релиз библиотеки Beduin для MAB, и мы запустили A/B‑тест на странице объявления как раз на МАВе. В выходные начало расти потребление памяти, и в воскресенье МАВ наконец упал.
Начали разбираться. Перевыкатили MAB, он поднялся, но мы поняли, что причина где‑то глубже. Продолжили искать и нашли A/B‑тест с новой версией страницы объявления на Beduin, который запустили в четверг. Отключили A/B‑тест, и потребление памяти вернулось в норму. Причиной оказался баг в библиотеке Beduin для MAB: ошибка выбрасывалась снова и снова, из‑за чего возникал бесконечный цикл и росло потребление памяти. До этого баг был незаметен, потому что не было такой нагрузки, как на странице объявления. Как только мы запустили A/B‑тест и часть пользователей получила Beduin‑версию страницы, нагрузка выросла, память закончилась и весь MAB упал.
Мы несколько раз перезапускали A/B‑тесты, но каждый раз были проблемы. Например, возникали ошибки гидрации: мы их исправляли, запускали тест снова, но проблемы всё равно оставались. Добавили в Grafana разбивку по категориям страницы объявления, чтобы понять, где именно возникают ошибки гидрации. Оказалось, что их особенно много в «Недвижимости» и связанных с ней категориях. Исправили эти ошибки и снова запустили A/B‑тест, но он всё равно оставался красным.

Потом мы решили сделать хитрую вещь — два рендера одновременно. Рендерили нативную версию, а под ней — невидимую Beduin‑версию. В каждой собирали названия блоков, которые появились на странице, и сравнивали их. Если находили отличие, записывали в Clickstream названия блоков, которых не было в одной из версий или которые, наоборот, были лишними.
Этот режим включали ненадолго, на 5–10 минут, только чтобы проверить соответствие и порядок блоков. Для измерения перформанса его не использовали.
Это помогло найти расхождения и исправить их. После этого A/B‑тесты стали более‑менее нормальными, и мы раскатили Beduin‑версию в категории «Товары» на 100%. Небольшая просадка по перформансу всё ещё оставалась, но её согласовали с командой перформанса. Эта версия с обёртками до сих пор работает на MAB в «Товарах»: внутри нативные компоненты, а снаружи — Beduin.
Мы бы оставили первую версию и постепенно переводили на Beduin другие категории, но планы резко изменились. «Товары» оставили на обёртках, а четыре новые категории решили переводить на новую версию Beduin с Kotlin DSL.
Kotlin DSL — это специальный язык на Kotlin для описания интерфейса. Если Beduin строит интерфейс по JSON, то в Kotlin DSL мы описываем его кодом, после чего из описания генерируется Beduin JSON.
Kotlin DSL нужен потому, что чем сложнее компонент, тем больше в нём зависимостей, функций и другой логики, которую сложно описывать напрямую в JSON. В Kotlin DSL есть версионирование, подсветка синтаксиса, переиспользование и типы (пусть они и отличаются от тех, к которым мы привыкли на вебе). На Kotlin DSL мы и делали вторую версию Beduin‑страницы объявления.

Кроме того, что мы перешли на Kotlin DSL, мы ещё поменяли ответ от бэкенда. Если раньше в ответе приходили заголовок объявления, цена и множество других полей, а в конце было поле beduin с JSON, в новой версии мы переделали ответ от бэкенда: убрали всю обвязку для нативных компонентов с заголовком, ценой и остальными данными и оставили в основном Beduin JSON. Отдельно остались поля для аналитики, SEO и UX feedback. Их сложно перенести внутрь Beduin, поэтому они по‑прежнему обрабатывались нативно.

Мы не просто взяли и начали всё переписывать на Kotlin DSL, мы провели хакатон. Две недели втроём, разработчиками веба, iOS и Android, мы переписывали один из компонентов на странице объявления — галерею и делали proof of concept, чтобы проверить, можно ли реализовать её на Beduin с Kotlin DSL.
Мы решили сразу сделать галерею более‑менее полноценной и придумали, что каждый её слайд будет Beduin‑компонентом, описанным через Beduin JSON. В этот JSON могла прийти картинка, видео или любая Beduin‑разметка. То есть в галерею можно будет добавить всё, что угодно, чтобы продуктовые вертикали могли собирать свои слайды. С fullscreen тоже пришлось сделать очень хитро, потому что в Beduin не было нативной поддержки переключения из inline‑вида в fullscreen‑вид. Поэтому пришлось делать синхронизацию и специальные интеракшены — это ещё один термин Beduin.
Сначала мы пробовали сделать всё на Beduin, но потом поняли, что его возможностей не хватает, и сделали галерею нативным компонентом. В случае MAB это JS‑компонент, который внутри себя отображает JS, а не Beduin.

Если в первом подходе у нас были обёртки, то на Kotlin DSL мы разрабатывали компоненты и переводили их на Kotlin DSL. Вертикали из этих компонентов собирали свои сценарии. Мы запускались в четырёх вертикалях, и каждая создавала свой сценарий из этих компонентов, как из кирпичиков складывая свою страницу.
Эти компоненты бывают двух типов:
метакомпоненты — они написаны на чистом Beduin и просто отображаются через рендерер;
нативные компоненты, как в случае с галереей. Нативный компонент — это JS‑компонент, который из Beduin JSON берёт только контракт и данные, а сама реализация находится в MAB и написана на обычном JS с использованием React.
Недостаток нативных компонентов в том, что их нужно отдельно реализовывать и поддерживать на всех трёх платформах: MAB, iOS и Android. Если мы делали галерею на MAB два‑три месяца, то примерно столько же времени нужно потратить на Android и iOS, чтобы реализовать и там.

Запуск второй версии тоже проходил не очень гладко. Например, снова возникли ошибки гидрации, и команда Beduin сделала отдельный рендерер для страницы объявления. Если обычный рендерер назывался Beduin Mobile, то для страницы объявления сделали отдельный Beduin Mobile Itemcard. В нём были только те компоненты, которые использовались на странице объявления.
Это было временное решение. Сейчас уже есть рабочий код, в котором из Beduin JSON достаются все используемые компоненты и в рендерере подключаются только они, а не всё подряд, как раньше.
Ещё появилась проблема с тяжёлым JS, потому что продуктовые вертикали писали свои сценарии с большим количеством компонентов. Страница получалась тяжёлая, а ещё оставалась обвязка от старой страницы объявления. На A/B‑тестах заметили, что страница рендерится, на ней появляются кнопки, но при нажатии они ещё не реагируют, потому что обработчики не успели загрузиться. Эту проблему временно решили тем, что продлили показ скелетона: пока весь JS не загружался, пользователь видел скелетон, а после загрузки кнопки становились активными. Сейчас это решение убрали вместе с лишним JS, и страница стала загружаться быстрее.

И последнее, с чем мы столкнулись, — поддержка HTML‑тегов. На вебе они необходимы, но Beduin поддерживал только div и span. Для полноценного запуска вертикалям нужны были обычные ссылки через a href, а не диплинки, метатеги для SEO, атрибуты, а также h1, p и другие HTML‑теги для нормальной вёрстки. Этого на момент запуска не было, поэтому нам пришлось дорабатывать и библиотеку Beduin для MAB, и Kotlin DSL. В итоге необходимую поддержку добавили, и с SEO стало всё нормально.
Сейчас страница на Kotlin DSL запущена в категориях «Новое авто», «Б/У Авто» и «Подработка», а в «Вакансиях» и в «Путешествиях» идут A/B‑тесты.

Чему мы научились при работе со страницей объявления:
Один шаблон для трёх платформ сделать можно, но с большим количеством ограничений. Возможностей Beduin не хватало, и их пришлось дорабатывать по ходу работы. Около трёх месяцев дорабатывали и команда Beduin, и мы. В итоге всё необходимое добавили, хотя местами получилось с костылями.
Нужно закладывать время на адаптацию к Beduin. Порог входа высокий и для написания кода на Beduin с Kotlin DSL, и для запуска у себя, тестирования, поиска и исправления ошибок. Разработчикам приходилось осваивать всё это на ходу.
Нужно быть готовым к просадке перформанса, потому что Beduin добавляет дополнительные JS‑ и CSS‑файлы, из‑за чего перформанс часто проседает. Отдельные Beduin‑блоки мы не профилировали: смотрели на страницу целиком и сравнивали её с нативной версией с помощью Lighthouse и Web Vitals. Поскольку всю страницу из общего JSON строит один рендерер, отдельно отследить загрузку каждого блока было сложно. Некоторые A/B‑тесты долго не удавалось завершить как раз из‑за проблем с перформансом и просадки продуктовых метрик.
Нельзя просто вставить взять и Beduin‑рендерер на страницу. Нужно убирать всё лишнее: всякие useEffect, хуки, JS‑обвязку, потому что в Beduin всё это будет мешать и тормозить страницу. В идеале страницу нужно делать с нуля, оставляя только Beduin‑рендерер и ничего лишнего.
Спасибо, что дочитали до конца! А вы переходили на BDUI? Расскажите в комментариях, как это было. А если только планируете, тогда задавайте вопросы, я постараюсь подсказать.