15 min czytaniaRoman Isopenko
  • cms
  • e-commerce
  • web-development

Payload — alternatywa dla WordPressa?

Czy Payload CMS może zastąpić WordPressa w tworzeniu efektywnego CMS lub sklepu online?

Czy tak naprawdę potrzebujemy dziesiątek pluginów oraz całego arsenału narzędzi dostarczanych przez WP, żeby zbudować dobry sklep e-commerce, spełniający potrzeby klienta? Z pomocą przychodzi Payload CMS.

Czym jest w skrócie Payload CMS

Payload to CMS napisany w TypeScripcie, który instaluje się wewnątrz Twojej aplikacji Next.js — panel admina i API działają w tym samym procesie co strona. Dostarcza przy tym cały backend do zarządzania treściami dynamicznymi, takimi jak posty, blogi, sklepy internetowe i wiele innych. Zapewnia pełny panel administracyjny, umożliwiający dodawanie, edytowanie oraz usuwanie niemal wszystkich treści cyfrowych za pomocą bardzo przyjaznego interfejsu oraz prostej konfiguracji dla deweloperów. W skrócie, jest to niezwykle wygodne rozwiązanie zarówno dla dewelopera, jak i redaktora treści. Ponadto technologia ta umożliwia dość mocną customizację aplikacji. Dosłownie możemy dostosować taki CMS pod klienta tak, jak się chce, nie będąc ograniczonymi ramami, jak ma to miejsce np. w WordPressie. Potrzebujesz strony z blogiem? Nie potrzebujesz pięciu pluginów - wystarczy, że dodasz kolekcję „Blog” z polami, których faktycznie potrzebujesz, i dopiszesz kilka linijek do configu. Voilà - możesz pisać do świata o swoim know-how w ogrodnictwie, nowej książce czy polecanych miejscach w Tajlandii.

Live Preview w Payload - zmiany w edytorze widać od razu w podglądzie strony. Źródło: https://payloadcms.com
Live Preview w Payload - zmiany w edytorze widać od razu w podglądzie strony. Źródło: https://payloadcms.com

Co więcej, Payload może służyć jako DAM do zarządzania zasobami, takimi jak obrazki, filmiki oraz dźwięki. A więc jeśli potrzebujesz jednego miejsca do przechowywania oraz edycji swoich zasobów - Payload spełni ten wymóg funkcjonalny. Wystarczy skonfigurować taką opcję oraz wybrać miejsce przechowywania assetów, np. na serwerze wirtualnym czy w chmurze.

Prezentacja zarządzania assetami w Payload - źródło: https://sugar-madam.pl
Prezentacja zarządzania assetami w Payload - źródło: https://sugar-madam.pl

Czemu warto rozważyć Payload

Elastyczność

Payload jest dość elastyczny w budowaniu rozwiązań CMS, dając deweloperowi swobodę w tworzeniu dedykowanego narzędzia pod konkretną wizję. Dzięki możliwości wyboru bazy danych, jesteś w stanie samodzielnie określić dokładną architekturę tworzonej aplikacji. Payload pozwala na zintegrowanie PostgreSQLMongoDB czy SQLite - to trzy oficjalne adaptery, więc w odróżnieniu od WordPressa przybitego na stałe do MySQL-a masz tu realny wybór. W zależności od złożoności problemu, jesteś w stanie samodzielnie wybrać rozwiązanie pasujące do bieżącego celu. To samo tyczy się deklarowania struktur danych - sam kontrolujesz, jak powinny wyglądać tabele użytkownika, produktów czy zamówień - a nie kolejny plugin. Co więcej, jako deweloper jesteś w stanie modyfikować wygląd panelu admina poprzez dodanie swoich komponentów Reactowych. W configu podajesz je jako ścieżki do plików wraz z nazwą eksportu - panel admina to React Server Components, więc Payload sam buduje mapę importów, zamiast przyjmować gotowe komponenty:

import { buildConfig } from 'payload';

