Rzemiosło · The Craft
Czym jest The CraftPoziomyJak zacząćRozdziały Szukaj Pobierz PLEN
Level 3 · Kodeks ~3 min czytania

W jednym zdaniu: Najpierw mierz, potem optymalizuj: indeksy, koniec z N+1, streaming — szybkość udowodniona liczbami.

Ten rozdział po ludzku

Ten rozdział ma jedno motto: wydajność to pomiar, nie przeczucie. „Wydaje się szybciej” nie istnieje — istnieją liczby przed i po. Najpierw mierzysz, co jest wolne, poprawiasz to, i dowodzisz liczbą, że pomogło. Inaczej „optymalizacja” bywa regresją w przebraniu.

Po stronie przeglądarki liczy się to, co użytkownik odczuwa jako „szybko”: nie ładuj obrazków, których nie widać, podawaj zdjęcia w lekkim formacie (WebP), a całość mierz narzędziem Lighthouse (wbudowanym w Chrome) i metrykami Core Web Vitals.

Po stronie bazy najczęstszy zabójca to brak indeksu (skorowidza) na często odpytywanej kolumnie — baza przegląda wtedy całą tabelę zamiast zajrzeć pod hasło. Drugi to N+1 — Błąd wydajności: aplikacja odpytuje bazę 200 razy zamiast raz, bo pyta osobno o każdy element. Najczęstsza przyczyna „mulącej” listy. Jak 200 telefonów zamiast jednej prośby o całą listę.: dwieście osobnych zapytań tam, gdzie wystarczyłoby jedno.

Sporo miejsca zajmuje telefon, bo jest podchwytliwy: pasek adresu i klawiatura zmieniają wysokość ekranu w locie. Stąd 100dvh zamiast 100vh i mierzenie realnego widoku — inaczej przyciski chowają się pod klawiaturą.

Przykład N+1: lista 200 produktów, gdzie kod pyta bazę o cenę osobno dla każdego, to 200 telefonów zamiast jednej prośby o całą listę. Efekt: „mulący” katalog, który po dodaniu jednego zapytania zbiorczego rusza od ręki.

Doczytaj: indeks bazodanowy · problem N+1 · dvh a mobilny viewport.

13 — Wydajność: frontend i SQL

Przykazanie III w czystej postaci: weryfikuj, nie deklaruj — zastosowane do szybkości. „Wydaje się szybciej” nie istnieje. Istnieją liczby przed/po.

Wydajność to nie przeczucie, to pomiar. Najczęstszy błąd optymalizacji: zgadywanie, co jest wolne, i „poprawianie” tego bez dowodu, że było problemem. Mierz najpierw, popraw to, co metryka wskazuje, i udowodnij liczbą, że poprawiłeś. Wydajność to też SEO (Core Web Vitals — Miary od Google: jak szybko strona się pokazuje, jak stabilnie się układa i jak szybko reaguje. Wpływają na pozycję w Google i na to, czy użytkownik nie ucieknie. Mierzalne, więc da się je poprawiać.10) i koszt (szybsze zapytanie = tańszy serwer → 12).

Mierz najpierw

  • Lighthouse / Core Web Vitals: LCP (largest contentful paint), CLS (layout shift), INP (interaction), TBT. To są twarde liczby, nie wrażenia.
  • Przed/po, nie „wydaje się szybciej”. Np. optymalizacja home: preload hero, GPU-promoted animacje, content-visibility: auto poniżej folda; raportujesz LCP przed i po, nie „chyba lżej”. → 03
  • SQL — Język zapytań do bazy danych — sposób, w jaki „pytasz” bazę o dane albo je zmieniasz. Uniwersalny standard rozmowy z bazą. Źle napisane zapytanie potrafi zamulić całą aplikację.: EXPLAIN QUERY PLAN mówi, czy zapytanie używa indeksu, czy skanuje całą tabelę.

