Single Page Application (SPA) характеризуются тем, что переход между «страницами» внутри приложения не сопровождается полной перезагрузкой документа. Такой подход улучшает пользовательский опыт за счёт скорости, но создаёт особенности для метрик Web Vitals, которые традиционно рассчитываются на полной загрузке страницы.
Мягкая навигация — это изменение состояния приложения (маршрута, контента) без полной перезагрузки окна браузера. В SPA она реализуется через:
pushState,
replaceState, popstate)Главная особенность мягкой навигации — DOM изменяется динамически,
поэтому стандартные события загрузки (load,
DOMContentLoaded) не срабатывают повторно. Метрики Web
Vitals (LCP, CLS, FID) требуют отдельного внимания, чтобы корректно
измерять показатели на каждой новой «странице».
import { getCLS, getFID, getLCP, getTTFB, getFCP } from 'web-vitals';
Эти функции принимают колбэк, который вызывается при изменении соответствующей метрики:
getLCP(metric => {
console.log('LCP:', metric.value);
});
В SPA это нужно делать не только один раз при загрузке приложения, но и при каждой мягкой навигации.
Largest Contentful Paint (LCP) измеряет время, когда на экране появляется наибольший элемент контента.
В SPA после перехода на новый маршрут элемент LCP может появиться через некоторое время, поэтому требуется перезапуск измерения LCP на каждом новом маршруте. Например:
function trackLCP() {
let lcpMetric;
getLCP(metric => {
lcpMetric = metric;
console.log('LCP текущей страницы:', lcpMetric.value);
});
return () => {
// Очистка наблюдателей перед новой навигацией
lcpMetric = null;
};
}
// Использование с маршрутизатором
router.afterEach(() => {
const stopTracking = trackLCP();
// Опционально: остановка через таймаут
setTimeout(stopTracking, 5000);
});
Cumulative Layout Shift (CLS) оценивает визуальные сдвиги элементов. В SPA CLS может накапливаться не только на загрузке, но и при динамических вставках контента после мягкой навигации.
web-vitals поддерживает передачу
reportAllChanges:getCLS(metric => {
console.log('CLS текущей страницы:', metric.value);
}, { reportAllChanges: true });
Таким образом можно получать отдельные CLS-значения для каждой «страницы», а не суммарные по всей сессии.
First Input Delay (FID) и Interaction to Next Paint (INP) зависят от интерактивности страницы. При мягкой навигации:
getFID(metric => {
console.log('FID новой страницы:', metric.value);
});
Для SPA часто используют комбинацию getFID и
getINP, чтобы учитывать все пользовательские взаимодействия
после динамической загрузки контента.
Пример для React Router:
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { getLCP, getCLS, getFID } from 'web-vitals';
export default function WebVitalsTracker() {
const location = useLocation();
useEffect(() => {
const stopLCP = getLCP(metric => console.log('LCP:', metric.value));
const stopCLS = getCLS(metric => console.log('CLS:', metric.value));
getFID(metric => console.log('FID:', metric.value));
return () => {
stopLCP();
stopCLS();
};
}, [location.pathname]);
return null;
}
location.pathname наблюдатели
LCP и CLS перезапускаются.web-vitals.getLCP и
getCLS заново.Такой подход обеспечивает точное измерение Core Web Vitals в приложениях с мягкой навигацией и позволяет выявлять проблемы производительности на каждом этапе пользовательского пути.