Каждая миллисекунда имеет значение, когда кто-то собирается вам заплатить. Когда пользователь попадает на страницу оплаты, он принимает молниеносное решение еще до того, как нажмет кнопку «Оформить заказ». Выглядит ли это безопасным? Законно ли это? Это решение начинается с того, как быстро загружается страница. Пользователи начинают задаваться вопросом, защищен ли их платеж, реален ли бизнес, стоящий за этой страницей, и получат ли они то, за что заплатили.
Скорость — это не пустой показатель. Это первый сигнал доверия, который получает ваш клиент, а для платежных ссылок он, пожалуй, самый важный. Мы переработали механизм отрисовки платежных ссылок Moov, и результаты говорят сами за себя:
На компьютерах мы теперь достигаем FCP 0.6 с, Speed Index 0.6 с, LCP 0.9 с и CLS 0.019. Это практически мгновенно для полностью интерактивной платежной формы. Вот скриншот наших результатов Lighthouse для десктопов.
Вот что мы изменили и почему.
Проблема
Наши платежные ссылки представляли собой SPA на React. Сервер возвращал минимальную оболочку index.html с JSON-данными, браузер загружал JavaScript-бандл, React начинал отрисовку, данные запрашивались, и только после этого пользователь наконец видел платежную форму. Общий размер незакэшированной загрузки по сети составлял 4.2 МБ.
Для страницы, чья единственная задача — принять платеж, такая архитектура работала против нас. Каждый байт и каждый сетевой запрос стояли между клиентом и кнопкой «Оплатить».
В отчете Portent говорится, что у интернет-магазина, который загружается за 1 секунду, коэффициент конверсии в 5 раз выше, чем у того, который загружается за 10 секунд. Это серьезный плюс.
Решение: серверный рендеринг с гидратацией на стороне клиента
Мы перенесли рендеринг из браузера на сервер. Вместо того чтобы отправлять пустую оболочку и тяжелый JS-бандл, мы теперь возвращаем полностью отрисованный HTML из вспомогательного SSR-сервиса на базе Bun.
Поток запросов выглядит следующим образом:
- Запрос поступает на наш сервер
- Сервер получает данные платежной ссылки
- SSR-сервис на базе Bun рендерит дерево компонентов React в HTML
- Сформированная страница возвращается в первом ответе
- React на стороне клиента выполняет гидратацию для обеспечения интерактивности
Браузер получает что-то для немедленной отрисовки вместо того, чтобы смотреть на пустую страницу во время загрузки, синтаксического анализа и выполнения JavaScript.
Почему Bun?
Мы оценили несколько подходов: SSR на базе Node, рендеринг на границе сети (edge) и статическую прегенерацию. победил благодаря своей простоте и высокой скорости. Встроенная поддержка JSX, нативная поддержка TypeScript и быстрое время запуска сделали его идеальным выбором для вспомогательного сервиса, которому необходимо рендерить компоненты React при каждом запросе без накладных расходов полноценной среды выполнения Node. И нам не потребовалось менять систему сборки или переписывать какой-либо код. Он просто делает то, что нам нужно, и делает это быстро, потребляя меньше памяти, чем Node.
Что помогло сдвинуть дело с мертвой точки
Миграция на SSR стала главным изменением, но несколько факторов в совокупности позволили нам сократить FCP на мобильных устройствах с 11 с до 2.2 с, а на десктопах — до 0.6 с:
- Устранение водопада рендеринга. В модели SPA браузер должен был сначала загрузить JS → выполнить JS → запросить данные → выполнить рендеринг. При SSR запрос данных и рендеринг происходят на сервере за один проход. Первый ответ браузера уже содержит готовую страницу.
- Меньший объем исходных данных. Сервер возвращает HTML, который браузер может отрисовать немедленно, а не приложение JavaScript. Бандл гидратации составляет лишь малую часть от исходного SPA-бандла, поскольку ему нужно только привязать обработчики событий к существующему DOM, а не перестраивать его с нуля.
- Более умное разбиение на чанки и ленивая загрузка. У нас всегда были разбиение на чанки, ленивая загрузка и удаление мертвого кода, но мы смогли улучшить это, более агрессивно разбивая кодовую базу и загружая только тот код, который необходим для текущего маршрута. Например, мы смогли удалить код страницы выплаты из бандла страницы оплаты и наоборот.
- Отсутствие сдвигов макета. Поскольку сервер рендерит финальный макет, страница не дергается по мере монтажа компонентов и прибытия данных. Наш показатель CLS отражает это.
И, конечно же, дизайн страницы играет большую роль. Мы загружаем высококачественный логотип компании как можно быстрее, используя атрибуты loading="eager" и decoding="async". Позиции заказа и сумма платежа видны сразу, и единственное, что остается загрузить — это платежную форму, которой нужны подсказки от браузера, например, следует ли показывать Apple Pay.
Небольшое отступление об изображениях продуктов. Мы масштабируем изображения до нужного размера в 2x на экранах с высокой плотностью пикселей и, в зависимости от добавленного количества и класса размера загружающего страницу браузера, загружаем соответствующее количество изображений, обязательно откладывая загрузку тех, которые не видны на экране.
Небольшое отступление об изображениях продуктов. Мы масштабируем изображения до нужного размера в 2x на экранах с высокой плотностью пикселей и, в зависимости от добавленного количества и класса размера загружающего страницу браузера, загружаем соответствующее количество изображений, обязательно откладывая загрузку тех, которые не видны на экране.
Цена решения
SSR не дается даром. Теперь мы запускаем вспомогательный сервис, который должен работать стабильно и быстро. Если SSR-сервис работает медленно, каждая загрузка страницы происходит медленно, так как нет клиентского резервного варианта, скрывающего задержку. Чтобы сервис работал быстро, мы запускаем бэкенд-сервис на Go и контейнеры SSR-сервиса Bun в тех же подах Kubernetes, сокращая сетевые накладные расходы. SSR-сервис отвечает только за прием данных от бэкенд-сервиса для создания дерева компонентов React и отдачу статических ресурсов.
Результаты
Мобильные показатели говорят сами за себя. Мы сократили показатель First Contentful Paint на 80%, а LCP — на 74%. На десктопах FCP упал до 0.6 секунды. Это достаточно быстро, чтобы страница казалась уже загруженной.
Что касается конкретно платежных ссылок, скорость — это не приятное доп. свойство. Это разница между завершенной транзакцией и брошенной. Если ваш процесс оформления заказа загружается на телефоне 16 секунд, какая-то часть этих клиентов уходит.
Мы считаем, что программное обеспечение должно быть красивым и быстрым. Мы еще не закончили, но мы гордимся тем прогрессом, которого уже добились.
Хотите создавать продукты вместе с нами? Присоединяйтесь к нашей команде.










