Przejdź do strony

5 sierpnia 2026 • Wojciech Smolarek

Headless e-commerce a SEO. Jak zbudować frontend, który wybacza błędy

Headless e-commerce a SEO. Jak zbudować frontend, który wybacza błędy

Nie ma wdrożeń bez błędów. Są tylko architektury, w których błąd kosztuje pół godziny, i takie, w których kosztuje kwartał ruchu. Cała sztuka polega na tym, żeby świadomie wybrać tę pierwszą.

Błędy się zdarzą. Pytanie brzmi, ile Cię będą kosztowały

Nie da się zaplanować migracji sklepu tak, żeby nic nie poszło nie tak. Zawsze wypadnie jakaś kategoria, o której nikt nie pamiętał, parametr filtra, który generował indeksowalne URL-e od pięciu lat, albo stary landing z linkami, którego nie ma w żadnym eksporcie, bo powstał ręcznie w 2019 roku.

To jest normalne. Nienormalne jest to, co dzieje się potem.

W jednym sklepie taka wpadka to piętnaście minut roboty: dopisujesz regułę, deploy, po chwili jest po sprawie. W drugim ta sama wpadka oznacza zgłoszenie do dostawcy platformy, czekanie na okno wdrożeniowe, wtyczkę, która „nie obsługuje takich reguł”, i trzy tygodnie, w czasie których Google spokojnie przelicza sobie Twoją widoczność w dół.

Różnica nie leży w kompetencjach zespołu. Leży w architekturze.

I właśnie o tym jest ten tekst — nie o tym, że headless jest modny, tylko o tym, że headless daje Ci miejsce, w którym możesz poprawiać błędy bez ruszania całego sklepu.

Co znaczy „frontend, który wybacza”

Na początku ustalmy pojęcia. „Wybaczający” frontend to nie taki, który magicznie nie ma błędów. To taki, który ma cztery cechy.

1. Odwracalność. Każdą zmianę da się cofnąć szybciej, niż Googlebot zdąży ją porządnie przetrawić. Deploy z rollbackiem w minutę to inna klasa ryzyka niż zmiana wgrana bezpośrednio w szablon na produkcji.

2. Izolacja warstw. Możesz zmienić wygląd i strukturę URL-i, nie dotykając bazy produktów, zamówień i płatności. I odwrotnie — backend może żyć swoim życiem, a Ty nie modlisz się przed każdą aktualizacją, czy nie wywali Ci kanonicznych.

3. Własny kontrakt URL. To Ty decydujesz, jaki adres ma dany zasób i jaki kod odpowiedzi dostaje robot. Nie platforma, nie wtyczka, nie „tak to działa w tym systemie”.

4. Widoczność. Wiesz, co robot zobaczył, jaki dostał status i ile to trwało — bo masz dostęp do logów i warstwy, w której to wszystko przelatuje.

Zauważ, że żadna z tych czterech cech nie jest cechą SEO. To są cechy inżynierskie. SEO jest tu skutkiem ubocznym — ale skutkiem, który widać w przychodzie.

Dlaczego klasyczny monolit nie wybacza

W typowym sklepie na monolitycznej platformie warstwa danych, logika i prezentacja są ze sobą zespawane. Wygląda to wygodnie, dopóki nie musisz czegoś zmienić.

Praktyczne konsekwencje, na które natykam się u klientów e-commerce najczęściej:

  • Struktura URL wynika z modelu danych. Chcesz zmienić ścieżkę kategorii? Ruszasz drzewo kategorii w bazie, a razem z nim breadcrumby, filtry i pół indeksu.
  • Przekierowania obsługuje moduł. Wrzucasz do niego kilkanaście tysięcy reguł i modlisz się, żeby nie zaczął mielić bazy przy każdym żądaniu 404. Albo, co gorsza, żeby nie zaczął ich obsługiwać z opóźnieniem przez łańcuchy 301 → 301 → 200.
  • Kody odpowiedzi są poza Twoją kontrolą. Wycofany produkt zwraca 200 ze stroną „produkt niedostępny”. Pusty wynik filtra też zwraca 200. Miękkie 404 hodują się same, a Ty nie masz gdzie tego przechwycić.
  • Renderowanie jest jedno dla wszystkiego. Nie ustawisz osobnej strategii dla karty produktu (która ma być świeża) i dla wpisu blogowego (który spokojnie może być statyczny).
  • Każda zmiana frontu to zmiana w całym systemie. Więc zmiany robi się rzadko, w dużych paczkach, z długim czasem naprawy. Czyli dokładnie odwrotnie, niż wymaga tego bezpieczna migracja.

