Soft Navigations: измеряем производительность SPA так, как это делает браузер
Эвристики soft navigation в Chrome наконец позволяют Core Web Vitals привязываться к клиентским сменам маршрута. Разбираем, как работает API и как измерять это без слежки.
Десять лет Core Web Vitals описывали только первую страницу, которую загружал посетитель. Любая последующая смена маршрута в single-page application оставалась невидимой для метрик. Эксперимент с soft navigations в Chrome закрывает этот пробел и меняет то, как аналитика, ориентированная на приватность, должна измерять реальную производительность пользователей.
Слепое пятно, которое устраняют soft navigations
Традиционная hard navigation выгружает документ и загружает новый. Браузер сбрасывает свою временную шкалу производительности, генерирует новый LCP и начинает считать CLS и INP с нуля. Полевые инструменты вроде CrUX относят всё к этому единственному URL.
SPA работают иначе. После первоначальной загрузки React Router, App Router в Next.js или SvelteKit подменяют контент на месте через History API. Ни один документ не выгружается, поэтому новая временная шкала производительности не начинается. Пользователь может пройти через десять «страниц», а каждый Core Web Vital останется привязанным к входному URL.
Результат знаком всем, кто проводил аудит SPA: целевой маршрут выглядит быстрым, а медленные взаимодействия в глубине приложения никогда не попадают в данные.
Как Chrome обнаруживает soft navigation
Эвристика Chrome требует, чтобы перед регистрацией soft navigation по порядку произошли три вещи:
- Навигация инициирована взаимодействием пользователя — кликом или нажатием клавиши.
- URL изменён через History API или Navigation API.
- За взаимодействием следует изменение DOM, затрагивающее уже существующий элемент DOM.
Эта последовательность намеренно строгая. Фоновый pushState для аналитики, автоматически прокручивающаяся карусель или смена URL без взаимодействия не подойдут. Эта точность важна: она означает, что soft navigation соответствует тому, что человек действительно сделал, а не случайной активности скриптов.
Поверхность API
Soft navigations доступны через стандартный PerformanceObserver с помощью отдельного типа записи. Подключение — на уровне observer:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime);
}
}).observe({ type: "soft-navigation", buffered: true });
Что ещё важнее, другие записи производительности получают navigationId. Записи LCP, layout-shift и event-timing, выданные после soft navigation, несут новый ID, поэтому можно переотнести каждый Core Web Vital к тому маршруту, на котором пользователь действительно находился:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// entry.navigationId связывает этот LCP с конкретной soft navigation
send({ metric: "LCP", value: entry.startTime, navId: entry.navigationId });
}
}).observe({ type: "largest-contentful-paint", buffered: true });
Сегодня всё это живёт за флагом chrome://flags/#soft-navigation-heuristics и origin trial, с ограниченным присутствием в CrUX. В стабильном Chrome это пока не поведение по умолчанию, поэтому относитесь к полевым цифрам как к ориентировочным, а не авторитетным. Библиотека web-vitals отдаёт те же данные через опцию reportSoftNavs — это практичный способ всё подключить, не писать observer вручную.
Почему это место в аналитике, ориентированной на приватность
Данные soft navigation — это чистый тайминг. В них нет идентификатора, нет cookie и ничего, что переживёт сессию страницы: длительность, грубый тип элемента, путь маршрута. Это ровно тот сигнал, который инструмент без cookie может собирать, не трогая механику согласия.
Трекер уже подключается к history.pushState, чтобы считать смены маршрута в SPA. Soft navigations уточняют тот же хук: вместо вопроса только сменился ли маршрут? можно спросить была ли это навигация, вызванная взаимодействием, и какова её производительность?. Записи производительности едут рядом с существующим маяком pageview к /collect, добавляя полевое измерение, не утяжеляя скрипт менее 2 КБ и не ломая модель идентичности на основе ежедневного хеша.
Ловушка, которой стоит избегать, — воспринимать soft navigations как повод собирать больше. Некоторые поставщики RUM используют новую атрибуцию, чтобы сшивать пользовательские маршруты навигации в рамках сессии — именно тот паттерн слежки, ради отказа от которого и существует аналитика без cookie. Метрика ценна именно тем, что может оставаться агрегированной: медианный INP по маршруту, распределение LCP по маршруту, без пути обратно к человеку.
Soft navigations впервые делают измеримыми самые глубокие и медленные части вашего приложения. Соберите тайминг, отбросьте всё остальное — и вы получите картину производительности без профиля.