Кейс из жизни: мини-приложения, анимированные обложки, внешние команды — и одна на вид «валидная» анимация, которая кладет все приложение. Рассказываем, как мы научились воспринимать Lottie-файлы не как медиа, а как исполняемый код — и почему это улучшило стабильность всей системы.
Проект: мини-приложения внутри большой экосистемы
Мы работаем над платформой для размещения, управления и общего менеджмента мини-приложениями — небольших встроенных приложений, создаваемых внешними разработчиками-энтузиастами. Любой желающий может зарегистрироваться и разместить свое приложение. Они запускаются прямо внутри основной системы, используют общий API и работают по принципу «быстрый запуск — максимум пользы».
Такая архитектура требует особого подхода к UX: мини-приложения должны быть легкими, быстрыми, отзывчивыми. Они появляются буквально «внутри экрана», без перезагрузок, без длинных переходов, в рамках общего интерфейса.
Чтобы пользователю было понятно, что происходит — загружается ли что-то, обработано ли действие, завершился ли переход — визуальный отклик становится критически важным. Здесь на сцену выходит анимация: анимированные иконки, лоадеры, интерактивные элементы, навигационные сигналы. Все это должно работать быстро, стабильно и быть визуально едино с остальной системой.
Кроме того, в некоторых случаях анимация используется не только как функциональный элемент интерфейса, но и как визуальное оформление — например, в виде анимированной обложки мини-приложения в каталоге. Это часть привлечения внимания, способ выделиться среди десятков других карточек. Такая обложка — первое, что видит пользователь. И если она не отрисовалась или сломала компонент — это уже не просто мелкая ошибка, а прямое влияние на пользовательский опыт и восприятие продукта.
Почему в нашем проекте вообще понадобились Lottie-анимации
С внедрением анимации в продукт появились и ограничения. Нам было важно, чтобы визуальные эффекты не перегружали страницу, быстро загружались, были легко управляемыми и редактируемыми.
Поэтому в проекте используется формат Lottie — компактные векторные анимации в формате JSON, которые удобно рендерить прямо в браузере, без использования тяжелых изображений или видео.
Преимущества Lottie:
Легкий размер (JSON, а не видео).
Масштабируемость без потери качества.
Поддержка Web, iOS, Android, React Native — без изменений.
Интерактивность: можно реагировать на события, управлять в рантайме.
Гибкость: поддержка векторной и растровой графики.
Для платформы, где пользователи взаимодействуют с десятками мини-приложений, интерфейсы кастомизируются под каждую команду, а анимации выполняют не просто декоративную, а навигационную и статусную функцию, — Lottie выглядит как идеальное решение.
В теории — все отлично. Но дальше начинается практика.

Как мы поймали проблему
На старте все шло по плану. Пользователи загружали Lottie-анимации через интерфейс. JSON-файлы успешно отправлялись на бэкенд и проходили валидацию: структура, формат, размер, наличие ключей. Если все ок — файл сохранялся в базе и по запросу с фронта отображался в нужном месте.
Потом начались сбои. Где-то не прорисовывалась иконка, где-то падал компонент или даже страница. Все это происходило в момент использования на фронте, хотя загрузка происходила без явных ошибок. Файл проходил бэкенд-валидацию, но во время рендера ломал компонент. Например, возникала “cannot read property of undefined (reading 'length')” и другие классические runtime-ошибки JavaScript.
С технической точки зрения Lottie — это JSON. Он может быть валиден, пока существует ”сам по себе”. Структура, формат, размер, наличие ключей - вроде бы все ок. Поэтому он легко проходил бэкенд-валидатор.
Но по сути — это набор инструкций, описывающих, как отрисовать векторную анимацию: по кадрам, слоям, key-фреймам. Когда эти инструкции выполняются через react-lottie (конкретно в нашем случае), наружу выползают все ошибки рантайма, например, классическое cannot read property of undefined