export default buildConfig({
  admin: {
    components: {
      Nav: '/components/Nav#MyCustomNav',
      graphics: {
        Icon: '/components/Icon#MyCustomIcon',
        Logo: '/components/Logo#MyCustomLogo',
      },
      actions: ['/components/AdminAction#MyCustomAdminAction'],
      views: {
        account: { Component: '/views/Account#MyCustomAccount' },
        dashboard: { Component: '/views/Dashboard#MyCustomDashboard' },
      },
      providers: ['/providers/MyProvider#MyProvider'],
    },
  },
});


Poniżej umieszczamy przykład deklarowania struktury danych Pricing za pomocą zwykłego TypeScriptu. Jesteśmy w stanie oznaczyć pola jako wymagane za pomocą flagi required, zdefiniować pola zagnieżdżone jako tablice wartości oraz określić dostępne opcje do wyboru. Później te wartości pojawiają się automatycznie w tworzonych formularzach.

import type { CollectionConfig } from 'payload';

export const Pricing: CollectionConfig = {
  slug: 'pricing',
  access: { read: () => true },
  admin: {
    useAsTitle: 'label',
    defaultColumns: ['label', 'section', 'order'],
  },
  fields: [
    {
      name: 'section',
      type: 'select',
      required: true,
      label: 'Sekcja',
      options: [
        { label: 'Depilacja', value: 'depilacja' },
        { label: 'Rzęsy & brwi', value: 'lashesBrows' },
      ],
    },
    { name: 'label', type: 'text', required: true, label: 'Nagłówek grupy' },
    {
      name: 'rows',
      type: 'array',
      required: true,
      label: 'Pozycje cennika',
      fields: [
        { name: 'name', type: 'text', required: true, label: 'Nazwa usługi' },
        {
          name: 'price',
          type: 'number',
          required: true,
          min: 0,
          label: 'Cena (zł)',
          admin: { step: 1 },
        },
      ],
    },
    {
      name: 'order',
      type: 'number',
      defaultValue: 0,
      label: 'Kolejność',
      admin: { step: 1 },
    },
  ],
};

Poniżej znajduje się formularz wygenerowany na podstawie podanej struktury danych:

Formularz wygenerowany na podstawie kolekcji Pricing - źródło: https://sugar-madam.pl
Formularz wygenerowany na podstawie kolekcji Pricing - źródło: https://sugar-madam.pl

Właśnie dzięki swojej elastyczności zachęcamy naszych klientów do rozważenia wdrożenia Payloada jako swojego dedykowanego systemu CMS. Daje on swobodę twórcom treści, jak i deweloperom, którzy są w stanie dodać lub zmodyfikować CMS pod konkretną, bieżącą potrzebę.

Uwierzytelnianie oraz autoryzacja w „pudełku”

Oprócz wielu funkcjonalności dostarczanych przez Payload, mamy również pod maską uwierzytelnianie oraz autoryzację, ponieważ posiada on natywną implementację JWT - domyślnie token trafia do ciasteczka HTTP-only, ale możesz go też pobrać i użyć we własnym froncie. Logowanie, wylogowanie, odzyskiwanie hasła - wszystkie te endpointy są już rozwiązane za Ciebie i nie musisz się tym przejmować. Skup się na logice biznesowej, a nie na trywialnych rzeczach, które mimo wszystko wymagają trochę czasu na implementację. Ponadto możemy skonfigurować takie rzeczy jak lockTimemaxLoginAttempts czy tokenExpiration. Wszystko dzieje się pod spodem i jest integrowalne nie tylko z częściami związanymi z Payload, ale również z naszą aplikacją, np. Next.js.

Również zostawiamy dokumentację, gdzie dokładnie opisany jest sposób konfiguracji i integracji.

payload auth

Access Control i role

