Język / Language
PLPolskiENEnglish

Case study · Sport i technologia

Polska Liga Koszykówki.

Nowy serwis PLK.PL - trzy aplikacje, kompletne archiwum rozgrywek, wyniki na żywo i dane sportowe gotowe na największy ruch w dniu kolejki.

3aplikacje w jednym repozytorium
71widoków publicznych
146endpointów API
60 sodświeżanie wyników live
84wydania produkcyjne
~68 tys.linii kodu aplikacyjnego
01

Sytuacja wyjściowa

Polska Liga Koszykówki miała serwis działający od kilkunastu lat: aktualności, terminarz, tabele, statystyki, profile drużyn i zawodników, galerie oraz wideo. Był centralnym źródłem informacji o rozgrywkach, ale technologicznie należał do innej epoki.

Baza pod stary CMS

Polskie nazwy tabel i kolumn, statusy jako nieopisane liczby, historycznie rozjechane relacje tagów i surowy HTML z FCKeditora - bez dokumentacji schematu i typów.

Brak przyjaznych adresów i SEO

Aktualności, wideo i galerie nie miały slugów. Istniały wyłącznie pod identyfikatorami liczbowymi.

Dane wyłącznie z ESOR

Terminarz, składy, statystyki, boxscore'y i przebieg meczów pochodziły z zewnętrznego, nietypowego API bez warstwy pośredniej.

Skokowy ruch

W dniu kolejki ruch rośnie gwałtownie, a wyniki muszą pozostać świeże. Odpytanie źródła przy każdym wejściu nie mogło się skalować.

Archaiczny panel redakcyjny

Aktualności, galerie, wideo, bannery, partnerzy i strony statyczne były zarządzane w przestarzałym narzędziu.

02

Zakres projektu

Przepisaliśmy frontend, API i panel redakcyjny na tej samej produkcyjnej bazie danych. Nowe encje zostały zmapowane na historyczne tabele, dlatego uruchomienie nie wymagało jednorazowej, ryzykownej migracji całego archiwum.

Trzy aplikacje wdrażane jednym procesem:

AplikacjaZakres
Frontend (Next.js)Publiczny serwis PLK.PL - 71 widoków i 159 komponentów.
Backend (NestJS)146 endpointów REST, 68 modułów, 187 DTO i dokumentacja OpenAPI.
Panel (Angular)59 komponentów do redakcji, mediów, bannerów, partnerów i konfiguracji.

Moduły serwisu:

ModułZakres
Wyniki na żywoGlobalny pasek trwających meczów z kwartą, czasem i wynikiem.
RozgrywkiTerminarz, filtry lig i kolejek, tabele, U23 oraz eksport .ics.
MeczBoxscore, statystyki, akcja po akcji, mapa rzutów, rekordy, multimedia i PDF.
Drużyny i zawodnicyProfile, składy, terminarze, historia kariery, statystyki, rekordy i wiadomości.
Statystyki i rekordyRankingi indywidualne i drużynowe, wielopoziomowe sortowanie i eksport CSV.
Archiwum sezonówPełne drzewo widoków w kontekście dowolnego historycznego sezonu.
Aktualności i multimediaKategorie, tagi, galerie, lightbox i wideo importowane z YouTube.
WyszukiwarkaPełny tekst treści redakcyjnych oraz drużyny i zawodnicy w systemie ligowym.
Play-offDrabinka, głosowanie kibiców, ranking typów i generowana grafika do udostępnienia.
KomercjalizacjaRotowane bannery, zliczanie przekierowań, promo-boxy, partnerzy i newsletter.
Treści statyczneStrony redakcyjne chronione przed kolizją z trasami serwisu.
03

Integracja z systemem ligowym ESOR

Każdy wynik, tabela i statystyka pochodzą z ESOR - systemu Polskiego Związku Koszykówki. Źródła nie dało się zmienić, a serwis nie mógł być wolniejszy ani mniej dostępny od niego.

Co zastaliśmy

Jedno wejście API, formularzowe żądania POST, operacja przekazywana w treści, brak wersjonowania, typów i kontraktu. Odpowiedzi odzwierciedlały wewnętrzny system ewidencji, nie potrzeby serwisu.

Co zrobiliśmy

Własna warstwa domenowa

13 modułów dla lig, sezonów, kolejek, meczów, drużyn, zawodników, trenerów, tabel, statystyk, hal i wyszukiwania. Frontend nie widzi surowych odpowiedzi ESOR.

Cache jako architektura

Każde zapytanie ma deterministyczny klucz i zdefiniowany czas życia w memcached, z kontrolowanym odświeżeniem i możliwością ominięcia cache.

