Strona główna / Artykuły

Testowanie ścieżki awarii: Next.js, Remix i przestarzałe dane autoryzacji

Przypadek studialny pokazujący, w jaki sposób błąd związany ze starymi uprawnieniami wpływa na decyzję między Next.js a Remix, oraz jak oceniać frameworki pod kątem mutacji, czasu trwania pamięci cache i możliwości debugowania.

Wydawca: REACTAPP.TOP9 min czytania2058 słów

Źródła: Next.js vs Remix in 2026: The Faster Page Won Every Benchmark. One Stale Permission Changed Our Decision.

Porównania frameworków zazwyczaj oceniają idealne warunki: nowy przeglądarka, dane już załadowane oraz strona, która ładuje się raz. Systemy produkcyjne są oceniane w inny sposób, w momencie po zmianie danych. Ten przypadek badawczy opisuje zespół, który stworzył tę samą zalogowaną stronę w Next.js oraz w architekturze typu Remix, zaobserwował, że Next.js wygrywa we wszystkich pomiarach wydajności, a następnie zmienił decyzję z powodu jednej cofniętej uprawnień, która nie chciała zniknąć. Na końcu otrzymasz praktyczny sposób oceny frameworków renderujących pod kątem błędów, których twoje produkty naprawdę sobie nie mogą pozwolić.

Jasny zwycięzca pod względem wydajności

Porównanie wydajności nie było bliskie. Wersja Next.js wysyłała mniej JavaScript do przeglądarki, szybciej generowała pierwszy użyteczny widok, przechowywała kosztowne operacje na serwerze i dzięki cache’owi sprawiała, że ponawiane nawigacje wydawały się natychmiastowe.

Następnie administrator został usunięty z konta klienta. Zarówno baza danych, jak i ślad audytowy odzwierciedlały to usunięcie, a ponowne załadowanie strony pokazywało właściwe uprawnienia. Jednak gdy dotknięty użytkownik wrócił na ekran konta za pomocą nawigacji w aplikacji, kontrolki administracyjne znów się pojawiły. Przyciski, które powinny były zniknąć, ponownie były widoczne.

API serwerowej części odrzucało każdą uprzywilejowaną akcję, więc żadne dane się nie zmieniły i nie doszło do żadnego incydentu bezpieczeństwa. To właśnie ta weryfikacja po stronie serwera jest powodem, dla którego autoryzacja musi być zawsze sprawdzana na poziomie API, a nie wywnioskowywana na podstawie tego, co pokazuje interfejs użytkownika. Jednak ta ochrona nie uratowała decyzji architektonicznej.

W budowie w stylu Remix inżynierowie w ciągu kilku minut zlokalizowali wszystkie miejsca, gdzie mogły przetrwać przestarzałe dane. W budowie Next.js potrzeba było kilku osób, dwóch sprzecznych śladów oraz jednego przypadkowego pełnego załadunku strony, aby ustalić, która warstwa wygenerowała odpowiedź. To dochodzenie odwróciło orzeczenie.

Ekran zbudowany dwa razy

Omawiana strona nie była czymś błahym. Służyła wielu użytkownikom i umożliwiała administratorom zarządzanie osobami, ich rolami, kluczami API, określanie, kto ma dostęp do informacji o fakturach, oraz przeglądanie wydarzeń związanych z kontem. Część ruchu stanowiło czytanie, natomiast kluczowe operacje to były zapisy: cofanie roli, rotacja klucza, usuwanie użytkownika, zmiana uprawnień do przeglądania faktur.