Wraz z uwierzytelnianiem Payload dostarcza też mechanizm praw do odczytu, tworzenia, edycji, usuwania itd. Access Control działa na poziomie każdej operacji osobno - możesz np. pozwolić wszystkim na odczyt opublikowanych postów, ale usuwanie zarezerwować wyłącznie dla użytkowników z rolą admin. Warto od razu zaznaczyć, że gotowych ról Payload nie ma: role dodajesz sam jako zwykłe pole w kolekcji użytkowników, a o tym, kto co może zrobić, decydują funkcje dostępu w configu. Brzmi jak więcej pracy i faktycznie nią jest, ale w zamian dostajesz uprawnienia opisane w kodzie, a nie wyklikane w panelu. Dzięki temu możesz zbudować role-based access control (RBAC) z kilkoma poziomami uprawnień, np. super-admin z pełnym dostępem oraz editor ograniczony wyłącznie do zarządzania treścią. Więcej o mechanizmach kontroli dostępu znajdziesz w dokumentacji Access Control.

Wsparcie i18n oraz localization

i18n

Jeśli potrzebujemy CMS-a dla określonej grupy językowej, żeby interfejs był zrozumiały dla całej tej grupy, możemy skorzystać z natywnego wsparcia i18n. Wciśnij po prostu npm i @payloadcms/translations w swoim ulubionym terminalu. Dodaj maksymalnie dwie linijki kodu do configu - i działa!

import { buildConfig } from 'payload';
import { en } from '@payloadcms/translations/languages/en';
import { pl } from '@payloadcms/translations/languages/pl';

export default buildConfig({
  // ...
  i18n: {
    supportedLanguages: { en, pl },
  },
});


Więcej w dokumentacji o i18n.

Warto jednak uważać na zbyt dużą listę języków, ponieważ zwiększa to rozmiar naszego końcowego bundla, a przecież zależy nam na optymalnym i dobrym kodzie, prawda? 😀

Warto jednak uważać na zbyt dużą listę języków, ponieważ zwiększa to rozmiar naszego końcowego bundla, a przecież zależy nam na optymalnym i dobrym kodzie, prawda? 😀

Localization

Localization to druga strona tej samej monety. O ile i18n tłumaczy sam interfejs panelu, o tyle localization pozwala „przetłumaczyć” nasze dane na wiele języków, dla różnych grup klienckich. Klient chce sprzedawać ciuchy zarówno w Zielonej Górze, jak i we Frankfurcie nad Odrą? Proszę bardzo — nie potrzebujesz do tego żadnej wtyczki, jak ma to miejsce w WordPressie. Po prostu dodaj to sam w configu, a treści uzupełniaj na bieżąco przy dodawaniu nowych produktów. Poniżej znajduje się przykład takiej konfiguracji:

import { buildConfig } from 'payload';

export default buildConfig({
  // ...
  localization: {
    locales: ['pl', 'de'],
    defaultLocale: 'pl',
    fallback: true, // domyślnie włączony: brak tłumaczenia? pokaż wersję z defaultLocale
  },
});

Payload musi jeszcze wiedzieć, które pola mają być tłumaczone - wystarczy oznaczyć je flagą localized: true. Pola bez tej flagi zostają wspólne dla wszystkich języków, więc nie duplikujesz danych, które duplikacji nie wymagają - SKU tej samej bluzy jest przecież takie samo po obu stronach Odry:

import type { CollectionConfig } from 'payload';

export const Products: CollectionConfig = {
  slug: 'products',
  admin: { useAsTitle: 'title' },
  fields: [
    // Tłumaczone - osobna wartość dla każdego locale
    { name: 'title', type: 'text', required: true, localized: true, label: 'Nazwa produktu' },
    { name: 'description', type: 'textarea', localized: true, label: 'Opis' },
    // Wspólne - jedna wartość dla wszystkich języków
    { name: 'sku', type: 'text', required: true, unique: true, label: 'SKU' },
    { name: 'stock', type: 'number', required: true, min: 0, label: 'Stan magazynowy' },
  ],
};

