Po każdym wdrożeniu część użytkowników widzi biały ekran
SPA na S3 i CloudFront. Po każdym deployu przez kilkanaście minut Sentry zbiera setki błędów ChunkLoadError i „Failed to fetch dynamically imported module”. Użytkownicy z otwartą kartą klikają w link i dostają pusty ekran. Zespół proponuje deployować tylko w nocy.
Zanim rozwiniesz: powiedz na głos, jak byś to rozwiązał, i nazwij co najmniej jeden kompromis.
Moje rozwiązanie
Zapisuje się samo w tej przeglądarce. Kopię zrobisz na stronie Notatki.
Pokaż, czego szuka rekruter, i modelową odpowiedź
Co sprawdza rekruter
- Czy rozumiesz, skąd biorą się błędy ładowania chunków po wdrożeniu
- Czy znasz strategię atomowego wdrożenia statycznych plików
- Czy myślisz o obu stronach: infrastrukturze i obsłudze w aplikacji
Odpowiedź seniora
Przyczyna: stara wersja aplikacji w otwartej karcie próbuje pobrać leniwy chunk z hashem, który nowy deploy usunął z bucketa. Wdrożenie usuwające stare pliki nie jest atomowe z punktu widzenia klienta.
Naprawa infrastruktury: assety z hashem w nazwie nigdy nie są kasowane przy deployu, tylko dokładane do tego samego bucketa (albo prefiksu z wersją), a sprzątanie robi lifecycle rule po np. 30 dniach. index.html ma Cache-Control: no-cache i jest podmieniany jako ostatni krok, po wgraniu wszystkich chunków. Invalidation w CloudFront dotyczy tylko index.html.
Naprawa w aplikacji: globalna obsługa błędu importu dynamicznego (handler w routerze lub wrapper na lazy), która raz przeładowuje stronę z flagą w sessionStorage, żeby nie wpaść w pętlę. Dodatkowo aplikacja może co jakiś czas sprawdzać plik version.json i proponować odświeżenie przy nawigacji. Mierzę liczbę ChunkLoadError na deploy w Sentry jako wskaźnik regresji.
Typowe wpadki
- Deploy w nocy jako rozwiązanie
- Kasowanie bucketa i wgrywanie od zera przy każdym wdrożeniu
- Automatyczny reload bez zabezpieczenia przed pętlą przeładowań
- Długi cache na index.html
Dopytają o
- Jak zrobisz rollback, skoro stare pliki zostają w buckecie?
- Co z service workerem, który ma własny cache?
- Jak to wygląda przy Module Federation, gdzie remote deployuje się osobno?