Odświeżanie wyprzedzające

Ciężkie zapytania są odbudowywane co 4 minuty, a mecze live co minutę - zanim użytkownik ich zażąda.

Świadomość klastra

Tylko instancja zerowa odświeża dane, więc zwiększanie liczby procesów nie zwiększa ruchu do ESOR.

Drugi bufor w serwisie

Regeneracja przyrostowa co 60 lub 300 sekund daje niezależną warstwę ochronną między kibicem a backendem.

Serwis pozostaje szybki nawet przy obciążonym źródle, a nowe widoki statystyk korzystają z gotowych, otypowanych modułów zamiast bezpośrednich wywołań obcego API.

04

Wyniki na żywo

Serwis ligowy jest oceniany w dniu kolejki. Wynik meczu musi być widoczny natychmiast na każdej podstronie, niezależnie od liczby kibiców obserwujących go w tej samej minucie.

Pobieranie po stronie serwera

API odpytuje ESOR o trwające mecze co 60 sekund z pominięciem cache. Przeglądarki nie wykonują żadnego pollingu.

Jedna instancja odpytuje

W klastrze wyłącznie instancja zerowa pobiera świeży stan, a wszystkie procesy go serwują.

Jeden model meczu

Dane live są scalane z terminarzem: flaga trwania, kwarta, zegar, wynik i transmisja trafiają do wspólnego modelu.

Pasek w globalnym layoucie

Trwające mecze i tabela towarzyszą kibicowi w artykule, profilu zawodnika, terminarzu i archiwum.

Interfejs prowadzący do meczu

Karuzela wskazuje mecz live, używa pulsującego znacznika i rozwija się do pełnego widoku kolejki oraz tabeli.

Koszt wyników live nie rośnie wraz z ruchem. Dziesiątki tysięcy sesji nadal oznaczają dokładnie jedno zapytanie na minutę do systemu ligowego.

05

Design system

PLK.PL powstał na dedykowanym design systemie rozwijanym i wersjonowanym razem z aplikacją. Projekt był systemem, z którego składają się widoki, a nie zbiorem niezależnych makiet.

Kolor - nazwane role zamiast wartości

Skala akcentu obsługuje pełny cykl interakcji, powierzchnie mają zdefiniowane poziomy głębi, a kolory sygnalne przekazują status bez czytania.

accent red 1#FDE1CBikony na ciemnym tle
accent red 2#F88D65obwódka fokusu
accent red 3#E81E04akcent podstawowy, live
accent red 4#A70209najechanie
accent red 5#6F0017wciśnięcie
drk 0#090718tło najgłębsze
drk 1#151324sekcje ciemne
drk 2#1E1C2Bprzycisk podstawowy
drk 3#292733stan najechania
lg 0#FFFFFFkarty, pola formularzy
lg 1#FDFDFDstan wciśnięcia
lg 2#F8F8F8tło sekcji, cień karty
text primary#0E0C1Dnagłówki
text tertiary#292733etykiety, podpowiedzi
support#4A4661tekst nieaktywny
border darker#F0F0F0ramka na jasnym tle
border darkest#ABA6C1przełączniki
disabled#DDDCE1element wyłączony
stats#3A3843siatka tabel statystyk
accent yellow 1#E8B604czas gry, odliczanie
accent green 1#4ECF00status pozytywny

Typografia - osobno do czytania i do liczb

Trzy rodziny krojów: jedna do treści oraz dwie o stałej szerokości znaku do wyników, czasu gry i tabel statystycznych.

Treść - Inter

Kolejny komplet punktów w hali przy Kolejowej

Nagłówki, zapowiedzi meczów, artykuły i nawigacja. Krój pozostaje czytelny w małych etykietach i gęstych układach tabel.

400 · 500 · 700 · 800

Dane - Roboto Mono / Geist Mono

89:76
Kwarta 402:14
ZawodnikPKTZBASEVAL
A. Kowalski247528
M. Nowak1811225
P. Zieliński93817
stała szerokość znaku - cyfry układają się w kolumnach

Komponenty - jedna implementacja na cały serwis

Warianty, rozmiary, stany fokusu i wyłączenia wynikają z systemu, a nie z lokalnych nadpisań.

Średnia na mecz
LiveGospodarze - Goście89 : 76Kwarta 4 · 02:14

Detale systemu

Promienie, cienie, obwódki fokusu i ruch również są tokenami.

Promień narożnika0.125rem
Cień karty0 2px 0 #F8F8F8
Obwódka fokusuring 2px #F88D65
Gradient kafla medialnego180° → #0E0C1D
Wskaźnik ładowanialoaderLine 1s linear