Cena to zresztą osobna historia - przy sprzedaży w dwóch krajach nie chodzi o tłumaczenie, tylko o walutę, więc złotówek i euro nie załatwisz flagą localized. To temat na inną strukturę danych albo na oficjalną wtyczkę e-commerce, która obsługę wielu walut ma wbudowaną - każda waluta dostaje wtedy osobne pole ceny na produkcie.

Mając taką kolekcję, dodanie produktu w dwóch językach sprowadza się do dwóch wywołań Local API - najpierw tworzymy dokument w języku domyślnym, a potem dokładamy do tego samego dokumentu wersję niemiecką:

// 1. Produkt w domyślnym locale (pl)
const product = await payload.create({
  collection: 'products',
  locale: 'pl', // Zwróć uwagę tu 🖍️
  data: {
    title: 'Bluza z kapturem',
    description: 'Bawełniana bluza z kapturem, gramatura 320 g/m².',
    sku: 'HOOD-001',
    stock: 25,
  },
});

// 2. Wersja niemiecka - ten sam dokument, inne locale
await payload.update({
  collection: 'products',
  id: product.id,
  locale: 'de', // Zwróć uwagę tu 🖍️
  data: {
    title: 'Kapuzenpullover',
    description: 'Kapuzenpullover aus Baumwolle, 320 g/m².',
  },
});

Od tej pory wystarczy przy pobieraniu danych wskazać język, a Payload sam poda właściwą wersję - pola nietłumaczone (skustock) pozostają identyczne:

const { docs } = await payload.find({
  collection: 'products',
  locale: 'de',
});

// docs[0].title === 'Kapuzenpullover'
// docs[0].sku === 'HOOD-001'

To samo działa oczywiście przez REST (/api/products?locale=de) i GraphQL. A w panelu admina wygląda to jeszcze prościej - nad formularzem pojawia się selektor języka i redaktor po prostu przełącza się między pl a de, uzupełniając tłumaczenia. Żadnego duplikowania wpisów, żadnych równoległych drzew stron - jeden produkt, kilka wersji językowych.

Flagę localized: true najlepiej ustawić od razu przy projektowaniu kolekcji. Dodanie jej do pola, w którym są już dane, zmienia strukturę zapisu i kasuje dotychczasową zawartość tego pola - przy działającym sklepie potrzebujesz wtedy migracji danych.

Więcej w dokumentacji o localization.

Kiedy jednak okiełznany WP wygrywa

Co tu dużo mówić, WordPress jest mega popularnym rozwiązaniem z olbrzymim ekosystemem - tylko w oficjalnym i darmowym katalogu WordPress.org dostępnych jest ponad 65 tysięcy wtyczek (pluginów), a więc to rozwiązanie dostarcza nam na start znacznie więcej „gotowców”. Widać to zresztą w liczbach: według W3Techs WordPress napędza dziś ok. 40,8% wszystkich stron WWW i odpowiada za 59% rynku CMS-ów. Faktem jest, że na dzień dzisiejszy jest to król wśród CMS-ów. A więc dlaczego czasami wybór Payloada jako rozwiązania CMS się nie opłaca:

Łatwość dla twórców nietechnicznych

Dzięki podejściu no-code oraz całkiem przejrzystemu Dashboardowi, z WordPressa mogą korzystać - i faktycznie korzystają - rzesze tak zwanych „nietechnicznych” osób, które nie piszą kodu lub po prostu nie muszą się uczyć programowania, żeby stworzyć mały projekt. Tu na pomoc przychodzi WordPress. Chcesz podstronę z blogiem? Instalujesz potrzebną wtyczkę, dodajesz treść. Masz bloga. Chcesz wysyłkę maili? Ten sam pattern.

Szybki setup