Frontend

  • Code-splitting + lazy-load below-fold — nie ładuj tego, czego user nie widzi.
  • Obrazy: WebP (np. konwersja wszystkich do WebP q=95), responsive srcset, preload hero (LCP), reszta lazy.
  • Fonty: self-host (np. Playfair/Outfit, jak na jakub.solutions), font-display: swap — tekst widoczny zanim font dojdzie.
  • content-visibility: auto na sekcjach poniżej folda — przeglądarka pomija render niewidocznego.
  • GPU-promoted animacje (translateZ(0)/transform) zamiast layoutujących właściwości.
  • Cache — Tymczasowo zapamiętany wynik, żeby nie liczyć tego samego od nowa przy każdym pytaniu. Przyspiesza aplikację, ale bywa pułapką: nieświeży cache pokazuje stare dane. statyków: immutable + długi max-age na prodzie (np. 7 dni immutable prod).
  • Cache-busting per deploy: skoro CSS/JS są immutable, doklej ?v=<git-short-hash> do każdego linku — każdy deploy zmienia URL → świeży pobór. Bez tego user widzi starą apkę (rozjechany layout) aż do twardego odświeżenia (→ 05).
  • Streaming — Wysyłanie dużej odpowiedzi kawałkami, na bieżąco, zamiast ładowania wszystkiego naraz do pamięci. Aplikacja nie zatyka się przy wielkich danych — użytkownik widzi wynik szybciej. / SSE dla czatu — odpowiedź LLM token-by-token (np. przez SSE), user widzi pierwsze słowa od razu, a nie pustkę do końca generacji. → 08
  • Server-side pagination — nigdy nie ślij całego katalogu do przeglądarki (np. paginacja po stronie serwera dla ~kilku tysięcy pozycji).

Mobile: viewport to ruchomy cel

Na telefonie viewport nie jest stałym prostokątem: pasek URL rośnie i kurczy się przy scrollu, a klawiatura ekranowa zjada dolną połowę. Potraktuj go jako statyczny, a dostajesz elementy schowane za klawiaturą albo wysoki pusty pas tam, gdzie był pasek URL. Mierz realny viewport — nie zakładaj go.

  • Używaj jednostki dynamicznej. 100dvh (dynamic viewport height) na pełnoekranowy panel — nie 100vh (to największy viewport i wchodzi pod pasek URL). Zadeklaruj najpierw height: 100vh jako fallback, potem height: 100dvh jako override — kolejność właściwości ma znaczenie, stary silnik trzyma ostatnią regułę, którą rozumie.
  • Elementy „przyklejone” nad klawiaturą kotwicz do visualViewport, nie do layout viewport. Input czatu albo sticky CTA na position: fixed; bottom: 0 jest mierzony względem layout viewport i wjeżdża pod klawiaturę. Jedyną pewną kotwicą jest API — Umówiony sposób, w jaki dwa programy gadają ze sobą — jeden prosi, drugi odpowiada w ustalonym formacie. Przez API Twoja aplikacja łączy się z cudzymi usługami (płatności, mapy, AI). Klucz API traktuj jak hasło. visualViewport (.height/.offsetTop, odczyt na jego resize/scroll) — zasłania różnice zachowania klawiatury iOS vs Android. Skacz do nowego rozmiaru; nie animuj otwarcia klawiatury (transition/transform na evencie klawiatury daje widoczny skok).
  • flex-basis ustawiony dla wiersza staje się WYSOKOŚCIĄ w kolumnie. flex: 1 1 220px to podpowiedź szerokości w wierszu; ta sama reguła pod breakpointem flex-direction: column robi pusty box wysoki na 220px. Zresetuj basis (flex: 0 0 auto) na breakpoincie, gdzie oś główna się obraca.
  • Emulator kłamie; DOM i urządzenie nie. Device-mode desktopowej przeglądarki renderuje elementy dvh/fixed inaczej niż realny telefon — nie „naprawiaj” layoutu ze screenshota. Mierz getBoundingClientRect()/getComputedStyle() na realnych elementach i trzymaj projekt testowy na profilu urządzenia (np. osobny projekt Playwright przypięty do telefonu), który co przebieg sprawdza geometrię: brak poziomego overflow, taby w jednej linii, kluczowy element nad foldem. Weryfikuj, nie deklaruj (→ 03).

