Witryna oparta na WordPressie z Elementorem — wolne ładowanie, słabe Core Web Vitals, brak kontroli nad strukturą HTML i JSON-LD, rosnące trudności z obsługą wielojęzyczności. Brzmi znajomo. W przypadku trimsandfasteners.com, producenta i dystrybutora zamków błyskawicznych i pasmanteryjnych na rynki EN i PL, te problemy przekładały się bezpośrednio na słabą indeksację i ograniczone możliwości dalszego rozwoju serwisu.
Celem projektu taf-next było oddzielenie warstwy prezentacji od backendu CMS, przejęcie pełnej kontroli nad performancem i SEO, przy jednoczesnym zachowaniu całej treści zarządzanej w WordPressie przez redakcję. Poniżej opisujemy dokładnie, co zbudowaliśmy i jakie decyzje techniczne podjęliśmy.
Architektura: Headless WordPress + Next.js App Router
WordPress na subdomenie wp.trimsandfasteners.com pełni wyłącznie rolę backendu — przechowuje treści, taksonomie, produkty i media. Dane pobierane są przez własne endpointy REST API (/wp-json/taf/v1/...) oraz standardowe WP REST API. Frontend zbudowany w Next.js 15 z App Routerem nie ma żadnego bezpośredniego kontaktu z bazą danych ani silnikiem PHP WordPress.
Model renderowania — SSG, ISR i Server Components
Wybór trybu renderowania per-route to jedna z kluczowych decyzji w projekcie Next.js. Tu zastosowaliśmy trzy podejścia w zależności od charakteru danych:
SSG (Static Site Generation) dla stron kategorii, podkategorii i wpisów blogowych. Strony generowane w całości przy buildzie, serwowane z CDN Vercel bez żadnego opóźnienia serwerowego — TTFB poniżej 100ms globalnie. Idealny tryb dla treści, które zmieniają się rzadko, a są odwiedzane najczęściej.
ISR (Incremental Static Regeneration) dla danych dynamicznych — automatyczne odświeżanie cache bez pełnego rebuildu. Kiedy redaktor zaktualizuje opis produktu w WordPressie, zmiana pojawi się na froncie po ustalonym interwale bez konieczności triggerowania nowego deploymentu.
Server Components jako domyślny model renderowania — JavaScript wysyłany do przeglądarki ograniczony wyłącznie do interaktywnych wysp: popupy produktowe, FAQ accordion, filtry. Reszta strony nie wysyła żadnego JS do klienta — to bezpośrednie przełożenie na niższe INP i szybszy Time to Interactive.
Wielojęzyczność z Polylang — locale-aware routing
Witryna działa w dwóch językach (EN/PL) z pełną segmentacją URL: / dla angielskiego i /pl/ dla polskiego. Implementacja wielojęzyczności w headless WordPress z Polylangiem ma jedną nieoczywistą pułapkę: Polylang przydziela różne Post ID dla tłumaczeń — angielska wersja posta i polska wersja to osobne rekordy w bazie, powiązane relacją.
Wymagało to osobnej obsługi mapowania między wersjami językowymi przy każdym zapytaniu do API. Zaimplementowano [locale] dynamic segment z middleware zarządzającym detekcją języka — przeglądarka bez preferencji językowej dostaje EN, request z nagłówkiem Accept-Language: pl jest automatycznie przekierowany do /pl/.
Interaktywny katalog produktów — Zipper Popup z in-memory cache
Kluczowa funkcja serwisu: kliknięcie zamka błyskawicznego w katalogu otwiera modal z pełnymi danymi technicznymi. To jednocześnie najważniejszy element interaktywny i największe wyzwanie wydajnościowe — modal musi się otwierać natychmiast, a dane muszą być poprawne językowo.
Rozwiązanie składa się z trzech warstw:
In-memory cache po stronie klienta z kluczem ${productId}-${locale} — pierwsze otwarcie popupu wykonuje fetch do API route, kolejne są natychmiastowe z pamięci. Cache rozróżnia wersje językowe, eliminując problem serwowania treści EN po przełączeniu języka na PL.
Locale-aware API route (/api/zipper/[id]?lang=pl) z parametrem lang przekazywanym do endpointu WP. Route działa jako proxy między frontendem a WordPress — ukrywa strukturę backendu, umożliwia cache'owanie odpowiedzi i dodaje warstwę transformacji danych.
PL Override Map — dla 13 produktów z kategorii nylon Polylang nie posiadał przetłumaczonych opisów w bazie WP. Zamiast ingerować w bazę danych klienta, stworzono plik src/lib/pl-zipper-overrides.ts z polskimi tłumaczeniami aplikowanymi po stronie Next.js API route. Zmiana izolowana całkowicie po stronie frontendu — backend WP nienaruszony.
FAQ Accordion i JSON-LD Schema — structured data, której Elementor nie dawał
W poprzedniej implementacji Elementor generował FAQ accordion jako widget, ale nie emitował żadnych structured data. To oznaczało, że Google crawlował pytania i odpowiedzi jako zwykły tekst, bez możliwości wyświetlenia ich jako rich result w SERP.
Po migracji na Next.js:
- Komponent
FaqAccordionz animowanym rozwijaniem odpowiedzi zbudowany jako Client Component zuseState. - Automatyczne generowanie schematu
FAQPagewedług specyfikacji Schema.org osadzanego jako<script type="application/ld+json">w initial HTML. - Pomimo że
FaqAccordionjest Client Component ('use client'), Next.js App Router wykonuje SSR również dla komponentów klienckich — JSON-LD jest obecny w source przy pierwszym załadowaniu strony, indeksowalny przez Googlebot bez JavaScript. - Ten sam pattern zastosowano w komponencie
PersonalizationContentna stronach personalizacji.
Przykładowa struktura emitowanego JSON-LD:
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What are coil (nylon) zippers...",
"acceptedAnswer": {
"@type": "Answer",
"text": "Coil zippers, also known as nylon zippers..."
}
}
]
}
Przekierowania 301 — konsolidacja link equity
Każda migracja URL-i to ryzyko utraty pozycji. Wszystkie historyczne URL-e z WordPressa zmigrowały do nowej struktury z zachowaniem wartości SEO:


