E-commerce · Case study
Perfumeria RHO – koncepcyjny sklep headless na Next.js i Shopify
Projekt portfolio pokazujący headless e-commerce oparty na Shopify: dedykowany frontend w Next.js, Storefront API, Customer Account API, koszyk i checkout Shopify, warianty produktów, wyszukiwarkę, wishlistę, opinie zweryfikowanych klientów, SEO oraz zabezpieczone webhooki.
- Klient
- Projekt koncepcyjny Netova
- Publikacja
- Czas czytania
- 11 min
- Zakres
- E-commerce

95+/100
Performance
100/100
SEO Score
96/100
Accessibility
0.9s
Czas ładowania
Galeria
Spis treści
- Co chciałem pokazać
- Jak zbudowałem projekt
- Podział odpowiedzialności
- Shopify jako backend, Next.js jako storefront
- Katalog produktów i warianty
- Wyszukiwarka produktów
- Koszyk połączony bezpośrednio z Shopify
- Checkout pozostaje po stronie Shopify
- Konto klienta przez Customer Account API
- Strefa klienta
- Wishlist bez wymagania konta
- Opinie tylko po zakupie produktu
- Ceny promocyjne i najniższa cena z 30 dni
- Próg darmowej dostawy z konfiguracji Shopify
- SEO dla sklepu headless
- Webhooki i aktualność danych
- Formularz kontaktowy
- Dodatkowy hardening aplikacji
- Projektowanie interfejsu pod perfumerię
- Testy zamiast wymyślonych statystyk
- Zakres weryfikacji
- Co pokazuje ten projekt
- Stack technologiczny
- Podsumowanie
Perfumeria RHO to projekt koncepcyjny sklepu internetowego z perfumami zbudowanego w architekturze Shopify Headless. Shopify odpowiada za warstwę commerce — katalog produktów, warianty, dostępność, koszyk i checkout — natomiast cały frontend sklepu działa jako osobna aplikacja w Next.js 16.
Punktem wyjścia był oficjalny szablon Vercel dla Next.js i Shopify. Nie traktuję więc projektu jako aplikacji napisanej od pustego repozytorium. Szablon wykorzystałem jako techniczną bazę, którą dostosowałem do koncepcji perfumerii i rozbudowałem o elementy potrzebne w bardziej kompletnym sklepie: własną warstwę wizualną, konta klientów, historię zamówień, książkę adresową, wishlistę, wyszukiwarkę, ceny zgodne z mechanizmem Omnibus, system opinii oraz dodatkowe zabezpieczenia.
Nie jest to sklep wykonany dla zewnętrznego klienta ani działająca marka, dlatego nie przypisuję projektowi fikcyjnej sprzedaży, konwersji czy liczby zamówień.
Co chciałem pokazać
- pracę z Shopify jako backendem e-commerce bez korzystania z klasycznego motywu Liquid,
- potencjał dedykowanego frontendu w Next.js i React, który może być bardziej elastyczny niż gotowy szablon,
- core web vitals i wydajność w headless commerce która nie jest ograniczona przez motyw Shopify,
- integrację z Shopify Storefront API przez GraphQL,
- obsługę produktów, kolekcji, wariantów, cen, dostępności i koszyka,
- połączenie własnego interfejsu z checkoutem Shopify,
- integrację Shopify Customer Account API,
- bezpieczne logowanie oparte na OAuth / OIDC i PKCE,
- historię zamówień i zarządzanie adresami klienta,
- wishlistę zapisywaną lokalnie w przeglądarce,
- system opinii dostępny dla klientów, którzy rzeczywiście kupili produkt,
- techniczne SEO dla produktów i kategorii,
- obsługę ceny referencyjnej i najniższej ceny z 30 dni,
- rewalidację danych po webhookach Shopify,
- walidację danych oraz podstawowy hardening aplikacji,
- testy jednostkowe, E2E i test dostępności.
Status projektu: projekt koncepcyjny / portfolio. Sklep bazuje na oficjalnym szablonie Vercel dla Shopify, który został wykorzystany jako fundament dalszej implementacji. Opis przedstawia funkcje znajdujące się w przygotowanej wersji projektu, a nie wyniki biznesowe prawdziwej perfumerii.
Jak zbudowałem projekt
Najważniejszą decyzją architektoniczną było rozdzielenie Shopify jako silnika sprzedaży od warstwy prezentacyjnej sklepu.
Zamiast budować cały storefront jako motyw Shopify, frontend działa w Next.js i komunikuje się z platformą za pomocą API. Dzięki temu interfejs nie jest ograniczony strukturą klasycznego szablonu sklepu, a Shopify nadal odpowiada za obszary, w których gotowa platforma commerce ma największą wartość.
Nie próbowałem pisać od zera systemu płatności, panelu produktowego czy infrastruktury checkoutu. Dedykowany kod skupia się tam, gdzie daje realną kontrolę nad doświadczeniem użytkownika i logiką aplikacji, natomiast Shopify pozostaje źródłem prawdy dla procesów commerce.
Podział odpowiedzialności
| Warstwa | Rozwiązanie | Odpowiedzialność |
|---|---|---|
| Storefront | Next.js 16, React 19, TypeScript | Interfejs, routing, rendering, Server Components i logika aplikacji |
| Commerce | Shopify | Produkty, kolekcje, warianty, dostępność, koszyk i checkout |
| Integracja sklepu | Shopify Storefront API + GraphQL | Komunikacja między frontendem a Shopify |
| Konta klientów | Shopify Customer Account API | Logowanie, profil, adresy i historia zamówień |
| Opinie | Shopify Metaobjects + Admin API | Opinie klientów zweryfikowanych na podstawie historii zakupów |
| Interfejs | Tailwind CSS, Headless UI | Responsywny design i komponenty interaktywne |
| Testy | Vitest, Playwright, axe-core | Logika biznesowa, główne flow sklepu i dostępność |
Shopify jako backend, Next.js jako storefront
Storefront komunikuje się z Shopify za pomocą zapytań GraphQL.
Warstwa integracyjna pobiera między innymi:
- produkty,
- kolekcje,
- warianty,
- ceny,
- dostępność,
- multimedia,
- rekomendowane produkty,
- strony i polityki sklepu,
- menu,
- konfigurację commerce.
Zapytania serwerowe mogą korzystać z prywatnego tokenu Storefront API. Dla zapytań GET-like zastosowałem również ponawianie żądania w przypadku czasowych odpowiedzi Shopify takich jak 429 lub błędy serwera.
Mutacje nie są automatycznie ponawiane w ten sam sposób, ponieważ powtarzanie operacji zmieniającej dane wymaga większej ostrożności.
Dzięki temu integracja z zewnętrznym API nie kończy się na pojedynczym fetch(), ale posiada własną warstwę obsługi błędów i różnych klas problemów Shopify.
Katalog produktów i warianty
Katalog obsługuje kolekcje Shopify, wyszukiwanie, sortowanie i paginację opartą na cursorach API.
Użytkownik może sortować produkty między innymi według:
- trafności,
- bestsellerów,
- najnowszych produktów,
- ceny rosnąco,
- ceny malejąco.
może również filtrować produkty po wariantach, takich jak:
- marka,
- typ produktu,
- kategoria.
Na karcie produktu frontend nie zakłada, że produkt ma tylko jeden wariant. Wybrana wersja jest identyfikowana na podstawie parametrów URL i opcji wariantu, a cena oraz dostępność aktualizują się dla konkretnej konfiguracji.
Strona produktu może obsługiwać również materiały dostarczane przez Shopify — zdjęcia, wideo oraz zewnętrzne materiały wideo.
Dzięki temu model produktu po stronie frontendu pozostaje powiązany z rzeczywistą strukturą Shopify zamiast być uproszczonym, statycznym obiektem przygotowanym wyłącznie na potrzeby demo.
Wyszukiwarka produktów
RHO posiada osobny endpoint wyszukiwania korzystający bezpośrednio ze Storefront API.
Zapytanie użytkownika jest przed wysłaniem walidowane i ograniczane długością. Wyniki zwracają najważniejsze dane potrzebne do szybkiego podglądu:
- nazwę produktu,
- zdjęcie,
- ceny,
- warianty,
- dostępność,
- dane związane z obniżkami.
Interfejs podpowiada również przykładowe popularne wyszukiwania, dzięki czemu użytkownik nie musi zaczynać od pustego pola.
Pełny katalog ma osobny widok wyników z paginacją i sortowaniem.
Koszyk połączony bezpośrednio z Shopify
Koszyk nie jest osobnym systemem napisanym obok platformy. Operacje dodawania, usuwania i zmiany ilości aktualizują Shopify Cart API.
Identyfikator koszyka jest przechowywany w zabezpieczonym ciasteczku, dzięki czemu koszyk może przetrwać kolejne wejścia użytkownika.
Przed wykonaniem mutacji Server Actions walidują dane wejściowe. Identyfikator wariantu musi odpowiadać formatowi Shopify Product Variant, a ilość produktu jest ograniczona do dopuszczalnego zakresu.
Kod rozróżnia też różne klasy problemów — na przykład:
- wygasły koszyk,
- niedostępny wariant,
- błąd walidacji,
- czasowy problem po stronie Shopify.
Dzięki temu użytkownik może dostać bardziej sensowną informację niż ogólne „coś poszło nie tak”.
Koszyk posiada również interaktywny panel z podsumowaniem, zmianą ilości, usuwaniem produktów i przejściem do płatności.
Checkout pozostaje po stronie Shopify
Jedną z ważniejszych decyzji było niepisanie własnego checkoutu.
Dedykowany storefront prowadzi użytkownika przez katalog, produkt i koszyk, ale finalny proces płatności przejmuje Shopify Checkout.
To celowe rozdzielenie odpowiedzialności. Własny frontend daje kontrolę nad wyglądem i doświadczeniem zakupowym, natomiast krytyczny proces płatności pozostaje w systemie przeznaczonym do obsługi e-commerce.
Przed przekierowaniem kod sprawdza, czy adres checkoutu korzysta z HTTPS. Jeżeli konto klienta jest aktywne, sklep może również przekazać do checkoutu tryb silent SSO.
Konto klienta przez Customer Account API
Projekt obsługuje nowy Shopify Customer Account API.
Logowanie korzysta z mechanizmu OIDC Authorization Code + PKCE. Podczas rozpoczęcia logowania generowane są między innymi:
state,nonce,code_verifier,code_challenge.
Po powrocie z Shopify aplikacja sprawdza stan transakcji oraz weryfikuje ID token.
Tokeny klienta nie są zapisywane bezpośrednio w zwykłym localStorage. Sesja jest szyfrowana po stronie serwera i przechowywana w ciasteczku HttpOnly.
W projekcie wykorzystałem szyfrowany JWT z A256GCM, a ciasteczka produkcyjne korzystają również z prefiksu __Host-.
Obsługiwane jest odświeżanie wygasającego access tokenu przy użyciu refresh tokenu.
Strefa klienta
Po zalogowaniu użytkownik może:
- sprawdzić swoje dane,
- zmienić imię i nazwisko,
- zobaczyć historię zamówień,
- otworzyć szczegóły konkretnego zamówienia,
- sprawdzić jego status finansowy i realizacyjny,
- zobaczyć informacje dotyczące przesyłki,
- przejść do trackingu, jeżeli Shopify zwraca odpowiedni URL,
- zarządzać książką adresową,
- dodawać i edytować adresy,
- ustawić adres domyślny,
- usuwać zapisane adresy.
Adresy i dane profilu są walidowane po stronie serwera przed przekazaniem ich do Shopify.
Wishlist bez wymagania konta
Lista ulubionych produktów jest rozwiązana niezależnie od Shopify Customer Account API.
Produkty dodane do wishlisty są zapisywane w localStorage, dzięki czemu użytkownik może korzystać z niej również bez zakładania konta.
Lista przechowuje informacje potrzebne do późniejszego pokazania produktu, między innymi jego identyfikator, handle, zdjęcie, cenę i warianty.
To prostsze rozwiązanie niż synchronizacja wishlisty z backendem i celowo pasuje do projektu, w którym sama możliwość zapisania produktu nie powinna wymagać logowania.
Opinie tylko po zakupie produktu
System opinii został powiązany z Shopify Customer Account API.
Samo posiadanie konta nie wystarcza do wystawienia recenzji.
Przed zapisaniem opinii aplikacja pobiera historię zamówień zalogowanego klienta i sprawdza, czy w którymś z nich znajduje się dany produkt.
Jeżeli produkt nie został kupiony przez tę osobę, API odrzuca próbę utworzenia opinii.
Dla połączenia opinii z użytkownikiem nie przechowuję publicznie jego Shopify Customer ID. Zamiast tego tworzony jest pseudonimowy identyfikator HMAC.
System ogranicza również jednego użytkownika do jednej opinii dla danego produktu.
Autor może później swoją recenzję:
- edytować,
- usunąć.
Przed wykonaniem takiej operacji backend ponownie sprawdza, czy opinia rzeczywiście należy do aktualnie zalogowanego klienta.
Same recenzje są przechowywane jako Shopify Metaobjects.
wszystkie opinie sa cache'owane po stronie frontendu, a rewalidacja następuje po webhooku.
Ceny promocyjne i najniższa cena z 30 dni
Warstwa cenowa rozróżnia:
- aktualną cenę,
- cenę referencyjną,
- zakres cen dla produktów z wieloma wariantami,
- najniższą cenę z 30 dni.
Najniższa cena przed obniżką jest pobierana z dedykowanego metafieldu Shopify dla wariantu produktu.
Frontend pokazuje ją wtedy, kiedy dany wariant rzeczywiście posiada obniżoną cenę.
Dzięki temu obsługa promocji nie jest zaszyta jako ręczny tekst w komponencie — dane są powiązane z konkretnym wariantem w katalogu Shopify.
Próg darmowej dostawy z konfiguracji Shopify
Próg darmowej dostawy również nie musi być wpisany na stałe w kodzie.
Sklep może odczytać go z metafieldu typu money ustawionego po stronie Shopify.
Jeżeli wartość istnieje, koszyk pokazuje klientowi, ile brakuje do osiągnięcia progu oraz wizualny pasek postępu.
Jeśli metafield nie został skonfigurowany, sklep nie pokazuje użytkownikowi obietnicy darmowej dostawy.
To niewielki detal, ale dobrze pokazuje kierunek projektu: elementy biznesowe, które mogą się zmieniać, powinny w miarę możliwości pochodzić z konfiguracji commerce zamiast wymagać deploymentu kodu.
SEO dla sklepu headless
Przy headless commerce część rzeczy, które w klasycznym Shopify dostaje się razem z motywem, trzeba świadomie odtworzyć po stronie własnego frontendu.
RHO posiada między innymi:
- dynamiczne meta title i description,
- canonical URL,
- Open Graph,
- robots.txt,
- sitemap.xml,
- osobne metadane dla produktów i kolekcji,
- kontrolę indeksowania ukrytych produktów,
- dane strukturalne Schema.org.
Na poziomie całego sklepu generowane są dane WebSite i OnlineStore.
Karta produktu otrzymuje dodatkowo między innymi:
Product,Offer,- informacje o cenie,
- walucie,
- SKU,
- dostępności wariantu,
- brandzie,
- BreadcrumbList.
Mapa witryny pobiera dynamicznie produkty, kolekcje i strony Shopify zamiast zawierać statyczną listę adresów.
Webhooki i aktualność danych
Headless storefront musi reagować na zmiany wykonane w panelu Shopify.
RHO posiada endpoint rewalidacyjny obsługujący webhooki zmian:
- produktów,
- kolekcji.
Po poprawnym zdarzeniu odpowiednie tagi cache Next.js są rewalidowane.
Webhook nie jest jednak przyjmowany bez sprawdzenia źródła.
Aplikacja obsługuje weryfikację Shopify HMAC na surowym body requestu. Projekt uwzględnia również rotację sekretu — przez okres przejściowy można zweryfikować webhook zarówno nowym, jak i poprzednim sekretem.
Dla ręcznie skonfigurowanych webhooków dostępna jest alternatywna weryfikacja przez osobny sekret rewalidacji.
Formularz kontaktowy
Kontakt nie kończy się na formularzu wysyłającym dane bezpośrednio do zewnętrznej usługi.
Dane są najpierw walidowane, a endpoint sprawdza między innymi:
- origin żądania,
- maksymalny rozmiar requestu,
- poprawność pól,
- token Cloudflare Turnstile.
Po pozytywnej weryfikacji wiadomość może zostać wysłana przez Resend.
Dane użytkownika są escapowane przed użyciem w wersji HTML wiadomości.
Dodatkowy hardening aplikacji
W konfiguracji aplikacji ustawiłem zestaw nagłówków bezpieczeństwa, między innymi:
- Content Security Policy,
X-Content-Type-Options: nosniff,Referrer-Policy,Permissions-Policy,- HSTS,
- blokadę osadzania strony przez
frame-ancestors.
Lista zewnętrznych źródeł dla obrazów, multimediów i ramek jest ograniczona do usług faktycznie potrzebnych przez storefront.
Tokeny Shopify pozostają po stronie serwera i są przekazywane przez zmienne środowiskowe.
Projektowanie interfejsu pod perfumerię
Warstwę wizualną dostosowałem do charakteru sklepu z perfumami.
Zamiast typowego technicznego wyglądu szablonu e-commerce interfejs wykorzystuje cieplejszą, bardziej premium estetykę:
- jasne kremowe tło,
- brązowo-bordowy kolor akcentowy,
- dużą typografię serif w nagłówkach,
- delikatne obramowania,
- miękkie cienie,
- duże zdjęcia produktów,
- ograniczoną liczbę konkurujących ze sobą elementów.
Strona główna składa się między innymi z:
- hero,
- prezentacji marek,
- wyróżnionych produktów,
- kategorii,
- benefitów zakupowych,
- karuzeli produktów.
Karta produktu stawia najważniejsze informacje obok galerii: markę, nazwę, cenę, wariant, dostępność, CTA, wishlistę oraz informacje wspierające decyzję zakupową.
Projekt uwzględnia również prefers-reduced-motion, dzięki czemu użytkownik, który ograniczył animacje w systemie, nie jest zmuszany do oglądania pełnych efektów ruchu.
Testy zamiast wymyślonych statystyk
Nie przypisuję projektowi fikcyjnych wyników Lighthouse ani sprzedaży tylko po to, żeby tabela wyglądała bardziej imponująco.
W repo znajdują się natomiast rzeczywiste testy automatyczne.
Zakres weryfikacji
| Obszar | Narzędzie | Co jest sprawdzane |
|---|---|---|
| Główna ścieżka sklepu | Playwright | Przejście ze strony głównej do katalogu |
| Koszyk | Playwright | Dodanie produktu, otwarcie koszyka i usunięcie produktu |
| Dostępność | axe-core + Playwright | Brak poważnych i krytycznych naruszeń WCAG 2A/2AA na stronie głównej w scenariuszu testowym |
| Ceny | Vitest | Operacje na kwotach i walutach |
| Warianty produktów | Vitest | Logika wariantów i cen referencyjnych |
| Metafieldy Shopify | Vitest | Parsowanie poprawnych i odrzucanie błędnych wartości money |
| Opinie | Vitest | Walidacja danych systemu recenzji |
| Webhooki | Vitest | Poprawne i błędne podpisy HMAC oraz rotacja sekretu |
Projekt posiada również osobne komendy do:
- sprawdzenia TypeScript,
- ESLint,
- Prettier,
- testów jednostkowych,
- testów E2E,
- produkcyjnego buildu.
Co pokazuje ten projekt
RHO nie ma udowadniać, że napisałem własnego Shopify od zera — bo nie miałoby to większego sensu.
Projekt pokazuje coś innego: potrafię wykorzystać istniejącą platformę commerce jako fundament i zbudować wokół niej dedykowany produkt.
Najważniejsza jest tutaj umiejętność połączenia kilku warstw:
- frontendu w Next.js,
- API Shopify,
- danych produktowych,
- koszyka i checkoutu,
- uwierzytelniania,
- kont klienta,
- opinii,
- SEO,
- cache i webhooków,
- bezpieczeństwa,
- testów.
To przykład sytuacji, w której Shopify zarządza sprzedażą, a dedykowany frontend daje większą kontrolę nad doświadczeniem klienta i prezentacją marki.
Nie każdy sklep potrzebuje takiej architektury. Dla prostszego e-commerce klasyczny Shopify Theme często będzie tańszym i szybszym rozwiązaniem.
Headless zaczyna mieć większy sens wtedy, gdy marka potrzebuje niestandardowego frontendu, własnej logiki interfejsu, integracji z innymi systemami albo większej swobody niż daje standardowy motyw.
Stack technologiczny
| Technologia | Zastosowanie |
|---|---|
| Next.js 16 | App Router, rendering, Server Components, Server Actions i API routes |
| React 19 | Interaktywna warstwa interfejsu |
| TypeScript | Typowanie danych aplikacji oraz Shopify API |
| Shopify Storefront API | Katalog, kolekcje, produkty, warianty i koszyk |
| Shopify Customer Account API | Konta klientów, zamówienia, profil i adresy |
| Shopify Admin API / Metaobjects | System opinii produktowych |
| GraphQL | Komunikacja z API Shopify |
| Tailwind CSS | Responsywny interfejs i design system |
| jose | OIDC, weryfikacja tokenów i szyfrowane sesje |
| Zod | Walidacja danych formularzy i API |
| Cloudflare Turnstile | Ochrona formularza kontaktowego przed spamem |
| Resend | Obsługa wiadomości z formularza kontaktowego |
| Vitest | Testy jednostkowe |
| Playwright + axe-core | Testy E2E i dostępności |
Podsumowanie
Perfumeria RHO to projekt pokazujący moje podejście do Shopify Headless: nie zastępuję sprawdzonych elementów Shopify własnym kodem tylko dlatego, że mogę, ale wykorzystuję platformę jako silnik commerce i buduję dedykowaną warstwę tam, gdzie daje to rzeczywistą elastyczność.
Projekt obejmuje znacznie więcej niż sam wygląd sklepu — katalog, warianty, wyszukiwanie, koszyk, checkout, konta klientów, zamówienia, wishlistę, opinie, SEO, webhooki, bezpieczeństwo i testy.
To właśnie ten zakres chciałem pokazać w portfolio.
Potrzebujesz sklepu Shopify z bardziej niestandardowym frontendem niż pozwala klasyczny motyw? Porozmawiajmy o tym, czy headless rzeczywiście ma sens dla Twojego biznesu — a jeśli nie, wybierzemy prostsze rozwiązanie.