Что мы пробовали (и как это не помогло)
Когда стало понятно, что бэкенд-валидация не спасает, мы решили перенести проверку Lottie-файлов на фронт. Логика была простая: если файл валидный — грузим его на бэкенд, если нет — сразу показываем ошибку. Казалось бы, идеальный способ снизить нагрузку, убрать мусор и улучшить UX.
Мы начали с того, что было под рукой.
1. lottie-react: onLoad и onError
Первым делом попробовали использовать onLoaded и onError из lottie-react:
<Lottie
animationData={animationData}
onLoaded={() => console.log('Lottie file is valid!')}
onError={(error) => console.error('Invalid Lottie file:', error)}
/>
На бумаге все выглядело логично. На практике — даже сломанный JSON вызывал onLoaded, как ни в чем не бывало.
2. lottie-web и data_ready
Ок, идем глубже — берем lottie-web и слушаем data_ready:
anim.addEventListener('data_ready', () => {
});
console.log('Lottie file is valid!');
Надеемся, что отрендерилось — значит, все хорошо. Увы. “Битые” Lottie-файлы опять проходили мимо, ни одного предупреждения.
3. JSON-схема через ajv
Последняя формальная попытка — проверка структуры через ajv с использованием схемы Lottie:
const ajv = new Ajv();
const validate = ajv.compile(lottieSchema);
const isValid = validate(data);
Да, формат проверяется. Да, ключи на месте. Да, структура соблюдена.
Нет, это не решает нашу проблему. Потому что даже полностью «валидный» с точки зрения схемы JSON мог ронять компонент при рендере.
Пришлось креативить
Если ни одна библиотека не может гарантировать, что файл не упадет при отрисовке — значит, нужно проверять рендер прямо руками. Вернее, через ErrorBoundary.
Так и появился наш подход: отрисовываем Lottie в невидимом контейнере, ловим ошибки — и только потом решаем, отправлять ли файл дальше.

Решение звучит просто, но на практике пришлось пройти пару кругов ада с асинхронностью, фазами рендера и жизненным циклом React-компонентов. Но оно того стоило.
Сначала мы использовали ErrorBoundary в классическом сценарии — чтобы обернуть отрисовку Lottie, пришедших с бэка. Это помогло с защитой фронта от «битых» файлов из БД. Теперь хотя бы страница не падала.
Следом — идея: раз мы и так проверяем анимации на выходе, почему бы не делать то же самое на входе? То есть: загружаем Lottie → пробуем отрендерить его → если все ок — сохраняем, если нет — сразу говорим пользователю, что файл сломан.
Сделали обертку:
<LottieErrorBoundary onSuccess={handleOKFile} onError={handleBrokenFile}>
<Lottie animationData={jsonFile} />
</LottieErrorBoundary>
Наивно надеялись, что этого будет достаточно. Но не тут-то было.
Сначала — не сработало
Первая реализация была предельно прямолинейной. Мы вызвали onSuccess() в componentDidMount, а onError() — в componentDidCatch. Все честно. И все — неправильно.
componentDidMount(): void {
this.props.onSuccess();
}
componentDidCatch(): void {
this.props.onError();
}
Lottie загружается асинхронно. Поэтому componentDidMount успевал сработать до того, как отрисовка падала. В результате мы получали успех — и следом ошибку. То есть сначала говорили пользователю: «все отлично», а через секунду — «упс, не то».
Переделали
Мы переписали компонент так, чтобы реагировать на результат рендера через состояние. Использовали getDerivedStateFromError
для фиксации ошибки на этапе render-phase, и componentDidUpdate — чтобы вызвать нужный колбэк.
static getDerivedStateFromError() {
return { hasError: true };
}
Теперь, если рендер проходит — ставим hasError: false
. Если ломается — hasError: true
. А дальше в componentDidUpdate
:
если все хорошо — вызываем onSuccess;
если упало — onError.
Также сбрасываем стейт, чтобы можно было повторно проверить другие файлы.
Итог
Так мы получили стабильную и адекватную проверку: отрисовываем Lottie, ловим поведение, и на основе этого решаем — файл пойдет в прод или нет. Все работает в рантайме, как надо. Ошибки вроде cannot read property of undefined
ловятся надежно. Фронт больше не падает. Жизнь стала спокойнее.