Setup oraz wystartowanie takiego projektu są błyskawiczne. Mały sklep e-commerce jesteśmy w stanie dostarczyć nawet w kilka dni. Instalując odpowiednie wtyczki, zapewniamy sobie też dobry standard SEO, a przy użyciu Elementora jesteśmy w stanie dość szybko zbudować sensowny UI bez wielkiej wiedzy o UX/UI. W sumie nawet nie musimy się zbytnio przejmować hostingiem i serwowaniem takiej strony, bo na rynku mamy mnóstwo gotowych propozycji, gdzie w komplecie kupujemy domenę, hosting oraz cały serwerek PHP z naszą stronką w WP. W odróżnieniu od Payloada, tu musimy sami dużo rzeczy napisać, stworzyć kolekcje, wypuścić kod itd.

Plugin zwalnia z odpowiedzialności

Tak jak pisałem wyżej, 65 tys. wtyczek to pokaźna liczba już działających i sprawdzonych przez lata narzędzi na rynku. W sumie pluginy zwalniają nas z odpowiedzialności i nie musimy nic pisać od zera. Co prawda, też nas to trochę kosztuje: z każdą kolejną instalowaną wtyczką wydłuża się czas ładowania strony (page load time), a przy okazji rośnie ryzyko luk bezpieczeństwa (security vulnerabilities). W skrajnych przypadkach niekontrolowane mnożenie pluginów potrafi zamienić prosty projekt w trudny do utrzymania, wolny i podatny na ataki twór.

Payload podchodzi do tego inaczej - stawia na podejście lean-code-first, w którym zamiast dokładać kolejne czarne skrzynki, sam kontrolujesz każdą linijkę kodu odpowiadającą za funkcjonalność Twojej aplikacji. Kosztuje to więcej czasu na starcie, ale w zamian dostajesz przewidywalną wydajność oraz pełną kontrolę nad tym, co faktycznie trafia do finalnego builda.

Dobrze sprawdzony budżetowy wariant

Jest jeszcze jeden argument, którego nie da się zbić elegancją kodu: pieniądze. Przy małych budżetach WordPress po prostu wygrywa i trzeba to powiedzieć wprost.

Strona na WP staje na hostingu współdzielonym, który kosztuje ułamek tego, co serwer Node'owy z bazą danych i object storage. Motyw kupujesz raz, większość wtyczek jest darmowa. Nie potrzebujesz dewelopera na start i, co ważniejsze, nie potrzebujesz go później: nowy formularz kontaktowy, sekcja z opiniami czy zmiana układu strony to kliknięcie w panelu, a nie zgłoszenie do wykonawcy i redeploy.

Payload jest w tym miejscu uczciwie droższy. Wszystko jest code-first, więc dodanie kolekcji czy nowego pola dzieje się w jednym miejscu i pod pełną kontrolą - ale to wciąż Ty piszesz to, co w WordPressie było gotową wtyczką. Jeśli te wtyczki robiły coś złożonego, mówimy o realnej robocie i realnej wycenie.

Granica przebiega mniej więcej tam, gdzie kończy się „standardowa strona” bez dużej customizacji. Wizytówka, blog firmowy, mały sklep na gotowym motywie - WP zrobi to taniej i szybciej. Rachunek zaczyna wyglądać inaczej dopiero wtedy, gdy zsumujesz roczne licencje kilku wtyczek, czas na ich aktualizacje i godziny debugowania konfliktu, który wyszedł po którymś z update'ów. Wtedy warto policzyć koszt do końca, a nie tylko do faktury za hosting.

Nasz use case: Sugar Madam

Ostatnio otrzymaliśmy zlecenie od klientki z branży beauty - studia urody Sugar Madam z Zielonej Góry. Wcześniej cała działalność żyła w mediach społecznościowych: oferta, cennik i sprzedaż kosmetyków były rozsypane po postach i wiadomościach prywatnych. Zadanie brzmiało prosto: jedno miejsce, które prezentuje markę, sprzedaje produkty i prowadzi do rezerwacji wizyty.

Z jednym warunkiem - właścicielka miała zmieniać treści sama, bez pisania do nas o każdą literówkę w cenniku. I to właśnie ten warunek zdecydował o wyborze Payloada.

Siedem kolekcji zamiast siedmiu wtyczek