159 komponentów zachowuje się jak jeden produkt na 71 widokach - również w funkcjach dodanych po premierze, takich jak drabinka play-off i tabela U23.

06

Praca na żywej bazie produkcyjnej

Nowy serwis nie dostał czystego schematu. 30 encji zmapowaliśmy na tabele starego CMS-a, a polskie kolumny przetłumaczyliśmy na czytelny model domenowy. Zmiany wprowadzały wersjonowane migracje uruchamiane przy wdrożeniu.

  • Dodanie slugów do aktualności, wideo, galerii i kategorii - fundament nowych adresów oraz SEO.
  • Naprawa historycznych relacji tagów rozjechanych przez lata pracy starego systemu.
  • Czyszczenie treści redakcyjnych z pozostałości po dawnym edytorze.
  • Indeksy pełnotekstowe dla aktualności, wideo i galerii.
  • Nowe obszary bez naruszania archiwum: play-off, głosowanie, promo-boxy i sekcje partnerów.

Media są konwertowane do WebP i skalowane w locie, ale zachowują strukturę CDN starego serwisu. Archiwalne zdjęcia i logotypy pozostały pod dotychczasowymi adresami.

07

Zastosowane technologie

Frontend
Next.js 14, React 18, TypeScript 5, Tailwind CSS, Radix UI, Swiper, PhotoSwipe i React Hook Form
Backend
NestJS 10, TypeORM, MySQL 8, Swagger/OpenAPI oraz sesje JWT z tokenami dostępowymi i odświeżającymi
Panel administracyjny
Angular 17, NGXS, Angular Material, Bootstrap i TinyMCE
Wydajność
memcached, zadania cykliczne, ISR w Next.js, sharp i konwersja WebP
Integracje
ESOR, YouTube Data API i GetResponse
Grafika serwerowa
node-canvas do generowania obrazów drabinki play-off z typem kibica
Infrastruktura
GitLab CI, pm2 w klastrze, Docker Compose i Adminer
Jakość
139 zestawów testów API, ESLint, Prettier i typy frontendu generowane z OpenAPI

Specyfikacja OpenAPI jest kontraktem między aplikacjami. Zmiana backendu ujawnia się jako błąd kompilacji frontendu, a nie błąd na produkcji.

08

Wdrożenia

Merge do gałęzi rozwojowej publikuje staging, a merge do głównej - produkcję. Pipeline porównuje commit z ostatnim buildem i przebudowuje tylko te z trzech aplikacji, które rzeczywiście się zmieniły.

Migracje bazy uruchamiają się przed nową wersją API. Każdy frontend trafia do osobnego katalogu wydania ze współdzielonym cache obrazów, a procesy są przeładowywane w klastrze bez przerwy w dostępie.

84 wydania produkcyjne

Regularny rytm małych zmian przy 1 230 commitach zamiast rzadkich, obciążonych ryzykiem wdrożeń.

Przyrostowy build

Wspólny proces wdraża frontend, backend i panel, ale przebudowuje wyłącznie zmienione aplikacje.

09

Osiągnięte rezultaty

Serwis wdrożony

71 publicznych widoków, wraz z pełnym drzewem archiwum sezonów, działa na produkcji.

Archiwum zachowane

Nowy serwis pracuje na tej samej bazie bez ryzykownej, jednorazowej migracji treści.

Przyjazne adresy i SEO

Slugi, metadane i Open Graph powstały również dla wieloletnich treści istniejących wcześniej tylko pod ID.

Skalowalne wyniki live

Kwarta, zegar i wynik odświeżają się co 60 sekund przy jednym zapytaniu do źródła na minutę.

Spójny design system

Tokeny i wspólne komponenty obejmują wszystkie 71 widoków i rozwijają się razem z produktem.

Odporność na ESOR

Dwie warstwy cache i wyprzedzające odświeżanie izolują kibiców od obciążenia zewnętrznego API.

Komplet statystyk

Rankingi, rekordy, mapa rzutów, akcja po akcji, CSV i raport PDF działają dla bieżących i historycznych sezonów.

Nowy panel redakcyjny

59 komponentów obsługuje treści, media, reklamy, partnerów, play-off i konfigurację z kontrolą uprawnień.

Zaangażowanie kibiców

Drabinka play-off, głosowanie, ranking typów i indywidualne grafiki zachęcają do udostępniania.

Rozwój po premierze

Kolejne sezony, Puchar, Superpuchar, U23 i eksport kalendarza są dokładane bez przestojów.