С самого начала HTML поддерживает потоковую передачу, поэтому вы можете вернуть страницу в виде потока, и браузеры будут отображать ее по частям, и страница будет медленно появляться на странице.
Недавно я обнаружил одно интересное применение стриминга, о котором раньше и не подозревал.
В чем проблема?
- Нашему приложению требовалось сделать массив тяжелых запросов для самой первой загрузки страницы. Эти запросы блокировали любой контент страницы.
- Мы не хотели использовать рендеринг на стороне сервера, потому что преимущества в производительности были сомнительными, а техническая сложность правильной настройки с архитектурой микроинтерфейса была громоздкой, особенно с учетом сроков.
Наивное решение по умолчанию
Для приложения рендеринга на стороне клиента мы могли бы переместить все вызовы API непосредственно в клиент, чтобы следующие 2 шага отображались в последовательном порядке для каждой загрузки страницы:
Шаг 1. скачать файлы JS/CSS
Шаг 2. когда они будут готовы, выполните необходимые вызовы API из клиента
TTI = задержка + время (шаг 1) + задержка + время (шаг 2) + рендеринг
Желаемое решение
Я хотел избежать этой двойной задержки и даже использовать распараллеливание. По сути, мы должны иметь возможность загружать файлы JS/CSS параллельно с вызовом API, сохраняя рендеринг на стороне клиента. TTI может быть значительно увеличен, если мы активируем это и будем измерять следующим образом:
TTI = Задержка + МАКС(Время(Шаг 1), Время(Шаг 2))+ Рендеринг
Алгоритм следующий:
- Начать потоковую передачу HTML
В начале потока HTML должны быть теги<script defer src="script.js">
и<link href="styles.css">
, чтобы браузер начал загрузку ресурсов как можно скорее.
Отложитьатрибут важен, чтобы не останавливать синтаксический анализ HTML и выполнять скрипты только после того, как страница будет полностью отрисована при сохранении порядка. - Преобразование HTML на лету
Затем бессерверная функция должна передавать данные, поступающие от API, непосредственно в HTML.
В случае ответа JSON данные могут быть внедрено в теги<script type="application/json">
. - Закрыть поток
Когда поток закрыт, DOMContentLoaded будет запущен, и загруженные файлы JS/CSS начнут рендеринг.
В этом решении мы добились желаемого параллелизма, сохраняя при этом рендеринг на стороне клиента.
Бессерверная функция
Самая сложная часть здесь — код бессерверной функции. По сути, функция должна поддерживать следующее:
- Перехватывать все запросы к странице
- Делайте вызовы API параллельно и передавайте их обратно в HTML
Наш выбор пал на Cloudflare@worker с его замечательным HTMLRewriter API. Это позволяет вам асинхронно изменять HTML, а затем передавать его обратно.
addEventListener('fetch', async (event: FetchEvent) => { const request = await fetch(event.request); const response = new HTMLRewriter() .on('#config-1', new Config1()) .on('#config-1', new Config2()) .transform(request); event.respondWith(response); });
Хотя AWS@lambda и AWS@edge не имеют встроенной поддержки потоковой передачи, ее все же можно реализовать с помощью собственного JS.
Это решение было протестировано в продакшне, и прирост производительности был очень заметен, он выглядел даже лучше, чем то, что мы имели с SSR.