Что это дало
Меньше падений, меньше сюрпризов
Во-первых, мы перестали ловить падения на ровном месте. Вместо странных ситуаций вроде “у кого-то не видно иконки”, “на айфоне все сломалось” или “в консоли пусто, но UI мертв” — получили контролируемый и воспроизводимый механизм.
Если файл не отрисовывается, он просто не отправляется на бэкенд. Пользователь получает понятное сообщение об ошибке, а не молчаливую пустоту. Мы ловим это до того, как файл попадет в прод.
Снижение сетевой нагрузки
Раньше все Lottie-файлы летели на сервер — независимо от качества. Теперь валидация проходит локально, и только “живой” файл отправляется дальше. Это снизило количество бесполезных запросов и упростило дебаг: меньше "мусора" в БД и логах.
Единообразие валидации
Мы добились одинаковой логики валидации на входе и на выходе. Ранее было так: один механизм проверки на этапе загрузки, другой — на этапе отображения. Теперь оба работают одинаково: через поведенческую проверку на рендер. Это сильно упростило мышление команды — и разработчиков, и тестировщиков.
Незаметное, но важное улучшение UX
Пожалуй, самое ценное — это то, чего теперь не замечает пользователь.
Нет пустых мест, нет “визуального молчания”, нет случаев, когда интерфейс “где-то теряет часть себя”. Все работает стабильно, предсказуемо, без багов, которые раньше долго искались и ни на кого не вешались.
Что можно улучшить дальше
Решение работает — и команда его использует каждый день. Но, как водится, появились идеи, как сделать еще лучше.
Хотим соединить валидацию и pre-render
Асинхронность загрузки все еще может сыграть злую шутку. Иногда Lottie-файл может пройти наш фронтовый тест, но не отрисоваться в реальной среде из-за особенностей данных. Поэтому мы планируем объединить текущую реализацию с более глубоким pre-render-подходом и стандартными Lottie-валидациями (lottie-lint и др.). Это даст дополнительный уровень уверенности.
Собираем аналитику по ошибкам
У нас большая пользовательская база и сотни Lottie-файлов. Сейчас мы начали логировать типы ошибок, частоту, браузеры, окружения. Это даст нам возможность не просто “проверять” файл, а предсказывать, что с ним может пойти не так — и помогать пользователю заранее.
В идеале, хотим прийти к состоянию, когда система подсказывает: “эта анимация сломается в Safari 15, потому что маска №2 настроена вот так”.

Вывод
Lottie-файлы — это не просто JSON. Это полноценные сценарии отрисовки, которые исполняются в рантайме. И если их не тестировать как код — они будут вести себя как любой непроверенный код: ломать интерфейс, вызывать ошибки, портить UX.
Мы встроили поведенческую валидацию в наш процесс: ловим ошибки на рендере, до отправки на бэкенд. Получили стабильный фронт, предсказуемый интерфейс, уменьшили количество падений и лишней поддержки.
Если у вас в проекте анимации приходят от сторонних команд или загружаются динамически — не полагайтесь только на схему. Тестируйте в бою.
Это простое улучшение может избавить от десятков сложных и “невоспроизводимых” проблем.
oldd
Не рассматривали способ валидации файлов на бекэнде с использованием различных браузеров? Ведь в зависимости от "воспроизводилки" баги будут разные, а кое у кого их не будет вообще. А так поставил зоопарк браузеров и валидируй сразу на всех.
alexsphera Автор
Да, мы думали про «зоопарк браузеров», но основной пойнт задачи был в другом — отсеять битые Lottie ещё на фронте, чтобы вообще не дёргать бэкенд.
Во-первых, это экономит ресурсы (и наши, и клиента), во-вторых, даёт мгновенный фидбэк пользователю, что файл кривой.
А массовую проверку в разных окружениях можно будет докрутить, если придёт много анимаций от сторонних команд.