Эта статья — продолжение цикла статей о платформе Bricks в Авито, поэтому для полного понимания происходящего перед прочтением данной статьи настоятельно рек��мендую ознакомиться с работами моих коллег: Bricks: новый подход к управлению интерфейсами, Современные подходы к управлению UI: low-сode & Backend-Driven UI, Backend-driven UI: от идеи к проду.

Дмитрий Гусев
Старший разработчик интерфейсов в Avito
Я — Дмитрий Гусев, Frontend-разработчик в команде Bricks в кластере Core Services в Авито, в статье подробно рассказываю, как и зачем мы внедрили механизм предварительного просмотра проектируемого интерфейса в реальном времени прямо в админку Bricks: зачем это понадобилось, как всё устроено под капотом и с какими неожиданностями столкнулись по пути.

О чём пойдёт речь
Немного контекста
Чтобы понять, как устроен предпросмотр, важно разобраться, что такое Bricks и как он связан с Beduin.
Bricks — платформа для управления разметкой пользовательских интерфейсов с помощью независимых и переиспользуемых виджетов. Она позволяет собирать страницы из готовых компонентов — «кирпичиков», описывая их поведение в декларативном формате (JSON / DSL).
Когда пользователь сохраняет макет, Bricks формирует структуру макета (bricksLayout
) — набор виджетов, их параметры и правила взаимодействия между ними — и передаёт её в сервис bricks-composition, который превращает описание в Beduin-сценарий (beduinLayout
).
Beduin — это кроссплатформенный движок, разработанный на Kotlin Multiplatform. Он отвечает за вычисление состояния приложения и взаимодействие компонентов, а интерфейс отрисовывается нативными рендерами, реализованными под каждую платформу: Android, iOS и Web.
Для веба используется модуль beduin-v2-web
— React-рендер, работающий на тех же компонентах дизайн-системы Авито Akita, что и нативные клиенты.
Фактически:
Bricks → собирает конфигурацию (
bricksLayout
);bricks-composition → превращает layout в Beduin-сценарий (
beduinLayout
);Beduin → рендерит UI на клиенте.
Зачем мы делали предпросмотр в Bricks
Когда команда Bricks только запускалась, интерфейсы в Авито собирались по классической схеме: «дизайнер → задача в Jira → разработчик → код → тесты → публикация». Даже если нужно было просто поменять порядок блоков или цвет кнопки, приходилось проходить весь цикл.
Изменения в коде, ревью, сборка, релиз — всё это занимало время. А если правки касались мобильного приложения, к процессу добавлялись ещё сборки под iOS и Android, загрузка в сторы и ожидание обновления пользователями.
В итоге между идеей и живым UI могло пройти несколько недель.
Bricks частично решал эту проблему, но оставалась одна боль — невозможно было мгновенно увидеть результат.
Для контент-менеджеров и дизайнеров это выглядело как чёрный ящик. Любая правка превращалась в цепочку «попроси разработчика → дождись публикации → поймай баг → начни заново». Проверка простого текста могла растянуться на полдня.
Мы хотели сделать так, чтобы:
интерфейс можно было увидеть сразу после изменения, прямо в админке Bricks;
предпросмотр был полностью реактивным, без перезагрузки и публикации;
и главное: любой пользователь — от разработчика до контентщика — мог проверить результат самостоятельно.
Так родилась идея встроить движок рендеринга Beduin прямо в Bricks и сделать предпросмотр в реальном времени — тот самый шаг, который сократил путь от идеи до UI до одной минуты.
По сути, это не просто визуальная фича, а изменение подхода: теперь цикл проверки гипотезы не требует релиза, отдельной среды или команды разработчиков. Всё происходит в одном месте — в интерфейсе Bricks.
Как мы жили без предпросмотра
До появления предпросмотра проверка шаблона выглядела так:
Создаёшь новый шаблон или правишь существующий.
Сохраняешь изменения.
Публикуешь версию, чтобы она дошла до bricks-composition.
Запрашиваешь собранный Beduin-сценарий у bricks-composition.
Открываешь песочницу Beduin на отдельном домене (или в Android Studio / Xcode).
Подставляешь сценарий. Надеешься, что всё собралось правильно.
Если нужно было поменять порядок блоков или поправить текст, весь этот цикл повторялся снова. Даже незначительная правка превращалась в мини-релиз.
Каждый эксперимент — как маленький релиз
Когда ты работаешь с десятками шаблонов и виджетов, любое изменение цепляет контракты, данные и зависимости. Из-за отсутствия предпросмотра приходилось буквально «проверять на проде» — публиковать новую версию, смотреть результат и, если что-то не так, откатываться назад.
Проблема усугублялась тем, что Bricks активно используют не только разработчики: контент-менеджеры меняют тексты и баннеры, аналитики проверяют гипотезы A/B, дизайнеры экспериментируют с порядком блоков.
И каждый раз им приходилось просить разработчика:
Опубликуй, пожалуйста, новую версию, я посмотрю, как выглядит.
В среднем между нажатием кнопки «Сохранить» и тем моментом, когда можно увидеть результат, проходило 5–10 минут. Это убивает динамику и мешает креативу: когда фидбек не мгновенный, человек теряет контекст задачи.
Ошибки, которых можно было избежать
В процессе публикации часто происходили банальные накладки, из-за чего приходилось «катать» правки по несколько раз. Даже если баг был чисто визуальный, он всё равно требовал повторного цикла публикации и проверки.
Кроме того, у нас не было возможности проверить несохранённые изменения, то есть черновики, что шло вразрез с идеей low-code, где результат должен быть виден сразу.
Почему этого стало недостаточно
Bricks со временем перестал быть внутренним инструментом только для инженеров. К нему подключились дизайнеры, маркетинг и аналитика: нужно быстро проверять гипотезы и визуальные варианты.
В какой-то момент мы поняли, что скорость проверки гипотез упирается не в код, а в сам процесс, и если не сократить путь «сохранил → увидел», то никакие оптимизации на уровне рендеринга уже не помогут.
Как мы подошли к решению
Когда мы наконец сформулировали основную боль — невозможность мгновенно увидеть результат изменений, то стало ясно: нужно не ускорять публикацию, а поменять сам принцип работы с интерфейсом.
Bricks и Beduin уже решали большую часть инфраструктурных задач: UI описывался декларативно, версии хранились в одном месте, изменения доставлялись без релиза мобильных приложений. Но оставался ключевой разрыв — между моментом «сохранить» и моментом, когда ты видишь, как всё выглядит «вживую».
Мы спросили себя:
Если движок Beduin уже умеет рендерить интерфейсы из сценария, зачем вообще ждать публикации, чтобы увидеть результат?
Первая гипотеза — рендерим прямо в админке
Мы начали с простого: попробовать отрисовать Beduin-сценарий прямо в интерфейсе Bricks. Без публикации, без походов в песочницы. Просто взять JSON макета и показать, как он будет выглядеть на клиенте.
На бумаге идея выглядела очевидно, но �� реальности пришлось решить массу нюансов:
корректно изолировать Beduin от основного приложения;
развести зависимости и контексты React;
обеспечить безопасность, чтобы код предпросмотра не влиял на админку.
Первый прототип — «песочница в песочнице»
Мы собрали минимальный вариант: обернули web-рендер Beduin в <iframe>
и загрузили туда рабочий сценарий. Так интерфейс впервые «ожил» прямо внутри Bricks.
Впервые можно было увидеть результат прямо в Bricks, без публикации и без ожидания сборок. Это был переломный момент — идея оказалась жизнеспособной.
Что было дальше
У нас была необходимость в следующих пунктах:
мгновенные обновления предпросмотра при изменении параметров шаблона, настройке зависимостей, источников данных и стейта;
возможность менять разрешение экрана предпросмотра под разные устройства;
поддержать несколько предпросмотров одновременно — для каждого шаблона и виджета.
Просто iframe
+ JSON уже не хватало — нужен был полноценный модуль, который можно встроить куда угодно.
Так появился YALC Preview (Yet Another Low Code) — встроенный рендерер Beduin, работающий прямо в Bricks.
Главное осознание
Когда мы показали прототип дизайнерам и контент-командам, стало очевидно: скорость обратной связи — это новый UX даже для разработчиков.
Путь «сделал → посмотрел → изменил» занял секунды. Bricks перестал быть просто системой управления макетами — он стал живым конструктором, где интерфейс оживает прямо на глазах.
Как устроена архитектура предпросмотра
Когда стало понятно, что прототип работает, настал черед следующего вопроса — как встроить предпросмотр в общую архитектуру Bricks, чтобы он не был «хаком», а стал частью основного пайплайна.
Мы не хотели делать отдельный сервис или песочницу, хотелось, чтобы предпросмотр использовал те же механизмы, что и продакшн: те же данные, ту же валидацию, тот же bricks-composition.
Как backend собирает Beduin-сценарий «на лету»
Когда пользователь открывает предпросмотр, bricks-backend определяет состояние макета и выбирает подходящий сценарий.