Cały panel to siedem kolekcji zadeklarowanych w TypeScripcie:

  • Products - kosmetyki dostępne w sklepie
  • Pricings - cennik zabiegów; to dokładnie ta struktura, którą pokazywaliśmy wyżej przy okazji Pricing
  • Packages - pakiety usług, gdzie pojedynczy pakiet zawiera listę zabiegów jako pole typu array
  • Galleries - zdjęcia z realizacji
  • Media - wszystkie assety w jednym miejscu, czyli DAM z początku artykułu
  • Orders - zamówienia ze sklepu
  • Users - dostęp do panelu

Każda z nich to jeden plik z definicją pól - żadnych wtyczek, żadnych custom post types, żadnego ACF. Nowe pole w pakiecie to dopisanie kilku linijek do tablicy fields, a nie polowanie na dodatek, który akurat robi to, o co chodzi.

Edycja pakietu w kolekcji Packages - lista zabiegów jako pole typu array, slug generowany automatycznie z nazwy. Źródło: https://sugar-madam.pl
Edycja pakietu w kolekcji Packages - lista zabiegów jako pole typu array, slug generowany automatycznie z nazwy. Źródło: https://sugar-madam.pl

Co dostaliśmy z pudełka, a czego nie musieliśmy pisać

Sklep obsługuje Stripe. Po opłaceniu zamówienia klientka i studio dostają maile potwierdzające, a samo zamówienie ląduje w kolekcji Orders, czyli w tym samym panelu co treść. Nie trzeba przełączać się między sklepem a CMS-em, żeby sprawdzić, co się sprzedało.

Logowanie do panelu, sesje i odzyskiwanie hasła to dokładnie ten mechanizm, który opisywaliśmy wyżej - nie napisaliśmy do niego ani linijki.

Efekt

Właścicielka samodzielnie dodaje produkty, zmienia cennik i wrzuca zdjęcia do galerii. My wracamy do projektu wtedy, gdy zmienia się struktura, a nie treść - i to jest uczciwa granica tego rozwiązania, o której pisaliśmy w sekcji o przewadze WordPressa. Nowe pole czy nowy typ sekcji to zadanie dla dewelopera i redeploy. Nowy produkt, nowa cena, nowe zdjęcie - to już nie nasza sprawa i o to właśnie chodziło.

Całość opisaliśmy szerzej w case study Sugar Madam, a efekt na żywo zobaczysz na sugar-madam.pl.

Podsumowanie

Czy Payload zastąpi WordPressa? Nie zawsze i nie wszędzie - ale coraz częściej warto policzyć go jako realną opcję, zamiast odruchowo instalować WP.

WordPress wygrywa tam, gdzie liczy się budżet, czas i to, że klient zna ten panel od lat. Wizytówka, blog firmowy, mały sklep na gotowym motywie - trudno zrobić to taniej i szybciej.

Payload wygrywa wtedy, gdy projekt ma własną logikę: nietypowe struktury danych, sklep z regułami, których nie da się wyklikać, kilka języków, uprawnienia skrojone pod konkretny zespół. Zamiast szukać wtyczki, która „prawie” robi to, o co chodzi, deklarujesz dokładnie to, czego potrzebujesz - w TypeScripcie, w jednym miejscu i z pełną kontrolą nad tym, co trafia do builda. Płacisz za to czasem na starcie oraz tym, że każda zmiana struktury wraca do dewelopera.

W praktyce pytanie nie brzmi więc „co jest lepsze”, tylko „czy ten projekt będzie rósł i w którą stronę”.

Jeśli ma pozostać stroną - WordPress w zupełności wystarczy. Jeśli ma stać się produktem, Payload zwraca się przy pierwszym poważniejszym wymaganiu, którego nie da się załatwić wtyczką.

Zastanawiasz się, po której stronie tej granicy leży Twój projekt? Napisz do nas - pomożemy to rozstrzygnąć, zanim zapadnie decyzja technologiczna.

Materiały źródłowe