Кейс из жизни: мини-приложения, анимированные обложки, внешние команды — и одна на вид «валидная» анимация, которая кладет все приложение. Рассказываем, как мы научились воспринимать 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.

Мы встроили поведенческую валидацию в наш процесс: ловим ошибки на рендере, до отправки на бэкенд. Получили стабильный фронт, предсказуемый интерфейс, уменьшили количество падений и лишней поддержки.

Если у вас в проекте анимации приходят от сторонних команд или загружаются динамически — не полагайтесь только на схему. Тестируйте в бою.

Это простое улучшение может избавить от десятков сложных и “невоспроизводимых” проблем.

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


  1. oldd
    13.08.2025 11:10

    Не рассматривали способ валидации файлов на бекэнде с использованием различных браузеров? Ведь в зависимости от "воспроизводилки" баги будут разные, а кое у кого их не будет вообще. А так поставил зоопарк браузеров и валидируй сразу на всех.


    1. alexsphera Автор
      13.08.2025 11:10

      Да, мы думали про «зоопарк браузеров», но основной пойнт задачи был в другом — отсеять битые Lottie ещё на фронте, чтобы вообще не дёргать бэкенд.

      Во-первых, это экономит ресурсы (и наши, и клиента), во-вторых, даёт мгновенный фидбэк пользователю, что файл кривой.

      А массовую проверку в разных окружениях можно будет докрутить, если придёт много анимаций от сторонних команд.