To nie jest hejt na monolity dla wielu sklepów są w zupełności wystarczające i tańsze w utrzymaniu. Ale trzeba sobie uczciwie powiedzieć: monolit nie ma warstwy, w której da się szybko naprawić błąd SEO. Naprawa zawsze dotyka czegoś większego.

Co headless faktycznie daje SEO

Headless w skrócie: backend (produkty, ceny, stany, zamówienia) wystawia dane przez API, a front jest osobną aplikacją, która te dane konsumuje i renderuje. Najczęściej Next.js albo Astro, czasem coś innego — mechanizm jest ten sam.

Konkret, a nie ideologia. Oto co się przez to zmienia.

Routing staje się kodem, który wersjonujesz

To najważniejsza zmiana i wszystko inne z niej wynika. W headless mapa adresów nie jest ustawieniem w panelu, tylko plikiem w repozytorium. Możesz ją przejrzeć, przetestować, zrobić na niej code review i cofnąć jednym rewertem.

Przekierowania przestają być „modułem”, a stają się warstwą, przez którą przelatuje ruch, zanim dotknie aplikacji:

// middleware.ts (Next.js)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { lookupRedirect } from "@/lib/redirects"; // np. KV / Redis / edge config

export async function middleware(req: NextRequest) {
  const { pathname, search } = req.nextUrl;

  const target = await lookupRedirect(pathname);
  if (target) {
    // od razu 301 na finalny adres — bez łańcuchów
    return NextResponse.redirect(new URL(target + search, req.url), 301);
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!_next|api|.*\\..*).*)"],
};

Kilkadziesiąt tysięcy reguł w magazynie klucz–wartość na brzegu sieci to jedno zapytanie o stałym koszcie. Nie obchodzi Cię, czy masz ich pięćset, czy pięćdziesiąt tysięcy.

Drugi zysk jest subtelniejszy, ale w migracjach ratuje skórę: reguły trzymasz jako dane, a nie jako klikane wpisy. Znalazłeś po tygodniu pięćset zapomnianych adresów? Dorzucasz je do zbioru i wdrażasz. Bez kolejnej migracji, bez wtyczki, bez proszenia kogokolwiek o pomoc. O tym, jak taką mapę przekierowań w ogóle sensownie zbudować i uzupełniać, pisaliśmy zresztą w jednym z wcześniejszych artykułów na blogu.

Kody odpowiedzi przestają być loterią

Miękkie 404 to jeden z tych błędów, które nie bolą od razu, tylko powoli. Google indeksuje pustki, marnuje na nie crawl budget, a Ty przez pół roku zastanawiasz się, czemu nowe kategorie tak wolno wchodzą.

W headless status jest Twoją decyzją, podejmowaną w jednym miejscu:

// app/produkt/[slug]/page.tsx
import { notFound } from "next/navigation";

export default async function ProductPage({ params }) {
  const product = await getProduct(params.slug);

  if (!product) notFound();                   // czyste 404
  if (product.replacedBy) redirect(`/produkt/${product.replacedBy}`, 301);
  if (product.discontinued) return <Discontinued product={product} />;

  return <Product product={product} />;
}

I odpowiednik dla treści, których świadomie już nie chcesz w indeksie — w middleware zwracasz twarde 410 zamiast udawać, że strona istnieje:

if (await isGone(pathname)) {
  return new NextResponse(null, { status: 410 });
}

Zwróć uwagę na logikę biznesową w tym fragmencie: produkt wycofany, ale z następcą, to 301. Produkt wycofany bez następcy, ale z ruchem i linkami, to strona, która zostaje i uczciwie pokazuje alternatywy. Produkt, którego nigdy nie powinno być w indeksie, to 410. W monolicie każdy z tych trzech przypadków dostaje to samo: 200 i komunikat „produkt niedostępny”.

Renderowanie dobierasz per typ strony