Trzy wymagania pociągały w różnych kierunkach:

  • Szybkie początkowe renderowanie, nawet w przypadku kont z długą historią działań.
  • Minimalny JavaScript w przeglądarce, ponieważ użytkownicy korzystali z wolnych maszyn firmowych.
  • Zmiany uprawnień, które są wyświetlane niezawodnie bez względu na to, w jaki sposób użytkownik dostaje się do strony.
  • W Next.js większość treści na ekranie była renderowana przez komponenty serwerowe, a małe komponenty klienckie służyły wyłącznie do interaktywnych elementów. Dane kont używane w kilku nawarstwionych układach przepływały przez pamięć cache, aby uniknąć ich wielokrotnego pobierania, a udane zapisy powodowały ponowną weryfikację.

    Wersja w stylu Remix opierała się na ładowaczach dla poszczególnych tras służących do odczytu danych oraz na akcjach do ich zapisu, dodając standardowe nagłówki cache HTTP tylko wtedy, gdy znano bezpieczny czas trwania operacji. Gdy akcja została zakończona, ładowacze dla dotkniętych tras uruchamiano ponownie, zgodnie z sekwencją na tyle prostą, że można ją było narysować na tablicy. (W chwili pisania ten model ładowaczy i akcji nadal jest używany w trybie frameworkowym React Router, do którego połączono Remix; sprawdź aktualną dokumentację pod kątem nazewnictwa.)

    Oba rozwiązania były solidne i żadne z nich nie zostało stworzone tak, by przegrać. W idealnych warunkach Next.js wyraźnie zwyciężył, co ujawniło pierwszą wadę testu: rzeczywisty ruch nie ogranicza się do idealnych warunków.

    Gdzie komponenty serwerowe komplikują sytuację

    Komponenty serwerowe spełniły to, co obiecywały. Sekcje tylko do odczytu nie wymagały kodu klienta, pobieranie danych odbywało się bezpośrednio obok komponentów, które je wykorzystywały, powolne elementy wyświetlały się stopniowo bez blokowania całej aplikacji, a stabilne dane konta zapobiegały wielokrotnym wyjazdom do serwera źródłowego.

    Ceną była konieczność składania pojedynczej widocznej strony z kilku niezależnych elementów:

    • Prace ograniczone do jednej renderizacji na serwerze.
    • Dane wykorzystywane ponownie dzięki buforowaniu po stronie serwera.
    • Stan routera przechowywany w przeglądarce podczas różnych nawigacji klienta.
    • Kilka instancji serwera, z których każda posiada własną kopię zbuforowanych wartości.
    • Anulowanie stanu po dokonaniu zmiany.
    • Każdy proxy lub CDN znajdujący się przed aplikacją.

    To, która warstwa przechowywała przestarzałą wartość, nie miało znaczenia dla użytkownika, ale było pilne dla osoby pełniącej dyżur. Jeden z zapisów pokazywał, że baza danych zwracała zaktualizowaną rolę; drugi pokazywał, że interfejs uprawnień wyświetlał tę poprzednią. Żadne z nich nie było błędne – każde rejestrowało inne żądanie, które przechodziło inną ścieżką w systemie.

    Rozwój lokalny nigdy nie odtwarzał tego problemu. Jeden proces zarządzał całym cache’em, inżynierowie aktualizowali dane po każdej zmianie, a konta testowe nigdy nie postępowały zgodnie z sekwencją rzeczywistego klienta, który trzymał aplikację otwartą przed zmianą uprawnień. Poprzez eliminację powtarzającej się pracy projekt wyeliminował również wszelkie proste sposoby na określenie, która warstwa dostarczała daną wartość.

    Dokładne zachowanie każdej warstwy cache w Next.js, np. czas, przez jaki router klienta przechowuje wcześniej pobrane segmenty, uległo zmianie pomiędzy głównymi wersjami. To jest część lekcji: jeśli twoja poprawność zależy od tych ustawień domyślnych, zapisz tę zależność i sprawdzaj ją przy każdej aktualizacji.

    Dlaczego prawdziwym wskazówką był ponowny załadunek

    Zespół wsparcia stwierdził, że przymusowe odświeżenie rozwiązywało problem. Wydaje się to wskazywać na rolę cache przeglądarki, jednak przeglądarka wyjaśniła tylko niewielką część tego zjawiska. Odświeżenie wysyłało do serwera zupełnie nowe żądanie, które zazwyczaj trafiało do instancji bez przestarzałej kopii. Z kolei nawigacja klienta ponownie wykorzystywała ścieżkę z innymi danymi i inną historią unieważnienia. Ponieważ strona wyglądała poprawnie po każdym odświeżeniu, błąd znikał za każdym razem, gdy ktoś na nią patrzył.

    Monitorowanie syntetyczne ponownie ujawniło ten sam słaby punkt. Każda weryfikacja rozpoczynała się od nowej sesji i potwierdzała, że użytkownik z nową rolą widzi właściwą stronę. Nikt nie przeprowadził przejścia od starych uprawnień do nowych w ramach istniejącej sesji. Zespół monitorował stany, podczas gdy błąd istniał pomiędzy nimi.

    Dzięki temu spostrzeżeniu odtwarzanie problemu stało się wiarygodne:

    1. Otwórz ekran konta, aby był serwowany przez jedną instancję aplikacji.
    2. Zmień rolę użytkownika z innej sesji.
    3. Wróć na ten ekran za pomocą nawigacji klienta, bez ponownego ładowania.

    W komponentach nie było żadnych błędów. Zapis w pamięci podręcznej funkcjonował dokładnie zgodnie z ustawieniami; określony czas trwania po prostu nie był odpowiedni dla danych autoryzacyjnych. Określanie tego jako „błędu przestarzałej pamięci podręcznej” sugeruje błąd w kodzie. Prawdziwym problemem była niewyrażona gwarancja architektoniczna: w jakim czasie uprawnienie cofnięte musi zniknąć ze wszystkiego, co użytkownik może zobaczyć?

    Pierwsza poprawka niemal przeniosła awarię na bazę danych

    Najszybszym sposobem łagodzenia skutków było wyłączenie pamięci podręcznej w całej sekcji konta. Przestarzałe uprawnienia zniknęły, a obciążenie bazy danych gwałtownie wzrosło.

    Układy i panele nawzajem powiązane żądały nakładających się danych konta. Pamięć podręczna sprawiała, że te dublowane odczyty wydawały się bezkosztowe; ich usunięcie przekształciło incydent związanym z uprawnieniami w rosnące czasy oczekiwania na połączenia z PostgreSQL, co zagroziło innym stronom konta.

    Dyskusje na temat frameworków zwykle pomijają dokładnie ten punkt. Cache może stanowić zagrożenie dla poprawności działania, a jednocześnie być elementem zapewniającym wydajność. Wyłączenie go przywraca świeżość danych, jednocześnie eliminując założenie, które utrzymywało bazę danych w dobrym stanie.

    Odbudowa odbywała się krok po kroku, w mniejszych i bezpieczniejszych etapach:

    • Dane uprawnień były unieważniane indywidualnie dla każdego konta i wersji uprawnień, a nie globalnie.
    • Czytania związane z autoryzacją były realizowane w ramach konkretnego żądania, dzięki czemu nigdy nie trwały dłużej niż to żądanie.
    • Duplikatowe odczyty były usuwane przed rozszerzeniem zakresu dostępu do niecacheowanych ścieżek.
    • Niewielka część ruchu była najpierw przetwarzana według nowego sposobu działania, przy jednoczesnym monitorowaniu czasu oczekiwania w puli zasobów.
    • Każda odpowiedź zawierała teraz mały znacznik wskazujący, w jaki sposób została wygenerowana, która instancja jej dostarczyła, w jakiej wersji znajdowały się dane oraz czy odpowiedź pochodziła z cache’u.

    Ten marker zmienił dalsze badania. Możliwość identyfikacji źródła odpowiedzi nie wymagała ujawniania samych danych dotyczących uprawnień, a inżynierowie nie musieli już zgadywać ścieżki wykonywania z powodu ewentualnego odświeżenia. Jest to tanie rozwiązanie dla każdego systemu posiadającego kilka warstw cache’owania.

    Dlaczego wolniejsza architektura okazała się lepsza w tym produkcie

    Budowa w stylu Remix nie była odporna na rozproszony stan. Cache’y istnieją w przeglądarkach i CDN, bazy danych uruchamiają repliki, a każda poważna aplikacja w końcu boryka się ze starymi danymi, częściowymi awariami i koniecznością ponownych prób.

    Jego możliwości były bardziej ograniczone. Triada ładowania, działania i ponownej walidacji dała zespołowi zwięzły model mentalny tego, co dzieje się po zapisaniu danych. Gdy jakieś działanie zmieniało rolę, inżynierowie mogli określić ścieżki, których dane musiały zostać odświeżone, oraz wiedzieli, gdzie je potwierdzić. Granice żądań pokrywały się z operatorami śledzenia, których już używano. Aplikacja pobierała niektóre dane dwukrotnie i wymagała dodatkowego kodu w przeglądarce dla określonych elementów interfejsu, ale jej nieprawidłowe zachowanie można było szybko zidentyfikować.

    Dlatego pasuje to do tego konkretnego produktu, co nie czyni go uniwersalnym. Na stronie, gdzie dominuje treść, której dane rzadko się zmieniają, a kluczową rolę odgrywa optymalizacja pracy przeglądarki, architektura Next.js pozostaje lepszą opcją; jej możliwości renderowania i cache’owania mogą zapewnić znacznie lepsze doświadczenie użytkownika. W przypadku narzędzia operacyjnego wymagającego autoryzacji, widoczna po odwołaniu upoważnienie kosztuje więcej niż zaobserwowana różnica w wydajności. Ważniejsze były możliwość śledzenia zmian oraz przewidywalność aktualności danych.

    Testy nie były błędne – po prostu odpowiedziały na niekompletne pytanie.

    Odbudowa oceny wokół problemów

    Druga ocena nie rozpoczynała się od czystej przeglądarki i już załadowanych danych. Rozpoczęła się od sytuacji, które pierwszy test wyeliminował:

    • Zmienia się uprawnienie, podczas gdy ekran pozostaje otwarty. Czy wszystkie drogi dostępu do strony pokazują tę zmianę w wyznaczonym czasie, a inżynier może sprawdzić, która wersja jest na ekranie?
    • Instancja aplikacji znika w trakcie działania. Czy użytkownik może bezpiecznie spróbować ponownie, a częściowa praca nie pozostawia mylącego stanu klienta?
    • Kasowanie z pamięci podręcznej jest wyłączone w godzinach szczytu ruchu. Czy baza danych może przetrwać najprostsze, pilne rozwiązanie?
    • Dwie wersje aplikacji obsługują ruch podczas wdrażania. Czy obie potrafią interpretować dane z pamięci podręcznej oraz odpowiedzi po mutacji?
    • Dział obsługi klienta odtwarza jedną ścieżkę użytkownika. Czy mogą określić, w jaki sposób została wyświetlona strona, która pamięć podręczna i instancja udzieliły odpowiedzi oraz która wersja danych się pojawiła, wszystko to bez debugowania serwerów w czasie rzeczywistym?

    Next.js zachowywał wyższą szybkość na kilku kluczowych ścieżkach. Model w stylu Remix ułatwiał zespołowi rozumienie sytuacji, gdy te ścieżki przestały działać prawidłowo. W tym momencie zespół porównywał systemy, a nie tylko ekranorysy.

    Decyzja na podstawie możliwych błędów strony

    Ograniczenie wyboru do „Next.js jest szybki, Remix jest prosty” nie przetrwa kontaktu z środowiskiem produkcyjnym. Zanim zmierzymy wydajność, odpowiedz na następujące pytania:

    • Które dane produkt może serwować przestarzałe i przez jak długi czas?
    • Czy mutacje wpływają na finanse, uprawnienia, zapasy czy tylko na wygląd?
    • Ile granic cacheowania i renderowania może zidentyfikować zespół na podstawie jednego raportu klienta?
    • Czy baza danych będzie funkcjonować, jeśli usunięto cache?
    • Jak zachowują się dwie wdrożone wersje obok siebie?

    Następnie zmierz szybkość działania na „szczęśliwej ścieżce”.

    Zespół doszedł do podzielonej decyzji. Next.js pozostał w użyciu na stronach, gdzie komponenty serwerowe zmniejszały obciążenie klienta i każdy element danych miał jasno określony, bezpieczny czas trwania. Często zmieniane dane uprawnień były celowo utrzymywane w prostocie: dostępne tylko na czas konkretnego żądania, nigdy nie przechowywane w pamięci cache. Natomiast dla ekranu operacji, gdzie poprawne przetwarzanie zmian miało większe znaczenie niż szybka dostawa statycznych treści, wybrano podejście Remix. Jeśli decydujesz się na Next.js dla takich ekranów, metoda unieważniania na podstawie tagów, opisana w dyrektywie use cache oraz ponownej walidacji na podstawie tagów, zapewnia lepszą kontrolę, a sprawdzanie uprawnień w każdej akcji serwerowej pozostaje zabezpieczeniem niezależnie od tego, co pokazuje interfejs.

    Główne wnioski

    • Benchmark rozpoczynający się od nowej sesji nie może wykryć błędów istniejących pomiędzy stanami; sprawdzaj przejścia między stanami w sesjach długotrwałych.
    • Traktuj dane autoryzacyjne jako odrębną klasę: ograniczoną do zakresu żądania, wersjonowaną i wyraźnie unieważnianą.
    • Pamięć cache może przenosić rzeczywisty obciążenie; zaplanuj, jak ją usunąć, zanim zmusi cię do tego incydent.
    • Uczyń źródło każdej odpowiedzi widocznym za pomocą lekkiego znacznika wskazującego tryb renderowania, instancję, wersję oraz stan cache.
    • Wybierz framework, którego tryby awarii twoja zespół potrafi szybko wyjaśnić. Jeden z opcji może dostarczyć więcej narzędzi do przyspieszenia pracy, a drugi ułatwić diagnozę błędnych stron. W przypadku niektórych produktów to druga cecha jest prawdziwym wymogiem.

    Literatura pokrewna