Если макет не сохранён, bricks-backend сначала собирает все данные макета, рассчитывает контракты, выполняет валидацию и создаёт временную сущность — как будто макет сохранён, но с фейковыми ID. После этого сформированный объект отправляется в bricks-composition, где дальше выполняется сборка.
Если макет уже сохранён, данные берутся напрямую, валидация выполняется стандартно, а затем идёт тот же вызов в bricks-composition.
Если макет опубликован, всё ещё проще: bricks-composition просто получает текущую версию конфигурации и собирает итоговый сценарий.
На стороне bricks-composition сборка происходит почти так же, как продакшн-сценарий:

Сначала выполняется resolveRenderConfig
: если в запрос пришёл параметр buildData
, рендер-конфиг собирается прямо из него; если нет — извлекается из хранилища.
Затем вызывается prepareAssemblyItem(renderConfig, mode=preview)
— это тот же ассемблер, который собирает макет в бою, только с дополнительным флагом preview
.
В этом режиме он:
подгружает источники данных и зависимости;
обогащает сценарий динамическими параметрами;
формирует итоговый
beduinLayout
иbricksLayout
;но не помечает ошибки как internal — чтобы автор макета сразу видел, где проблема.
Таким образом, пайплайн предпросмотра полностью совпадает с prod-цепочкой, включая все слои валидации и сборки. Разница лишь в том, что никакие сущности не сохраняются, а фейковые ID используются только в пределах одной сессии.
Как всё устроено на фронтенде
Фронтенд-часть предпросмотра живёт в отдельном модуле beduin
. Она построена вокруг трёх компонентов: Presenter
, FrameWrapper
и Renderer
— каждый из них отвечает за свой уровень «жизни» предпросмотра.
Presenter — точка входа из админки
Presenter
встраивается прямо в интерфейс Bricks. Он создаёт <iframe>
с url'ом /preview/{key}
и подстраивает размеры области предпросмотра под выбранное устройство:
export const Presenter: FC<PresenterProps> = ({ keyProp, isLoading }) => {
const [isFrameLoading, setIsFrameLoading] = useState(true);
const { viewport } = useViewport({ id: keyProp });
const contentClassname = classNames(styles.content, {
[styles.blur]: isLoading,
});
const onFrameLoad = (): void => {
setIsFrameLoading(false);
};
return (
<div className={styles.root}>
<div
className={styles['content-wrapper']}
style={
{
'--preview-viewport-width': `${viewport?.width}px`,
'--preview-viewport-height': `${viewport?.height}px`,
} as CSSProperties
}
>
{isFrameLoading && <Loader />}
<iframe
id={keyProp}
className={contentClassname}
onLoad={onFrameLoad}
title={keyProp}
sandbox='allow-same-origin allow-scripts'
src={`/preview/${keyProp}`}
/>
</div>
</div>
);
};
Благодаря этому предпросмотр можно вставить в любое место админки — он всегда будет вести себя одинаково.
FrameWrapper — «панель устройства»
Вокруг Presenter
работает FrameWrapper
— компонент, который превращает предпросмотр в полноценный «режим адаптива».
Можно менять разрешение вручную, при помощи перетягивания границ или переключаться между пресетами — и всё это синхронно обновляется:
export const FrameWrapper: FC<FrameWrapperProps> = ({ keyProp, frame, children }) => {
const isAdaptive = frame === 'responsive';
const { viewport } = useViewport({
id: keyProp,
});
return (
<div className={styles.root}>
<ResizeableElement
id={keyProp}
className={styles.wrapper}
initialWidth={viewport?.width}
initialHeight={viewport?.height}
minWidth={0}
minHeight={0}
maxWidth={999}
maxHeight={999}
position={isAdaptive ? ['left', 'right', 'top', 'bottom'] : undefined}
anchor={<MoreVerticalIcon color='white' />}
>
<div className={styles.content}>{children}</div>
</ResizeableElement>
</div>
);
};