To jest ta część, w której headless spłaca się w Core Web Vitals — pod warunkiem, że go nie zepsujesz (o tym za chwilę).

Karta produktu ma zmienną cenę i dostępność, więc chcesz ją mieć świeżą. Kategoria zmienia się rzadziej. Artykuł blogowy praktycznie w ogóle. W jednym projekcie możesz obsłużyć wszystkie trzy scenariusze inaczej:

// blog: statycznie, przebudowa raz na dobę
export const revalidate = 86400;

// kategoria: statycznie z krótszym oknem
export const revalidate = 900;

// produkt: statycznie + odświeżenie na żądanie z webhooka po zmianie ceny
export const revalidate = 3600;
// a w API route: revalidatePath(`/produkt/${slug}`)

Robot dostaje gotowy HTML z brzegu sieci, użytkownik dostaje szybki LCP, a backend nie dostaje zawału przy każdym crawlu. W monolicie tę samą rzecz robi się warstwą cache’u, która działa albo nie działa i której nikt nie chce dotykać.

Dane ustrukturyzowane z jednego źródła prawdy

W headless JSON-LD generujesz z tego samego obiektu, z którego renderujesz stronę. Nie z drugiej wtyczki, która ma własne pole na cenę i własne wyobrażenie o dostępności.

To brzmi jak drobiazg, dopóki nie zobaczysz sklepu, w którym schemat mówi InStock, a strona mówi „powiadom o dostępności”. Rozjazd między treścią widoczną a danymi w znacznikach potrafi kosztować rich results — a diagnozuje się go paskudnie, bo w panelu wszystko wygląda dobrze.

To samo dotyczy kanonicznych, hreflangów i sitemap. Sitemapa generowana z API zawsze zgadza się ze stanem sklepu:

// app/sitemap.ts
export default async function sitemap() {
  const products = await getIndexableProducts(); // filtr po stronie danych
  return products.map((p) => ({
    url: `https://sklep.pl/produkt/${p.slug}`,
    lastModified: p.updatedAt,
  }));
}

Klucz jest w tym getIndexableProducts(). Reguła „co ma być w indeksie” żyje w kodzie, w jednym miejscu, i obsługuje jednocześnie sitemapę, canonical i meta robots. Nie w trzech niezależnych ustawieniach, które po roku mówią trzy różne rzeczy.

Warstwa, o której wszyscy zapominają: kontrakt URL

Jeśli miałbyś zapamiętać z tego tekstu jedną rzecz, to tę: adresy URL to nie efekt uboczny Twojego CMS-a, tylko osobny, świadomie utrzymywany kontrakt między Tobą a wyszukiwarką.

W headless możesz go w końcu wyodrębnić. Wygląda to mniej więcej tak:

  • jedno źródło prawdy o tym, jaki zasób ma jaki adres (najlepiej wyliczane z danych, nie wpisywane ręcznie),
  • warstwa aliasów: stare adresy, które kiedyś prowadziły do tego zasobu, trzymane razem z nim,
  • reguła: zmiana slugu nigdy nie kasuje starego adresu, tylko dopisuje go do aliasów i tworzy 301,
  • testy, które przy każdym deployu sprawdzają garść krytycznych ścieżek — czy nadal odpowiadają tym, czym powinny.

Ten ostatni punkt to pięć minut roboty, a łapie absurdalną liczbę wpadek:

// smoke test w CI
const cases = [
  ["/stara-kategoria", 301, "/nowa-kategoria"],
  ["/nowa-kategoria", 200],
  ["/produkt/nieistniejacy", 404],
  ["/promocja-2019", 410],
];

Jak przejdzie — wdrażasz. Jak nie przejdzie — nie wdrażasz. Tyle. Żadna wtyczka Ci tego nie da, bo wtyczka nie wie, jaka jest Twoja intencja.

Headless też potrafi wystawić Cię do wiatru

Pułapka pierwsza: front bez renderowania po stronie serwera. Jeśli zrobisz z tego czystą aplikację kliencką, która dociąga treść w przeglądarce, to właśnie kupiłeś sobie wszystkie wady monolitu plus opóźnione indeksowanie. Google to zrenderuje, ale nie od razu i nie zawsze. Headless bez SSR/SSG to headless bez sensu — przynajmniej z punktu widzenia SEO.

