Kolofon · 7 sierpnia 2026
Biała strona — ten sam pakiet w dwóch kopiach
zgłoszone słowami „mryga zawartość i od razu potem pusta strona”

Tekst: Kolofon
Ta strona przestała działać. Nie na chwilę, nie dla części czytelników — dla wszystkich, na każdym urządzeniu. Objaw był podręcznikowo mylący: treść pojawiała się na ułamek sekundy i znikała, zostawiając biel.
Rozłóżmy to na czynniki, bo taki objaw czyta się jak zdanie. Serwer renderował stronę poprawnie — kompletny HTML, cała treść, stopka. Dlatego mrygała. Potem React próbował przejąć gotowy dokument i wywalał się z wyjątkiem. A komponent obsługi błędów, chcąc pomóc, przeładowywał stronę. Kolejny render, kolejny wyjątek, kolejne przeładowanie. Pętla wyglądająca jak pusta strona.
Pierwsze podejrzenia padły na wszystko, co zmieniłem tego dnia: nowy podpis w stopce, przebudowany układ wpisu, nowa seria z dwunastoma wpisami. Sprawdziłem po kolei: zasoby zwracają 200, moduły przechodzą kontrolę składni, treść jest poprawnie wkompilowana, HTML z serwera kompletny. Wszystko czyste. Śledztwo stanęło.
Odblokowała je jedna linijka z konsoli przeglądarki: `Cannot read properties of undefined (reading 'get')`, a przy niej ślad stosu wskazujący na `stores.matchesId.get()` — wnętrze frameworka, nie mój kod. To zdanie mówi coś bardzo konkretnego: obiekt routera istnieje, ale nie ma w nim pola, którego framework się po nim spodziewa.
Sposób, w jaki obiekt może nie mieć pola wymaganego przez własną bibliotekę, jest w zasadzie jeden: to nie jest ta sama biblioteka. W paczce siedziały DWIE kopie `@tanstack/react-router` — wersje 1.170.18 i 1.170.20. Jedna tworzyła instancję routera, druga próbowała z niej czytać. Każda z nich miała rację co do swojego kontraktu. Kontrakty się rozjechały.
Przyczyna źródłowa jest banalna do wstydu: ta instancja nie miała pliku blokady zależności. Zakresy wersji z daszkiem rozwiązywały się od nowa przy każdym budowaniu, więc paczka zależała od tego, co dostawcy bibliotek wypuścili danego dnia. Wystarczyło, że jedna zależność przechodnia poprosiła o odrobinę nowszy zakres niż druga — i menedżer pakietów, zamiast wybrać jedną wersję, uczciwie zainstalował obie.
Naprawa zajęła mniej czasu niż diagnoza: wersje przypięte na dokładnie te, na których stoi działające wdrożenie, plus reguły wymuszające pojedynczą kopię routera w całym drzewie zależności. Przypięcie samego szczytu nie wystarcza — zależności przechodnie i tak dociągnęłyby drugą.
Zostaje morał ostrzejszy, niż bym chciał. Brak pliku blokady figurował na mojej liście jako drobiazg z kategorii „warto, bo przyspieszy budowanie”. W rzeczywistości to nie jest optymalizacja, tylko jedyna rzecz, która sprawia, że dwa buildy tego samego commita dają ten sam wynik. Bez niego produkcja może się wywrócić w nocy, bez żadnej zmiany po twojej stronie, a objaw nie będzie w najmniejszym stopniu wskazywał na zależności.
Nowa pozycja w checkliście wdrożeniowej, na samej górze: instancja bez pliku blokady nie jest gotowa do wdrożenia.
Kolofon