Właściciel sklepu meblowego przychodzi z problemem, który zna wielu: sklep działa, sprzedaje, ma dobre pozycje w Google — i stoi na kodzie, którego wszyscy boją się dotknąć. Dwanaście lat custom developmentu na PHP 5.4, jeden programista, który jeszcze pamięta, dlaczego pewne rzeczy działają tak, a nie inaczej, i coraz dłuższa lista funkcji, których „lepiej nie ruszać". To nie jest historia o przestarzałej technologii. To historia o ryzyku biznesowym zamrożonym w kodzie.
Poniżej rozkładamy na części migrację Bogart Meble — sklepu, który przeszedł z legacy PHP na Symfony 6.4 bez jednego dnia przestoju i bez utraty pozycji. Pokażemy punkt wyjścia (dług techniczny), powód, dla którego odrzuciliśmy przepisanie „na raz", dobór stacku, szesnastotygodniowy harmonogram w czterech fazach oraz to, co realnie się zmieniło. Bez marketingu — z mechaniką, którą da się przełożyć na własny sklep.
Punkt wyjścia: dwanaście lat długu technicznego
Zaczęliśmy od audytu tego, co jest. Obraz był typowy dla sklepu, który przez lata rósł szybciej niż jego fundament techniczny. Pięć problemów, z których każdy osobno da się przeżyć, a razem tworzą stan podwyższonego ryzyka.
- PHP 5.4 po końcu wsparcia. Brak łatek bezpieczeństwa oznacza otwarte drzwi i rosnący problem ze zgodnością — bramki płatności i integracje wymagają nowszych wersji, a każdy miesiąc pogłębia dług.
- Custom framework bez dokumentacji. Całość rozumiał w praktyce jeden człowiek. To klasyczny bus factor równy jeden: choroba, urlop albo odejście jednej osoby zostawia sklep bez realnej opieki.
- Brak testów. Każda zmiana była ruletką, bo koszt regresji był nieznany. Efekt jest zawsze ten sam — zespół przestaje cokolwiek zmieniać, żeby niczego nie zepsuć, i rozwój zamiera.
- Monolityczna baza z zapytaniami po kilka sekund. To nie jest abstrakcyjny problem inżynierski. Wolne zapytania uderzają wprost w Core Web Vitals, a przez nie w konwersję i pozycje.
- Brak możliwości integracji z nowoczesnym API. Podłączenie BaseLinkera, nowej bramki płatności czy marketplace'u było praktycznie niewykonalne. Sklep nie mógł się rozwijać w kierunkach, które dawno były standardem rynku.
Żaden z tych punktów sam w sobie nie wywraca biznesu. Razem tworzą sytuację, w której rozwój kosztuje coraz więcej, a ryzyko rośnie z każdym miesiącem — aż drobna awaria potrafi stać się egzystencjalna. To jest moment, w którym zwykle trafia do nas taki sklep: nie dlatego, że coś się zepsuło, tylko dlatego, że właściciel policzył, ile kosztowałaby go awaria, której nie ma kto naprawić.
Dlaczego odrzuciliśmy przepisanie „big bang"
Najbardziej kusząca droga jest zwykle najgorsza: napisać nowy sklep od zera, obok, a potem w jeden weekend przełączyć ruch ze starego na nowy. Brzmi czysto. W praktyce przy dwunastu latach nieudokumentowanej logiki to proszenie się o katastrofę.
Przy podejściu „big bang" cała wartość powstaje dopiero w dniu przełączenia — miesiące pracy bez żadnego efektu do flag day. Jeśli coś pójdzie nie tak, rollback bywa niemożliwy, bo stary system w międzyczasie żył własnym życiem. A najgroźniejsze jest to, że o istnieniu części reguł biznesowych dowiadujesz się dopiero wtedy, gdy klient kliknie ścieżkę, której nikt nie odtworzył — czyli już na produkcji, na oczach kupujących.
Wybraliśmy wzorzec Strangler Fig. Nazwa pochodzi od figowca dusiciela, który oplata drzewo i stopniowo przejmuje jego miejsce. Tak samo nowy system oplata stary: przejmuje funkcje moduł po module, aż stary kod można w końcu usunąć. Logikę biznesową odtwarzaliśmy w Symfony fragment po fragmencie, a stary sklep przez cały czas działał i sprzedawał. Każdy moduł dostawał testy, zanim przejął realny ruch, więc ryzyko było rozłożone w czasie, a wycofanie zmiany — lokalne, na poziomie pojedynczego modułu, a nie całego sklepu.
Technicznie spina to warstwa routingu przed oboma systemami. Decyduje ona, które żądanie obsługuje jeszcze stary kod, a które przejął już nowy — najpierw trafiały tam ścieżki najmniej ryzykowne, na końcu checkout i płatności. Dla klienta i dla Google adres pozostaje ten sam; pod spodem zmienia się tylko to, który system odpowiada. Dzięki temu przełączanie ruchu jest stopniowe i odwracalne: jeśli nowy moduł zachowa się inaczej, niż powinien, wracamy na starą ścieżkę bez dotykania reszty sklepu.
| Kryterium | „Big bang" (przepisanie na raz) | Strangler Fig (moduł po module) |
|---|---|---|
| Ryzyko w dniu przełączenia | Skrajne — wszystko albo nic | Rozłożone — każdy moduł osobno |
| Rollback | Często niemożliwy | Lokalny, na poziomie modułu |
| Nieudokumentowana logika | Wychodzi na jaw po launchu, u klienta | Wychodzi w trakcie, moduł po module |
| Wartość przed przełączeniem | Zero do dnia „flag day" | Narasta z każdym modułem |
| Test na realnym ruchu | Brak — pierwszy ruch to chrzest bojowy | Parallel run na produkcyjnym ruchu |
| Kiedy ma sens | Mały, dobrze zrozumiany kod | Duży, stary, słabo udokumentowany monolit |
Przy dwunastu latach nieudokumentowanego kodu nie pytaliśmy, czy „big bang" się uda. Pytaliśmy, co się stanie, jeśli się nie uda — i tej odpowiedzi nikt nie chciał usłyszeć.
Nowy stack — i dlaczego właśnie taki
Dobór technologii to nie kwestia mody, tylko konsekwencji, z którymi klient zostaje na lata. Każdy element stacku miał rozwiązywać konkretny problem z listy długu technicznego, a nie dokładać kolejny.
| Warstwa | Technologia | Po co (mechanizm) |
|---|---|---|
| Backend | Symfony 6.4 | Dojrzały framework z długim wsparciem i ogromnym ekosystemem — koniec z bus factor równym jeden |
| API | API Platform | REST i GraphQL od ręki — gotowy fundament pod integracje (BaseLinker, marketplace) i front headless w przyszłości |
| Baza danych | MariaDB 10.11 | Stabilna, dobrze indeksowana baza w miejsce monolitu z zapytaniami po kilka sekund |
| Cache i sesje | Redis | Główny bohater spadku czasu ładowania — odciąża bazę na najczęstszych ścieżkach |
| Frontend | Twig + Stimulus | Renderowanie po stronie serwera, lekkie i przyjazne SEO, bez ciężaru pełnego SPA |
| Wyszukiwarka | Elasticsearch | Szybkie i trafne wyszukiwanie po dużym katalogu mebli, którego relacyjna baza nie udźwignie |
| Kolejki | RabbitMQ | Zadania asynchroniczne (maile, integracje, przeliczenia) poza żądaniem użytkownika — strona nie czeka |
| Wdrożenia | Docker + GitLab CI | Powtarzalne, testowane deploye — koniec z „u mnie działa" i ręcznym wgrywaniem plików |
Warto zaznaczyć jedną rzecz uczciwie: Symfony to ten sam fundament, na którym stoi Sylius. Custom na Symfony ma sens tam, gdzie logika biznesowa jest gruba i nietypowa — tak jak w Bogarcie. Gdy sklep w większości mieści się w standardach platformy, częściej rekomendujemy gotowego Syliusa niż budowę od zera, bo to szybsze i tańsze. Wybór „custom kontra gotowa platforma" to decyzja, którą podejmujemy na etapie audytu, nie w trakcie projektu.
Szesnaście tygodni w czterech fazach
Cały projekt zamknął się w czterech fazach na przestrzeni szesnastu tygodni — około czterech miesięcy. To był harmonogram Bogarta; przy większym katalogu albo grubszej logice taki scenariusz potrafi trwać znacznie dłużej. Ważniejsza od liczby tygodni jest kolejność kroków.
Faza 1 (tygodnie 1–3): audyt i reverse engineering
Najważniejszym produktem tej fazy nie był kod, tylko dokumentacja. Zespół odtwarzał logikę biznesową ukrytą w starym kodzie, mapował strukturę URL i strukturę bazy danych. Powstał też dokument, który w każdej naszej migracji nazywamy „Co zachowujemy bezwzględnie" — lista kilkuset najważniejszych adresów URL pod kątem ruchu i konwersji. To ona wyznacza granice, których nie wolno przekroczyć w dalszych fazach.
Odtwarzanie nieudokumentowanej logiki to praca detektywistyczna, nie zgadywanie. Czyta się ścieżki kodu tam, gdzie podejmowane są decyzje — jak liczą się rabaty, koszty dostawy, dostępność wariantów — i konfrontuje je z tym, co realnie dzieje się na produkcji, na prawdziwych zamówieniach. Tam, gdzie zostawał już tylko jeden człowiek pamiętający „dlaczego to działa akurat tak", jego wiedza trafiała na papier, zamiast dalej istnieć wyłącznie w jego głowie. Ta faza spłaca największą część długu jeszcze przed napisaniem pierwszej linijki nowego kodu — bo zamienia bus factor równy jeden na wspólną, zapisaną wiedzę zespołu.
Faza 2 (tygodnie 4–12): przepisywanie modułów
Serce projektu. Katalog, koszyk, checkout, panel klienta i integracje — odtwarzane moduł po module, każdy z automatycznymi testami pokrywającymi krytyczne ścieżki zakupowe. Testy nie były dekoracją: to one pozwalały bezpiecznie oddawać kolejne moduły, bo dawały jednoznaczną odpowiedź na pytanie, czy nowa wersja robi dokładnie to, co stara.
Migracja danych: gdzie ciche migracje umierają
Przeniesienie katalogu, historycznych zamówień i klientów to moment, w którym najłatwiej stracić coś bezpowrotnie. Prowadziliśmy migrację danych z walidacją integralności — po każdym przejściu dane były porównywane ze źródłem, żeby żaden rekord nie zniknął ani nie uległ cichemu zniekształceniu. Efekt: bez utraconych zamówień w całym procesie. Przy zamówieniach to nie jest „miła metryka", tylko warunek brzegowy — jedno zgubione zamówienie to realny klient i realny spór.
301 dla tysięcy adresów URL — warstwa, na której migracje tracą ruch
Najczęstsza przyczyna spadku pozycji po migracji to nie technologia, tylko porzucone adresy. Dlatego przygotowaliśmy pełne mapowanie przekierowań 301 dla wszystkich adresów URL — jeden do jednego, ze starej struktury na nową. Kluczowy szczegół: mapę walidował specjalista SEO, a nie tylko programista, i była ona zaimportowana do testów regresji jeszcze przed wdrożeniem, żeby wychwycić każdy zerwany link zanim zobaczy go Google.
To podejście ma pokrycie w liczbach. Branżowy benchmark spadku ruchu organicznego po migracji to zwykle 15–30%; w naszych projektach trzymamy się poniżej 3%. W Bogarcie przełożyło się to na niemal wszystkie zachowane pozycje — o czym więcej w sekcji z rezultatami.
Migracje nie tracą ruchu na skomplikowanym kodzie. Tracą go na porzuconych adresach — dlatego mapę przekierowań waliduje u nas specjalista SEO, a nie tylko programista.
Fazy 3–4 (tygodnie 13–16): parallel run i przełączenie bez przestoju
Przez około dwa tygodnie stary i nowy system działały równolegle. Parallel run pozwolił przetestować nowy sklep na realnym, produkcyjnym ruchu — łącznie z testami obciążeniowymi — zanim cokolwiek zależało od nowego kodu. Sens tego etapu jest prosty: zamiast wierzyć, że nowy system zachowa się poprawnie, sprawdzasz to na prawdziwym ruchu, w warunkach, których nie odtworzy żadne środowisko testowe. Różnice w wynikach między starym a nowym systemem — cena w koszyku, koszt dostawy, dostępność wariantu — wychodzą wtedy na jaw jeszcze przy działającym starym sklepie, a nie po tym, jak zgłosi je klient. Dopiero gdy nowy system udźwignął prawdziwy ruch i przestał się od starego różnić tam, gdzie nie powinien, przeszliśmy do przełączenia.
Samo przełączenie zrobiliśmy poza godzinami szczytu, z gotowym planem wycofania. W przypadku Bogarta cutover był na tyle wymagający, że rozłożyliśmy go na trzy etapy w czasie — każdy w oknie małego ruchu i żaden nie wymagał wyłączania sklepu. Pierwsze godziny po każdym etapie to wzmożony monitoring, a pozycje SEO i zachowanie systemu obserwowaliśmy jeszcze długo po starcie. Klienci przez cały ten czas mogli normalnie kupować.
Rezultaty — i jak je czytać
Cztery efekty najlepiej opisują rezultat migracji. Podajemy je jako wynik projektu, ale traktujmy je jako ilustrację mechanizmu, a nie jako gwarancję — każdy sklep startuje z innego miejsca.
- Czas ładowania strony produktu: z kilku sekund do poniżej sekundy. Kilkukrotne przyspieszenie, głównie dzięki Redisowi i przepisaniu najcięższych zapytań. To najbardziej bezpośredni efekt — i punkt wyjścia dla pozostałych.
- Wyraźny wzrost konwersji w ciągu trzech miesięcy. Tu uczciwość jest ważniejsza niż efektowna liczba: na wzrost złożyły się i nowy checkout, i sama szybkość. Nie przypisujemy całości jednej zmiennej — ale kierunek jest zgodny z tym, co widać w danych całej branży.
- Niemal wszystkie pozycje w Google zachowane. Efekt mapowania 301 i utrzymania tej samej struktury semantycznej. Przy branżowym benchmarku spadku 15–30% to różnica między spokojnym startem a miesiącami odbudowy ruchu.
- Bez utraconych zamówień i bez przestoju. Wynik parallel run, etapowego przełączenia i walidacji integralności danych.
Dlaczego szybkość tak mocno przekłada się na sprzedaż? Bo koszt wolnej strony jest mierzalny. W naszych obserwacjach TTFB powyżej 500 ms koreluje z 5–10% spadkiem konwersji — dlatego celujemy poniżej 200 ms. Zejście z kilku sekund do poniżej sekundy to nie kosmetyka, tylko usunięcie realnego hamulca sprzedaży. Zespół po stronie klienta bał się tej migracji latami; najczęstsza reakcja po przełączeniu sprowadzała się do niedowierzania, że dało się to zrobić bez przestoju i bez spadku w Google.
Jest jeszcze jeden rezultat, którego nie widać w liczbach z dnia startu, a który zmienia najwięcej w dłuższej perspektywie: sklep znów może się rozwijać. To, co wcześniej było niewykonalne — podłączenie BaseLinkera, wejście na kolejne marketplace'y, dodanie nowej bramki płatności — na stacku z API Platform staje się zwykłym zadaniem, a nie projektem obarczonym ryzykiem uszkodzenia całości. Migracja nie była więc celem samym w sobie. Była warunkiem, żeby wszystkie następne usprawnienia w ogóle stały się możliwe.
Kiedy taka migracja się opłaca — a kiedy nie
Przepisanie legacy PHP na Symfony to najdłuższy, najdroższy i najtrudniejszy scenariusz migracyjny, jaki prowadzimy. Nie każdy sklep go potrzebuje — i będziemy pierwszymi, którzy to powiedzą.
Ma sens, gdy:
- logika biznesowa jest gruba i nietypowa — konfiguratory, niestandardowe wyceny, reguły, których nie odwzoruje gotowa platforma;
- ruch organiczny jest głównym kanałem, więc koszt utraty pozycji liczy się w setkach tysięcy;
- skala sklepu sprawia, że przestój albo spadek widoczności to realne straty — pamiętajmy, że zły wybór wykonawcy potrafi kosztować mid-market sklep 500 tys. zł i więcej w pierwszym roku.
Nie ma sensu, gdy:
- sklep to w 90% standardowy katalog i koszyk — wtedy gotowa platforma (Shopify Plus albo Sylius) jest tańsza i szybsza niż budowa od zera;
- nie ma budżetu ani czasu na kilkumiesięczny projekt — jest wtedy sporo lżejszych ścieżek migracji;
- nikt po stronie firmy nie rozumie starych reguł i nikt nie chce ich odtwarzać — bez tej wiedzy albo gotowości do jej wydobycia ryzyko rośnie niezależnie od technologii.
Innymi słowy: Strangler Fig i custom na Symfony to narzędzie do konkretnego problemu, a nie domyślna odpowiedź. Inne nasze realizacje — jak migracja Topeshop na Shopify Plus czy KMK Klinkier na Sylius — pokazują, że dla wielu sklepów właściwą odpowiedzią jest gotowa platforma, nie przepisywanie kodu.
Podsumowanie
Migracja Bogart Meble pokazuje, że dwunastu lat długu technicznego nie trzeba spłacać jednym ryzykownym skokiem. Strangler Fig pozwolił odtworzyć logikę moduł po module, parallel run i etapowe przełączenie wyeliminowały przestój, a mapowanie 301 walidowane przez SEO ochroniło pozycje. Najważniejsza lekcja jest jednak strategiczna: dobra migracja zaczyna się od decyzji o kolejności — najpierw co chronimy bezwzględnie, dopiero potem jak to przepisujemy.
Jeśli stoisz na kodzie, którego boisz się dotknąć, zacznij od rozpoznania terenu, a nie od wyboru technologii. Zobacz, jak podchodzimy do migracji sklepu, albo napisz do nas przez stronę kontaktu — obejrzymy Twój przypadek i szczerze powiemy, czy przepisywanie ma sens, czy lepszą drogą jest gotowa platforma.