При каждом изменении размера FrameWrapper
вызывает useViewport.setSize()
, который синхронизирует новое значение сразу во всех вкладках (в следующей главе описывается принцип работы).
Renderer — Beduin внутри iframe
А вот внутри самого iframe
рендерится полноценный Beduin-сценарий. Компонент Renderer
создаёт окружение (тема, диплинки, нотификации) и монтирует BeduinRenderer
из @avito/beduin-v2-web
:
export const Renderer: FC<RendererProps> = ({ keyProp, layout, kind }) => {
if (!keyProp) {
return null;
}
return (
<DeeplinkProvider>
<CssThemeProvider
className={styles.theme}
design='2023'
>
<ThemeProvider isolate>
<BeduinRenderer
kind={kind}
layout={layout}
/>
<NotificationContainer />
</ThemeProvider>
</CssThemeProvider>
</DeeplinkProvider>
);
};
Сам BeduinRenderer
выбирает нужный рендер (BeduinMobile
или BeduinDesktop
) и оборачивает его в ErrorBoundary
, чтобы ошибка внутри предпросмотра не уронила всю админку:
export const BeduinRenderer: FC<RendererProps> = ({ layout, kind = 'mobile' }) => {
const Component = getRenderer(kind);
const errorKey = useErrorReset(layout);
useDeeplinkRegistry();
if (layout === '{}') {
return <ErrorRenderer title='' />;
}
return (
<ErrorBoundary
// Используем ключ для принудительного сброса ошибки
key={errorKey}
FallbackComponent={({ error }: { error: Error }) => {
const { name, message, stack } = error;
return (
<ErrorRenderer
title={name}
message={message}
trace={stack}
/>
);
}}
>
<Component
keepBeduinState
screenName='Bricks Preview'
state={layout}
nativeStatesRegistry={nativeStatesRegistry}
nativeComponentsRegistry={nativeComponentsRegistry}
/>
</ErrorBoundary>
);
};
Всё это работает внутри iframe
с sandbox
. Это даёт полную изоляцию: любые события, модалки или тостеры остаются внутри предпросмотра. Можно открыть несколько предпросмотров одновременно, они не влияют ни друг на друга, ни на сам Bricks.
Как мы сделали обновление данных и размеров в реальном времени
Одно из ключевых требований — реактивность. Пользователь не должен «ждать предпросмотр», он должен видеть результат сразу после изменения любого параметра.
Механика синхронизации через localStorage
Для этого мы сделали простой, но эффективный механизм синхронизации. При изменении параметров макета данные сохраняются в localStorage
, а затем вручную рассылается кастомное событие, которое ловят все открытые окна и фреймы:
const dispatchEvent = useCallback((key: string) => {
window.dispatchEvent(new StorageEvent(windowCustomEventMap['storage'], { key }));
}, []);
Это решение появилось из-за особенностей Web Storage API:
/*
https://developer.mozilla.org/ru/docs/Web/API/Web\_Storage\_API
The StorageEvent is fired whenever a change is made to the Storage object.
This won't work on the same page that is making the changes — it is really a way
for other pages on the domain using the storage to sync any changes that are made.
Из-за такого поведения единственным вариантом для мгновенной обработки изменений в storage
остается отправка кастомных событий вручную:
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent
*/
То есть стандартное событие storage
не срабатывает в том же окне, где была запись. Поэтому, чтобы предпросмотр в iframe
моментально реагировал, мы вручную «бросаем» событие и слушаем его в хуках.
useLocalStorage и useViewport
Всё построено на двух базовых хуках — useLocalStorage
и useViewport
.
Первый хранит данные в localStorage
и рассылает кастомное событие при каждом изменении:
const [storedValue, setStoredValue] = useState(() => {
if (initializeWithValue) {
return getValue(key);
}
return initialValue instanceof Function ? initialValue() : initialValue;
});
const setValue = (value: T) => {
const newValue = value instanceof Function ? value(getValue(key)) : value;
window.localStorage.setItem(key, serializer(newValue));
setStoredValue(newValue);
dispatchEvent(key);
};
А второй поверх этого механизма синхронизирует размеры предпросмотра — ширину и высоту в пикселях:
export const useViewport = ({
id,
min = 0,
max = 999,
initialValue = defaultViewport,
}: ViewportProps): ViewportResult => {
const storageKey = `viewport-id-${id}`;
const [viewport, setSizeInStorage] = useLocalStorage(storageKey, initialValue);
const setSize = (value: ViewportSize) => {
setSizeInStorage(value); // сохраняем и рассылаем событие
};
const handleStorageChange = useCallback(
(event: StorageEvent | CustomEvent) => {
const storageEvent = event as StorageEvent;
const storageEventExist = storageEvent.key && storageEvent.key === storageKey;
if (!storageEventExist) {
return;
}
setViewport(getValue(storageKey));
},
[storageKey, getValue],
);
useEffect(() => {
window.addEventListener('storage', handleStorageChange);
window.addEventListener(windowCustomEventMap['storage'], handleStorageChange);
return () => {
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener(windowCustomEventMap['storage'], handleStorageChange);
};
}, [handleStorageChange]);
};
Благодаря этому любое изменение, будь то параметр шаблона или растягивание предпросмотра в адаптивном режиме, мгновенно отражается на экране.
На практике это оказалось невероятно быстрым решением. Средний beduinLayout
весит около 50 КБ, а лимита localStorage
(~5 МБ) хватает примерно на сотню активных предпросмотров. Для тяжёлых кейсов мы предусмотрели переход на IndexedDB, но на данный момент такой необходимости нет.
Как предпросмотр поддерживает контекст A/B-тестов
При прохождении через API-gateway запрос может наполняться дополнительным контекстом, содержащим платформу, версию приложения, userId, deviceId и другие данные, используемые системой A/B-тестов.
Пример такого контекста выглядит так:
app=104.6&did=12345678-ABCD-1234-ABCD-1234567890AB&uid=12345678&p=4
Чтобы можно было тестировать такие кейсы прямо из админки, мы научили админку подменять эти данные, а bricks-backend прокидывать его до bricks-composition.
При формировании запроса на предпросмотр можно передать нужные параметры, например, appVersion=123&p=4
. Bricks-composition получит эти значения и соберёт сценарий так, как если бы запрос пришёл от реального пользователя с этим контекстом.
Это даёт огромный плюс: можно проверять A/B-варианты прямо в предпросмотре без запуска эксперимента и без публикации.
Допустим, для определенной версии iOS приложения мы хотим показывать новую секцию — «онлайн-показ». Для этого на макет добавляем новый набор виджетов и у корневого ставим параметр visible
в значение false
:

После настраиваем зависимость на версию приложения:

Открываем предпросмотр и указываем контекст с версией приложения, которую указывали при создании зависимости (i.e.,app=123&p=4
):

Bricks-composition подставляет контекст, применяет зависимости, и секция появляется в итоговой разметке. Если поменять значение версии, то секция снова будет скрыта:

Таким образом, предпросмотр превратился не просто в визуальный конструктор, а в эмулятор реального поведения продакшна. Он учитывает контекст пользователя, версии платформ, активные фичи и зависимости.
И напомню: всё это — без публикации и без релиза.
С какими трудностями столкнулись
1) Изоляция Beduin рендерера внутри админки, которая уже на React.
Первая проблема была довольно банальной: предпросмотр — это тоже React-дерево, но со своими контекстами, порталами (модалки, тосты), хоткеями и фокус-ловушками. Если монтировать его «рядом» с админкой, события начинают протекать: модалка из предпросмотра перехватывает Esc у главной страницы, всплывают тосты не там, где ожидаешь, стили пересекаются.
Решение: жёсткая изоляция через iframe
с sandbox='allow-same-origin allow-scripts'
. Так мы сохранили доступ к localStorage
(для синхронизации) и одновременно закрыли все «дыры» событий и стилей. Параллельно убедились, что внутри iframe
инициализируются все нужные провайдеры (тема, диплинки, контейнер нотификаций).
2) Крашоустойчивость рендерера
@avito/beduin-v2-web
— мощная штука, но, как и любой рендерер, иногда кидает ошибки.
Решение: обернули рендерер в react-error-boundary
и сделали «жёсткий» сброс по ключу: при каждом изменении Beduin-сценария мы инкрементим key и тем самым пересоздаём чистую ноду рендера. Пользователь видит понятное сообщение (title / message / stack), а следующий валидный сценарий стартует с нуля.
3) Реактивность без перезагрузки
Событие storage
не срабатывает в том же окне, где произошло изменение localStorage
, оно предназначено только для синхронизации между разными вкладками.
Нам же нужна мгновенная двусторонняя синхронизация: редактор → предпросмотр и обратно, в том числе внутри одного окна с iframe
.
Решение: dispatch кастомных событий, после записи в localStorage
мы явно шлём:
// см. комментарий в коде — почему так (Web Storage API)
window.dispatchEvent(new StorageEvent(windowCustomEventMap['storage'], { key }));
и подписываемся на два типа событий: нативный storage
(на случай другой вкладки / фрейма) и наше storage
(на случай текущего окна).
Именно это позволило сделать живую адаптивность: тянешь границу предпросмотра — useViewport
меняет размеры → событие улетает всем → фрейм моментально меняет ширину / высоту.
4) Ограничения localStorage и производительность сериализации
Лимит ~5 МБ — не шутка. Один средний beduinLayout
≈ 50 КБ; сотня открытых предпросмотров — и мы уже у границы.
Решение:
неймспейсим ключи (
viewport-id-{id}
,{previewKey}-layout
и т. п.) и чистим старыe записи по LRU / таймаутам;храним ровно то, что нужно для realtime (layout / контракты / viewport), всё остальное — по запросу;
для тяжёлых кейсов предусмотрена миграция на IndexedDB. Формально пока она нам не понадобилась, но путь миграции готов, API совместимо.
5) Конфликт алиасов и иконок в сборке
И админка, и рендерер используют внутреннюю библиотеку Авито иконок @avito/zna4ki
.
В качестве сборщика мы используем Vite, а он иногда резолвит их по-разному, и в предпросмотре иконки не подцепляются.
Решение: генерация Vite-плагина перед сборкой:
import path from 'path';
import fs from 'fs';
const shouldIncludeKey = (key: string) => {
return key.startsWith('./kvadratiki/') || key.match(/^\.\/[A-Z]/);
};
const getFindPath = (key: string): string => {
return `@avito/zna4ki${key.replace(/^\./, '')}`;
};
const getReplacementPath = (value: string): string => {
return path.resolve(__dirname, `../node_modules/@avito/zna4ki/${value}`);
};
export const resolveZna4kiAliases = (): { find: string; replacement: string }[] => {
const packagePath = path.resolve(__dirname, '../node_modules/@avito/zna4ki/package.json');
if (!fs.existsSync(packagePath)) {
return [];
}
const zna4kiPackage = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
return Object.entries(zna4kiPackage.exports)
.filter(([key]) => shouldIncludeKey(key))
.map(([key, value]) => ({
find: getFindPath(key),
replacement: getReplacementPath(value as string),
}));
};
Скрипт проходит по всем экспортам пакета и преобразует их в абсолютные пути внутри сборки. Так плагин всегда использует актуальные ссылки, а проблемы с неверными иконками исчезают.
6) Несохранённые макеты и «фейковые» ID
Предпросмотр для несохранённой конфигурации означает, что у сущностей ещё нет реальных идентификаторов. Но пайплайн бэкенда к ним привык.
Решение. На бэкенде добавили ветку сборки «как бы сохранённой» сущности, после чего сборку layout'а bricks-composition, как если бы всё лежало в БД.
Что получили в итоге
Когда предпросмотр заработал стабильно и перестал быть экспериментом, стало ясно — это не просто «удобная фича для разработчиков». Это точка перелома во всей цепочке создания интерфейсов.
Если раньше путь от идеи до рабочего UI занимал часы, то теперь этот путь реально укладывается в минуты. Раньше дизайнер или аналитик мог только ждать, пока разработчик внесёт правки и выкатит их на песочницу. Теперь — может сам открыть шаблон, внести изменения, нажать Предпросмотр и сразу увидеть результат.
Это не ускорение на «немного» — это другой способ работы.
Разработчики
Для инженеров предпросмотр стал настоящим quality-of-life upgrade:
больше не нужно катать песочницы и переключаться между средами. Всё, включая Beduin-рендер, теперь живёт прямо в админке;
CI/CD перестал быть частью тестового цикла. Разработчик проверяет результат локально или прямо в Bricks, не дожидаясь публикации;
ошибки видны сразу. Любая несовместимость контрактов, битые данные или неверные зависимости отображаются в предпросмотре в понятном виде;
кроссплатформенность «по умолчанию». Так как Bricks использует один и тот же движок Beduin, предпросмотр на web полностью повторяет поведение на Android и iOS.
Результат — меньше контекстных переключений, меньше ручных проверок и ощутимо меньше рутинных действий.
По нашим внутренним метрикам, время от изменения шаблона до проверки визуального результата сократилось примерно в 20 раз.
Для команды это означало не просто ускорение, а появление нового уровня обратной связи: можно проверять гипотезы буквально на лету, не теряя концентрации.
Дизайнеры, аналитики и контент-менеджеры
Bricks из инструмента для инженеров превратился в инструмент для всей продуктовой команды. Предпросмотр стал окном, через которое можно проверять идеи без участия разработчиков:
дизайнеры проверяют адаптивность, виз��альную целостность и взаимодействие компонентов сразу в живом интерфейсе, а не на статичных макетах;
аналитики могут проверять, как разные версии интерфейса реагируют на контекст, и быстро валидировать гипотезы до запуска A/B-теста;
контент-менеджеры меняют тексты, изображения и правила показа блоков, не дожидаясь билда — и видят, как это отразится на реальных экранах пользователей.
Всё это снизило нагрузку на разработчиков и убрало десятки микрокоммуникаций.
Бизнес и продукт
Для бизнеса ключевое — сокращение TTM и повышение скорости обратной связи:
проверка изменений теперь занимает минута, а не спринт;
новые гипотезы можно проверять параллельно: сразу несколько ролей могут работать в одних и тех же макетах, не мешая друг другу;
благодаря предпросмотру с контекстом можно эмулировать реальные сценарии A/B-тестов, не дожидаясь rollout.
Культурный эффект
Есть и менее измеримый, но важный результат. Предпросмотр изменил саму философию работы: теперь Bricks воспринимается не как система конфигураций, а как интерактивный конструктор, где можно творить, экспериментировать и видеть результат сразу.
Команды перестали бояться менять UI — «сломать» стало сложнее, а откатить проще.
Роли начали пересекаться: дизайнеры учатся смотреть на структуру данных, разработчики — на визуальную композицию. Система перестала быть узким горлышком и стала площадкой для сотрудничества.
Итог
Внедрение предпросмотра дало не просто ускорение, а новый способ разработки: от «напиши и подожди релиз» — к «поменял и увидел».
Bricks стал тем местом, где UI рождается и тестируется в реальном времени, а фраза «от идеи до готового интерфейса за 60 секунд» перестала быть преувеличением — это действительно стало нормой.
Что планируем добавить
Следующий крупный шаг в развитии предпросмотра — переход от статичного окна рендера к полноценному интерактивному canvas.
Сейчас предпросмотр показывает готовый экран один к одному с реальным UI, но работать с ним можно только в фиксированном масштабе. При большом количестве элементов или длинных лэйаутах это не всегда удобно: чтобы рассмотреть интерфейс целиком или сфокусироваться на отдельной секции, приходится вручную менять размеры области предпросмотра.
Чтобы решить это, мы добавляем canvas-режим с поддержкой zoom и pan. Пользователь сможет свободно масштабировать экран, перемещаться по нему и взаимодействовать с элементами прямо на canvas — точно так же, как в инструментах вроде Figma.
Для реализации мы выбрали React Flow — библиотеку, изначально спроектированную под React и идеально подходящую для интерактивных схем и графов.
Она даёт всё, что нужно предпросмотру «из коробки»:
плавный zoom и pan всей области;
drag-n-drop и кликабельные элементы;
декларативное управление через state;
возможность отображать не только UI-элементы, но и связи между ними (например, нам это понадобится для показа зависимостей между виджетами).
Другие варианты вроде PixiJS, Fabric.js или React-Konva мы рассматривали, но все они требовали ручной реализации логики связей, масштабирования и событий, тогда как React Flow решает эти задачи изначально.
Что это даст
После внедрения canvas-режима предпросмотр станет нагляднее и интерактивнее:
можно будет свободно приближать / отдалять интерфейс, как в дизайнерских редакторах;
появится основа для визуализации связей между компонентами и данных;
разработчикам будет проще отлаживать сложные UI-структуры с большим количеством элементов;
в будущем на этом канвасе можно будет добавить режим визуального редактирования — например, выделение и настройку компонентов прямо на предпросмотре.
Финальные мысли
Предпросмотр начинался как побочный эксперимент — способ увидеть макет без публикации. Но довольно быстро стало ясно: мы делаем не вспомогательный инструмент, а новый способ работы с интерфейсами.
Сегодня он стал естественной частью Bricks: разработчики тестируют шаблоны без билда, аналитики проверяют гипотезы прямо в админке, дизайнеры видят поведение макета на реальных данных.
Мы сократили путь от идеи до готового UI в десятки раз — и это не просто ускорение, а смена парадигмы. Интерфейс перестал быть результатом долгого цикла разработки. Он стал живым, реактивным, таким же быстрым, как сама мысль.
Когда-то мы говорили «low-code — это про скорость». Теперь — это про обратную связь: мгновенную и наглядную. Именно она позволяет принимать решения быстрее и делать продукты осознаннее.
Итог простой: Bricks стал той low-code платформой, где идея, дизайн и код живут в одном месте и встречаются не на релизе, а уже на этапе предпросмотра.
Сегодня мы зашли ещё дальше, так как начали собирать Bricks на Bricks — это логичное продолжение идеи low-code: когда инструмент становится настолько зрелым, что может развивать сам себя.
Спасибо за уделённое статье время! Увидимся в следующих =)
Материал написан по мотивам моего выступления на конференции Avito Frontend Meetup:
А эта ссылка — для тех, кто хочет посмотреть выступление на YouTube.