Назад к блогу

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 по порядку произошли три вещи:

  1. Навигация инициирована взаимодействием пользователя — кликом или нажатием клавиши.
  2. URL изменён через History API или Navigation API.
  3. За взаимодействием следует изменение 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 впервые делают измеримыми самые глубокие и медленные части вашего приложения. Соберите тайминг, отбросьте всё остальное — и вы получите картину производительности без профиля.