SQL

  • Indeks (bazodanowy) — Skorowidz w bazie danych, dzięki któremu wyszukiwanie idzie błyskawicznie, zamiast przeglądać wszystko po kolei. Pierwszy ruch przy wolnych zapytaniach — jak indeks na końcu książki zamiast czytania 400 stron. na kolumnach z WHERE i JOIN — gorące zapytanie bez indeksu to skan całej tabeli.
  • Partial indexes — np. uniq_prices_active … WHERE expired_at IS NULL (indeks tylko na aktywnych cenach — mniejszy, szybszy, wymusza unikalność, → 11).
  • EXPLAIN QUERY PLAN przed i po dodaniu indeksu — dowód, że plan się zmienił.
  • Unikaj N+1 — Błąd wydajności: aplikacja odpytuje bazę 200 razy zamiast raz, bo pyta osobno o każdy element. Najczęstsza przyczyna „mulącej” listy. Jak 200 telefonów zamiast jednej prośby o całą listę. — nie zapytanie w pętli po każdym wierszu; batch/join za jednym razem.
  • SELECT tylko potrzebnych kolumn — nie SELECT *, gdy potrzebujesz trzech pól.
  • Paginacja server-side + cache’owane agregaty (np. site_stats zamiast COUNT(*) po całej bazie na każde wejście na home).
  • WAL — Write-Ahead Log: tryb bazy, w którym zmiany najpierw trafiają do dziennika, a potem do danych. Daje bezpieczeństwo (odtwarzalność po awarii) i lepszą równoczesność odczytu/zapisu. (SQLite — Najprostsza baza danych — cała mieszka w jednym pliku, bez osobnego serwera. Świetny default na start: zero konfiguracji, łatwy backup (kopiujesz plik). Przy wzroście migrujesz wyżej.) — czytelnicy nie blokują pisarza; domyślny tryb w projekcie referencyjnym.
  • Ostrożnie z LIKE '%foo' — wiodący wildcard zabija indeks (full scan); rozważ FTS, jeśli to gorąca ścieżka wyszukiwania.

Anty-wzorce

  • 🚫 Optymalizacja bez pomiaru — „poprawiłem”, nie wiedząc, czy było wolne (→ 03).
  • 🚫 Brak indeksu na gorącym zapytaniu — najczęstsza przyczyna wolnej strony katalogu.
  • 🚫 N+1 w pętli — 200 zapytań tam, gdzie wystarczył jeden join.
  • 🚫 SELECT * — przesyłasz i deserializujesz kolumny, których nie używasz.
  • 🚫 Client-side pagination ogromnych zbiorów — ślesz kilka tysięcy rekordów, by pokazać 20.
  • 🚫 Blokowanie UI w oczekiwaniu na pełną odpowiedź LLM zamiast streamingu (→ 12).
  • 🚫 Brak cache drogich agregatówCOUNT(*) po całej bazie na każde żądanie.
  • 🚫 100vh na pełnoekranowy panel mobilny — dół chowa się pod paskiem URL/klawiaturą (użyj dvh + visualViewport).
  • 🚫 position: fixed; bottom: 0 na input przyklejony nad klawiaturą — wjeżdża pod klawiaturę (kotwicz do visualViewport).
  • 🚫 flex-basis przeniesiony z wiersza w breakpoint kolumny — wysoki pusty box (zresetuj basis, gdzie oś się obraca).
  • 🚫 „Naprawianie” mobile ze screenshota z desktopowego emulatora — naprawiasz artefakt; mierz realną geometrię DOM / testuj na profilu urządzenia.

Dla nowych projektów

Wpisz do Dnia 0 (→ 07): baseline Lighthouse zaraz po pierwszej działającej stronie (masz punkt odniesienia), indeksy na kolumnach filtrów od pierwszej Migracja (bazy danych) — Kontrolowana zmiana układu bazy — dodanie kolumny, tabeli czy przeniesienie danych — krok po kroku. Jak remont według planu: przebudowa danych w ustalonej kolejności, żeby nic się nie „zawaliło”., EXPLAIN QUERY PLAN jako nawyk przy każdym gorącym zapytaniu. Reguła nadrzędna: żadna optymalizacja bez liczby przed i po — bo bez dowodu „optymalizacja” bywa regresją w przebraniu (→ 03). Szybkość to jednocześnie SEO (→ 10) i koszt infrastruktury (→ 12) — jedna inwestycja, trzy zwroty.

Treść doktryny prowadzimy po polsku.