26 proc. dobrych LCP w metryce stabilnej, 87 proc. w eksperymentalnej. Ten sam SPA, inna definicja nawigacji
CrUX publikuje już LCP, INP i CLS z miękkimi nawigacjami. Jak zmierzyć je per trasa w SPA przed oddaniem projektu i którą liczbę wpisać do odbioru.
W październikowym wydaniu CrUX serwisy, w których od 75 do 100 proc. odsłon na Androidzie to zmiany trasy po stronie klienta, mają 26 proc. udziału w kategorii „dobre LCP” liczonego bez miękkich nawigacji i 87 proc. po ich doliczeniu. Ten sam kod, ten sam ruch, dwie liczby. Różni je wyłącznie to, co przeglądarka uznaje za nawigację.
Dla kogoś, kto oddaje klientowi aplikację jednostronicową, to nie jest ciekawostka ze zmian w API. To pytanie, pod którą liczbę podpisujesz protokół odbioru, bo przez najbliższe miesiące w obiegu będą obie, w różnych narzędziach, i będą się od siebie różnić o kilkadziesiąt punktów procentowych.
Dlaczego stary pomiar karał każdą zmianę trasy
Do niedawna wszystkie trzy metryki Core Web Vitals były przypięte do jednego wczytania dokumentu. Zmiana adresu przez history.pushState() nie resetowała niczego: metryka dostawała adres, pod którym użytkownik rozpoczął wczytywanie strony, a nie ten, który faktycznie oglądał.
Konsekwencje układały się w jedną stronę, zawsze na niekorzyść aplikacji jednostronicowej:
- LCP mierzył wyłącznie pierwsze wejście. Szósty widok, do którego użytkownik doszedł klikając, nie miał własnego LCP — nie miał go w ogóle.
- CLS narastał przez całe życie dokumentu. Przeskoki z pięciu kolejnych widoków sumowały się w jedną wartość przypisaną do adresu wejściowego.
- INP obejmował wszystkie interakcje w sesji, więc im dłużej ktoś korzystał z aplikacji, tym większa szansa, że jedno kliknięcie zepsuje wynik całości.
Serwis wielostronicowy dostawał czystą kartę przy każdym przejściu. Aplikacja jednostronicowa niosła cały bagaż na jednym adresie. Stąd luka 26 wobec 87 proc. — nie z tego, że te aplikacje nagle przyspieszyły.
Trzy warunki, które przeglądarka musi zobaczyć naraz
Chrome 151 wprowadził wpis PerformanceSoftNavigation. Pojawia się on, gdy zachodzą równocześnie trzy rzeczy: zaufana interakcja użytkownika, wynikający z niej widoczny contentful paint oraz zmiana adresu w pasku przeglądarki. Wykrywanie siedzi w przeglądarce, nie w aplikacji, więc nie ma znaczenia, czy framework zmienia adres na początku, czy na końcu obsługi przejścia.
Definicja jest wąska i to ona rozstrzyga, czy wygenerowany projekt dostanie sensowne dane. Dwa przypadki brzegowe, które warto sprawdzić w swoim kodzie, zanim zaufasz wynikom:
- Fałszywe trafienia. Komponent zakładek albo filtr listy, który dopisuje parametr do adresu i przerysowuje fragment widoku, spełnia wszystkie trzy warunki. Przeglądarka policzy go jako nawigację, choć użytkownik nie zmienił strony. Generatory chętnie robią zakładki na routerze, więc liczba nawigacji w raporcie bywa wyższa niż liczba realnych widoków.
- Przeoczenia. Przejście wywołane przekierowaniem po zalogowaniu, licznikiem czasu albo kodem bez bezpośredniej interakcji nie spełnia pierwszego warunku i nie zostanie policzone. Dokumentacja biblioteki
web-vitalswymienia zarówno fałszywe trafienia, jak i przeoczenia jako znaną cechę tej heurystyki, nie jako błąd do obejścia.
Co dokładnie zeruje się przy miękkiej nawigacji
Po włączeniu raportowania miękkich nawigacji metryki przestają być kumulacją z całego dokumentu, ale każda zachowuje się inaczej. Zestawienie poniżej jest przepisane z dokumentacji biblioteki web-vitals.
| Metryka | Zachowanie po miękkiej nawigacji | Co z tego wynika dla wygenerowanego widoku |
|---|---|---|
| TTFB | Raportowany jako 0, nie jako czas pierwszego żądania po zmianie trasy | Nie da się z niego nic wyczytać o pobieraniu danych do nowego widoku; potrzebny osobny pomiar |
| FCP i LCP | Pierwszy i największy contentful paint po nawigacji; elementy przeniesione z poprzedniego widoku nie są kandydatami, bo nie zostały przerysowane | Jeśli layout trzyma duży obraz nagłówkowy wspólny dla tras, LCP trasy policzy mniejszy element z obszaru treści |
| INP | Licznik zerowany, obejmuje wyłącznie interakcje po nawigacji | Ciężki JavaScript w samym przejściu trasy nadal wchodzi do wyniku — przesuwa się tylko to, do której trasy |
| CLS | Zerowany, mierzony osobno od odsłony wejściowej | Przeskok przy dociąganiu danych widać na tej trasie, która go powoduje, a nie na stronie startowej |
Drugi wiersz tej tabeli jest najmniej oczywisty i najczęściej przestawia wnioski. Aplikacja, w której przy zmianie trasy przerysowuje się tylko kolumna treści, dostanie LCP liczony z tej kolumny. Wartość może być świetna, a użytkownik i tak czeka — bo czeka na coś, czego ta metryka nie obejmuje.
Którą liczbę zobaczy klient i w jakim narzędziu
Eksperymentalne metryki nie zastąpiły stabilnych, tylko stanęły obok nich. Stabilne wyniki Core Web Vitals pozostają niezmienione, dopóki warianty eksperymentalne są oceniane, a to oznacza dwa równoległe obiegi danych.
| Co | Metryka stabilna | Metryka z miękkimi nawigacjami |
|---|---|---|
| Nazwy pól w CrUX | largest_contentful_paint, interaction_to_next_paint, cumulative_layout_shift |
experimental_largest_contentful_paint_with_softnavs, experimental_interaction_to_next_paint_with_softnavs, experimental_cumulative_layout_shift_with_softnavs |
| Gdzie dostępna | Wszędzie, w tym Search Console i PageSpeed Insights | Zbiór BigQuery, CrUX API, CrUX History API i CrUX Vis |
| Przypisanie do adresu | Adres, pod którym rozpoczęło się wczytywanie dokumentu | Adres, do którego użytkownik przeszedł |
| Status | Obowiązująca | Eksperymentalna, może zmieniać się między wydaniami |
Dwie daty, które warto mieć w kalendarzu projektu. Chrome celuje w wyjście tych metryk ze statusu eksperymentalnego w maju 2027, a Search Console przejdzie na nowe dane po tym przejściu. W listopadowym wydaniu CrUX mają dojść eksperymentalne metryki dla typów nawigacji i dla FCP. Do maja 2027 raport, który klient otwiera sam, pokazuje starą liczbę — i to ona jest podstawą rozmowy o odbiorze, nawet jeśli uważasz ją za niesprawiedliwą wobec architektury projektu. Dwie poprawne liczby dla jednego wskaźnika to nie przypadłość tej metryki — ta sama rozbieżność powstaje wszędzie, gdzie zmienia się mianownik, a nazwa wskaźnika zostaje bez zmian.
Pomiar per trasa, zanim dane z CrUX w ogóle się pojawią
CrUX nie pokaże nic dla serwisu bez wystarczającego ruchu, a świeżo oddany projekt takiego ruchu nie ma. Pomiar przed oddaniem robi się więc u siebie. Najkrótsza droga to biblioteka web-vitals — w wersji 6.0.0 lub nowszej obsługuje opcję reportSoftNavs dla onLCP, onFCP, onCLS i onINP.
import {onCLS, onINP, onLCP} from 'web-vitals';
function wyslij(metric) {
const payload = {
nazwa: metric.name,
wartosc: metric.value,
typ: metric.navigationType,
adres: metric.navigationURL,
nawigacja: metric.navigationId,
interakcja: metric.navigationInteractionId,
};
navigator.sendBeacon('/rum', JSON.stringify(payload));
}
onLCP(wyslij, {reportSoftNavs: true});
onCLS(wyslij, {reportSoftNavs: true});
onINP(wyslij, {reportSoftNavs: true});
Trzy pola z tego ładunku decydują o tym, czy raport będzie czytelny. Dokumentacja biblioteki opisuje je wprost:
navigationTypema wartość'soft-navigation'dla miękkich nawigacji. To jedyny pewny sposób odfiltrowania ich od zwykłych wczytań tego samego adresu.navigationURLto adres, którego metryka dotyczy. Trzeba użyć tego pola, a nielocation.href, bo metryki raportują się z opóźnieniem i w momencie wysyłki użytkownik może być już na innej trasie.navigationIdwiąże metrykę z konkretną nawigacją i potrafi wskazywać poprzedni adres, anavigationInteractionId— interakcję, która nawigację wywołała.
Jest jeden skutek uboczny włączenia tej opcji, który łatwo przeoczyć przy porównywaniu wyników sprzed i po zmianie. Flaga zmienia także pomiar samego wczytania wejściowego: metryki adresu początkowego są domykane w chwili pierwszej miękkiej nawigacji, co zamraża CLS i INP tej odsłony. Liczby dla strony startowej zwykle wyjdą lepiej niż wcześniej, bo przestają zbierać przeskoki z dalszej części sesji. To nie jest poprawa wydajności, tylko zmiana zakresu pomiaru, i w porównaniu „przed i po” należy to rozliczyć osobno.
Odczyt z surowych wpisów, gdy biblioteka nie wystarcza
Jeśli potrzebny jest pomiar poza progami Core Web Vitals — na przykład czas od kliknięcia w menu do pierwszej treści nowego widoku — trzeba zejść do wpisów soft-navigation i interaction-contentful-paint. Tu jest szczegół, na którym da się zbudować cały błędny raport: wpisy paintu dopasowuje się do nawigacji przez interactionId, a nie przez navigationId. Paint, który poszedł na ekran przed wywołaniem history.pushState(), nosi jeszcze poprzedni navigationId i przy dopasowaniu po tym polu wypadnie z wyniku.
let aktywnaNawigacja = null;
let kandydatLcp = null;
new PerformanceObserver((list) => {
for (const wpis of list.getEntries()) {
aktywnaNawigacja = wpis;
// paint sprzed zmiany adresu trzeba dociągnąć z samego wpisu nawigacji
kandydatLcp = wpis.getLargestInteractionContentfulPaint();
}
}).observe({type: 'soft-navigation', buffered: true});
new PerformanceObserver((list) => {
for (const wpis of list.getEntries()) {
if (aktywnaNawigacja && wpis.interactionId === aktywnaNawigacja.interactionId) {
kandydatLcp = wpis;
}
}
}).observe({type: 'interaction-contentful-paint', buffered: true});
Wynik domyka się przy następnej miękkiej nawigacji albo gdy document.visibilityState przechodzi w 'hidden'. Opóźnienie trasy czyta się z pola duration, które jest różnicą presentationTime i startTime, czyli czasem od początku interakcji do pikseli na ekranie — nie z samego presentationTime.
Dwa ograniczenia tego odczytu, oba udokumentowane. Pierwsze: bufor przeglądarki trzyma tylko pierwszych 50 wpisów soft-navigation, więc performance.getEntriesByType() nie nadaje się do aplikacji, w której ktoś siedzi godzinę — trzeba obserwatora. Drugie: rejestrację obserwatorów trzeba osłonić sprawdzeniem PerformanceObserver.supportedEntryTypes, bo w przeglądarce bez obsługi observe() rzuci wyjątkiem i zabierze ze sobą resztę skryptu pomiarowego.
Dane z dwóch silników nie składają się w jedną średnią
Te wpisy są dostępne wyłącznie w Chromium od wersji 151. MDN opisuje PerformanceSoftNavigation jako funkcję o ograniczonej dostępności, oznaczoną jako eksperymentalna, a dokumentacja biblioteki web-vitals dodaje, że przeglądarka bez obsługi raportuje tak jak wcześniej — nawet z włączoną flagą.
Praktyczny skutek jest nieprzyjemny: jeden strumień RUM miesza dwa różne sposoby liczenia tego samego wskaźnika. Średnia LCP z całego ruchu nie znaczy nic, bo ruch z Safari i Firefoksa wnosi do niej wartości z pierwszego wczytania, a ruch z Chrome — wartości per trasa. Segmentacja po silniku przeglądarki przestaje być dobrą praktyką i staje się warunkiem, żeby liczba cokolwiek znaczyła.
Ile ten problem realnie waży, zależy od tego, jak bardzo projekt opiera się na miękkich nawigacjach. Około 70 proc. serwisów ma takiego ruchu mniej niż 5 proc. odsłon, a powyżej połowy odsłon wychodzi 7,0 proc. serwisów na Androidzie i 10,6 proc. na desktopie; w tysiącu największych serwisów to już 35 proc. na Androidzie i 42 proc. na desktopie. Zwykła strona firmowa z osobnymi adresami nie zauważy zmiany. Panel klienta napisany na routerze — zauważy całą.
Co wpisać do protokołu odbioru
Lista do przejścia przed oddaniem projektu, w tej kolejności. Jeśli projekt przechodzi przez kilka osób w zespole, ta lista powinna wisieć pod spisaną procedurą przekazania zadania, a nie powstawać od zera przy każdym zleceniu:
- Ustal, czy projekt w ogóle robi miękkie nawigacje: policz wpisy
soft-navigationw typowej ścieżce użytkownika i porównaj z liczbą tras, przez które przeszedł. Rozbieżność w górę to zakładki i filtry udające nawigację. - Zmierz każdą trasę osobno, nie tylko wejściową. Trasa, do której użytkownik dochodzi po dwóch kliknięciach, w starym pomiarze nie istniała i nikt nigdy nie sprawdził jej LCP.
- Sprawdź, co jest kandydatem na LCP na trasach wewnętrznych. Jeśli duży element nagłówkowy jest wspólny dla układu, metryka mierzy coś mniejszego, niż użytkownik widzi jako treść główną.
- Zapisz w protokole, z której metryki pochodzą liczby i w jakiej przeglądarce były zbierane. Bez tego porównania po trzech miesiącach nie da się odtworzyć.
- Oddzielnie zapisz progi, które klient będzie czytał w Search Console — to nadal metryka stabilna, nie ta z pliku z pomiarami.
- Wpisz do kalendarza utrzymania przegląd na maj 2027. Jeśli metryki wyjdą wtedy z eksperymentu, raport klienta zmieni się bez żadnego wdrożenia z waszej strony.
Decyzja, którą trzeba podjąć teraz, dotyczy jednego zdania w umowie albo w protokole: czy odbiór projektu jest rozliczany z metryki, którą klient widzi dzisiaj w Search Console, czy z pomiaru per trasa, który odpowiada architekturze tego, co mu oddajesz. Oba warianty są obronialne. Niezapisanie żadnego oznacza, że przy pierwszej rozmowie o wydajności każda strona przyniesie własną liczbę i obie będą prawdziwe.