Pułapka druga: interaktywność ponad rozsądek. Ładujesz do karty produktu pół megabajta JavaScriptu, bo „design tak wyszedł”, i INP leci w kosmos. Sam fakt, że używasz nowoczesnego frameworka, nie czyni strony szybką. Framework daje Ci narzędzia, nie wyniki.

Pułapka trzecia: dwa źródła prawdy. Backend uważa, że produkt jest wycofany, front ma go w cache’u przez dobę. Bez porządnej inwalidacji cache’u będziesz miał w indeksie zombie.

Pułapka czwarta: koszt utrzymania. Headless to własna aplikacja. Ktoś musi ją aktualizować, monitorować i naprawiać o 23:00, kiedy padnie deploy. Jeśli nie masz tego kogoś — ani wewnątrz, ani u partnera — to nie jest architektura dla Ciebie, choćby była najładniejsza na diagramie.

Checklista: frontend, który naprawdę wybacza

Zanim powiesz „idziemy w headless”, sprawdź, czy projekt ma to wszystko. Bez tych punktów dostajesz tylko nowszy stos technologiczny i te same problemy.

  1. SSR lub SSG na każdej stronie, która ma być w indeksie. Bez wyjątków dla „tej jednej sekcji”.
  2. Przekierowania jako dane, obsługiwane na brzegu, bez łańcuchów. Wejście → finalny adres, jednym skokiem.
  3. Świadome kody odpowiedzi: 200, 301, 404 i 410 rozdzielone regułą biznesową, nie przypadkiem.
  4. Jedna funkcja decydująca o indeksowalności, zasilająca sitemapę, canonical i meta robots.
  5. Sitemapa generowana z API, nie plik wgrywany raz na kwartał.
  6. Testy kontraktu URL w CI, blokujące deploy przy rozjeździe.
  7. Rollback w minutę — i przećwiczony, a nie „teoretycznie mamy”.
  8. Inwalidacja cache’u spięta z webhookami ze zmian w backendzie.
  9. Logi serwera dostępne i czytane. To nadal najuczciwsze źródło informacji o tym, co robot faktycznie robi.
  10. Budżet JS pod kontrolą, mierzony na danych z pola, nie tylko w laboratorium.

Kiedy headless to zły pomysł

Krótko, bo to ważne: jeśli masz sklep z kilkuset produktami, stabilną strukturą, jednym rynkiem i zespołem złożonym z jednej osoby od marketingu — headless nie zwróci Ci się nigdy. Dostaniesz wyższy koszt utrzymania i nową klasę awarii w zamian za elastyczność, której nie wykorzystasz.

Headless zaczyna mieć sens, kiedy: często zmieniasz strukturę, wchodzisz na kolejne rynki, masz duży katalog i realny problem z wydajnością, albo szykujesz migrację, po której nie chcesz już nigdy być zakładnikiem cyklu wydawniczego swojej platformy.

To jest decyzja biznesowa, nie technologiczna. Technika ma robić robotę dla sklepu, nie odwrotnie.

Na koniec

Dobra architektura nie polega na tym, że nie popełnisz błędu. Polega na tym, że kiedy go popełnisz — a popełnisz — masz gdzie go poprawić, zanim ktokolwiek to zauważy w raporcie.

I jeszcze jedno, z doświadczenia: po wdrożeniu nie odświeżaj Search Console co dwie godziny. Zrób smoke test, sprawdź logi, zobacz, czy roboty dostają to, co mają dostawać, i zamknij laptopa. Google i tak przeliczy to po swojemu, w swoim tempie. Kawa lepiej smakuje, kiedy testy przeszły na zielono.

Planujesz migrację i nie wiesz, czy headless to dla Ciebie właściwy kierunek? Napisz, obgadamy Twój przypadek na konkretach, bez sprzedawania Ci architektury, której nie potrzebujesz.

Wojciech Smolarek

O autorze: Wojciech Smolarek

Web Developer

Web Developer, Technical SEO Specialist i specjalista od automatyzacji. W Studio Algorytm odpowiada za development, zamieniając skomplikowane procesy biznesowe i marzenia klientów w czysty, zoptymalizowany kod.

Zobacz wszystkie artykuły autora