Contentful, headless CMS dla dużych treści
Contentful powstał w Berlinie w 2013 roku i przez lata był domyślną odpowiedzią na pytanie, gdzie trzymać treść, jeśli front pisze się w JavaScripcie. Model jest prosty: redaktor pracuje w panelu, aplikacja pobiera treść przez API, a warstwa prezentacji nie ma z zapleczem nic wspólnego poza kontraktem danych.
W czerwcu 2026 roku Salesforce podpisał umowę przejęcia firmy. Dla istniejących wdrożeń nic się nie zmieniło z dnia na dzień, ale przy wyborze platformy na kolejne pięć lat to okoliczność, którą trzeba wziąć pod uwagę razem z ceną i możliwościami.
Transakcja nie została jeszcze zamknięta. Kupujący zapowiedział zamknięcie na trzeci kwartał swojego roku obrotowego 2027, czyli między sierpniem a końcem października 2026, pod warunkiem zgód regulacyjnych. Warunki finansowe nie zostały ujawnione. Do tego czasu obie firmy działają osobno, a deklaracje o przyszłości produktu warto czytać z tą datą w tle.
Jak to działa
Treść opisujesz typami, a nie stronami. Typ to definicja pól, na przykład artykuł z tytułem, treścią sformatowaną, obrazem i odnośnikiem do autora. Redaktor tworzy wpisy tego typu, a Ty pobierasz je przez API i renderujesz, jak chcesz.
Ta zmiana perspektywy jest sednem podejścia bezgłowego. W klasycznym systemie zarządzania treścią wpis wie, jak ma wyglądać. Tutaj wpis wie tylko, czym jest, a wygląd należy do aplikacji, więc ta sama treść zasila stronę, aplikację mobilną i newsletter bez duplikowania.
Platforma udostępnia kilka osobnych interfejsów, co bywa mylące na starcie. Delivery API zwraca treść opublikowaną i jest tym, którego używa aplikacja. Preview API zwraca również wersje robocze i służy do podglądu. Management API pozwala tworzyć i modyfikować typy oraz wpisy z poziomu kodu. Images API skaluje i konwertuje obrazy w locie.
Kluczowa zasada bezpieczeństwa wynika wprost z tego podziału. Klucz do Delivery API jest tylko do odczytu i może trafić do przeglądarki. Klucz do Management API pozwala skasować całą przestrzeń, więc żyje wyłącznie po stronie serwera i nigdy nie ląduje w zmiennej z przedrostkiem publicznym.
Pierwsze pobranie treści
pnpm add contentfulimport { createClient } from 'contentful'
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_DELIVERY_TOKEN!
})
export async function pobierzArtykuly(limit = 10) {
const wynik = await client.getEntries({
content_type: 'article',
order: ['-fields.publishedAt'],
limit
})
return wynik.items
}Pojedynczy wpis pobierasz po polu identyfikującym, nie po wewnętrznym identyfikatorze, bo ten drugi nie nadaje się do adresu URL.
export async function pobierzArtykul(slug: string) {
const wynik = await client.getEntries({
content_type: 'article',
'fields.slug': slug,
limit: 1,
include: 2
})
return wynik.items[0] ?? null
}Parametr include decyduje, ile poziomów powiązań przyjdzie razem z wpisem. Wartość domyślna to jeden poziom, więc autor artykułu przyjdzie, ale zdjęcie autora już nie. Dwa poziomy zwykle wystarczają, a większe wartości powiększają odpowiedź szybciej, niż się spodziewasz.
Integracja z Next.js
Najczęstszy układ to generowanie stron przy budowaniu i odświeżanie ich w tle. W Next.js z routerem aplikacji sprowadza się to do jednej opcji przy pobieraniu.
export const revalidate = 3600
export async function generateStaticParams() {
const wynik = await client.getEntries({ content_type: 'article' })
return wynik.items.map((wpis) => ({ slug: wpis.fields.slug as string }))
}
export default async function Strona({ params }: { params: { slug: string } }) {
const artykul = await pobierzArtykul(params.slug)
if (!artykul) notFound()
return <Artykul dane={artykul} />
}Godzina odświeżania to punkt wyjścia, nie reguła. Jeśli redakcja publikuje kilka razy dziennie i oczekuje natychmiastowego efektu, lepiej dołożyć webhook wywołujący przebudowę konkretnej ścieżki po publikacji. Wtedy odświeżanie czasowe zostaje wyłącznie jako zabezpieczenie.
Treść sformatowana wraca jako struktura drzewa, nie jako HTML. To celowe, bo pozwala renderować akapity własnymi komponentami, ale wymaga osobnego pakietu i mapowania węzłów. Najczęstszy błąd na tym etapie to wstawianie surowego HTML z pola tekstowego, co otwiera wektor ataku, którego typ pola miał uniknąć.
Podgląd wersji roboczej
Redakcja prędzej czy później poprosi o możliwość zobaczenia niepublikowanego wpisu. Rozwiązanie polega na drugim kliencie wskazującym host podglądu i osobnym tokenie.
const klientPodgladu = createClient({
space: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN!,
host: 'preview.contentful.com'
})Wybór klienta zależy od trybu, w którym działa żądanie. Ważne, żeby tryb podglądu nie był domyślny ani włączany zwykłym parametrem w adresie, bo wtedy każdy zobaczy szkice. Standardowe rozwiązanie to podpisany tryb draft aktywowany osobną trasą, którą panel otwiera z sekretem.
Warto też pamiętać, że treść podglądowa nie jest buforowana na tych samych zasadach co opublikowana. Strona w trybie podglądu ma być renderowana na żądanie, inaczej redakcja zobaczy poprzednią wersję i zgłosi to jako błąd systemu.
Modelowanie treści, czyli miejsce, gdzie decyduje się wszystko
Największe koszty w projektach na tej platformie nie biorą się z ceny abonamentu, tylko ze źle zaprojektowanych typów. Zmiana modelu po roku publikowania oznacza migrację tysięcy wpisów.
Trzy zasady oszczędzają najwięcej. Po pierwsze, nie twórz typu na każdą stronę. Strona to układ, a układ powinien składać się z komponentów, które da się użyć ponownie. Po drugie, unikaj pól odpowiadających wyglądowi, takich jak kolor tła czy szerokość kolumny, bo za rok układ się zmieni, a pole zostanie. Po trzecie, waliduj pola od początku, bo dodanie wymagalności do pola wypełnionego w połowie wpisów to osobny projekt.
Osobnym tematem są języki. Platforma obsługuje wielojęzyczność na poziomie pola, więc jeden wpis trzyma wszystkie wersje. To wygodne przy tłumaczeniach, ale znaczy, że pobranie wpisu bez wskazania języka zwraca wszystkie warianty naraz i niepotrzebnie powiększa odpowiedź.
Przydatnym testem projektu modelu jest pytanie, ile typów musisz zmienić, żeby dodać nową sekcję na stronie głównej. Jeśli odpowiedź brzmi „jeden", model jest w porządku. Jeśli trzeba ruszyć typ strony, typ sekcji i typ układu naraz, warstwy są ze sobą splecione mocniej, niż powinny.
Warto też wcześnie ustalić, co jest treścią, a co konfiguracją. Nazwa firmy w stopce, adres i numer telefonu to treść, którą redakcja powinna móc zmienić. Klucz do usługi zewnętrznej albo próg paginacji to konfiguracja i miejsce dla zmiennych środowiskowych, nie dla wpisu w panelu.
Ostatnia sprawa to nazewnictwo pól. Identyfikator pola trafia wprost do kodu, więc nazwa nadana pod wpływem chwili zostaje na lata, a jej zmiana wymaga migracji wpisów i poprawki po stronie aplikacji. Kilka minut na ustalenie konwencji na początku oszczędza znacznie więcej później.
Ceny i to, co w nich zaskakuje
| Plan | Koszt | Dla kogo |
|---|---|---|
| Free | 0 USD | Jedna przestrzeń, projekt prywatny, nauka |
| Lite | 300 USD miesięcznie | Zespół produktowy z ruchem produkcyjnym |
| Premium | wycena indywidualna | Wiele marek, zaawansowane role, dodatki |
Skok z planu darmowego na pierwszy płatny jest tym, co najczęściej zaskakuje. Nie ma pośredniego progu za sto dolarów, więc projekt, który przerasta plan darmowy, od razu trafia w rachunek rzędu trzech i pół tysiąca dolarów rocznie. Kolejny próg to już wycena indywidualna, bez podanej stawki.
Do tego dochodzą pozycje rozliczane osobno. Dodatkowe przestrzenie kosztują, narzędzie do wizualnego składania stron jest dodatkiem, a personalizacja treści również. Przy szacowaniu budżetu policz nie sam plan, lecz plan plus przewidywane dodatki, bo to one najczęściej podwajają kwotę.
Trzecia rzecz to limity wywołań API. Plan darmowy ma ograniczoną liczbę zapytań, a aplikacja bez buforowania po stronie serwera potrafi je wyczerpać samym ruchem robotów wyszukiwarek. Generowanie statyczne rozwiązuje ten problem niemal całkowicie, bo pobrania dzieją się przy budowaniu, a nie przy każdej wizycie.
Obrazy i pliki
Zasoby graficzne trafiają do osobnego magazynu i wracają z adresem, do którego można dopisać parametry przekształceń. Skalowanie, przycięcie i zmiana formatu dzieją się po stronie usługi, więc nie potrzebujesz własnego przetwarzania.
const miniatura = `${obraz.fields.file.url}?w=640&fm=webp&q=75&fit=fill`Trzy parametry robią większość roboty. Szerokość ogranicza rozmiar pobrania, format nowoczesny zbija wagę pliku o połowę wobec formatu klasycznego, a jakość na poziomie siedemdziesięciu pięciu jest praktycznie nieodróżnialna od stu przy zauważalnie mniejszym pliku.
Adres zwracany przez API zaczyna się od dwóch ukośników bez protokołu. Komponent obrazu w Next.js tego nie przyjmie, więc protokół trzeba dopisać, a domenę zasobów dodać do listy dozwolonych w konfiguracji. To jedna z pierwszych rzeczy, o którą rozbija się wdrożenie.
Warto też wymagać tekstu alternatywnego na poziomie modelu, a nie w wytycznych dla redakcji. Pole opisowe oznaczone jako wymagane przy typie zasobu daje dostępność i lepsze indeksowanie bez przypominania komukolwiek o czymkolwiek.
Webhooki i automatyzacja
Publikacja wpisu może uruchomić dowolne żądanie HTTP. Najczęstsze zastosowanie to odświeżenie ścieżki na hostingu, ale ten sam mechanizm nadaje się do powiadomień, indeksowania w wyszukiwarce po stronie aplikacji albo synchronizacji z magazynem produktów.
export async function POST(request: Request) {
const sekret = request.headers.get('x-contentful-secret')
if (sekret !== process.env.WEBHOOK_SECRET) {
return new Response('Brak dostepu', { status: 401 })
}
const dane = await request.json()
revalidatePath(`/blog/${dane.fields.slug['en-US']}`)
return Response.json({ ok: true })
}Weryfikacja sekretu jest obowiązkowa. Trasa przebudowująca strony bez zabezpieczenia to zaproszenie do wywołania jej w pętli przez kogokolwiek, kto zna adres.
Drugi element to filtrowanie zdarzeń po stronie panelu. Webhook nasłuchujący wszystkiego wywoła się także przy zapisie szkicu, co odświeży stronę treścią, której nikt nie zatwierdził. Ustaw wyzwalacz wyłącznie na publikację i cofnięcie publikacji.
Platforma dorzuciła też akcje oparte na modelach językowych, działające wprost w edytorze: wersje robocze tłumaczeń, opisy alternatywne obrazów, metadane oraz skracanie tekstu. Traktuj je jako punkt wyjścia dla redaktora, a nie jako gotowy tekst, bo publikowanie bez czytania zwraca się problemami po stronie treści.
Contentful kontra alternatywy
| Narzędzie | Mocna strona | Słabość | Kiedy wybrać |
|---|---|---|---|
| Contentful | Dojrzałość, role, wielojęzyczność, stabilne API | Duży skok cenowy, dodatki płatne osobno | Duża organizacja, wiele kanałów, redakcja nietechniczna |
| Supabase | Baza, uwierzytelnianie i pliki w jednym, niski koszt | Brak panelu redakcyjnego dla treści | Zespół techniczny budujący własny panel |
| Sanity | Panel jako kod, elastyczne zapytania | Krzywa uczenia przy własnym panelu | Projekt wymagający nietypowego układu redakcji |
| Strapi | Instalacja u siebie, pełna kontrola nad danymi | Utrzymanie serwera po Twojej stronie | Wymóg trzymania treści we własnej infrastrukturze |
Wybór sprowadza się do pytania, kto pracuje z treścią na co dzień. Jeśli redakcja jest duża, nietechniczna i pracuje w kilku językach, dojrzały panel i system uprawnień są warte swojej ceny. Jeśli treść dodaje trzech programistów, ten sam panel jest kosztem bez odpowiadającej korzyści.
Typowe błędy
Pierwszy to pobieranie treści po stronie klienta na stronie, która mogłaby być statyczna. Każda wizyta oznacza wtedy wywołanie API, wolniejsze pierwsze renderowanie i szybsze wyczerpanie limitu.
Drugi to token zarządzania w zmiennej publicznej. Przedrostek udostępniający zmienną przeglądarce przy tokenie zapisu oznacza, że dowolna osoba może zmodyfikować treść. Ten błąd zdarza się przy kopiowaniu konfiguracji z jednego projektu do drugiego.
Trzeci to brak obsługi braku wpisu. Zapytanie o nieistniejący identyfikator zwraca pustą listę, a nie błąd, więc kod sięgający po pierwszy element bez sprawdzenia wywala się dopiero na produkcji.
Czwarty to zbyt głębokie powiązania. Wartość include ustawiona na maksimum zwraca drzewo, którego rozmiar rośnie wykładniczo i spowalnia zarówno odpowiedź, jak i renderowanie.
Piąty to traktowanie treści sformatowanej jak HTML. Struktura drzewa wymaga mapowania na komponenty, a skrót polegający na wstawieniu surowego kodu do strony przekreśla bezpieczeństwo, które daje typowane pole.
Szósty to brak planu na migrację modelu. Zanim opublikujesz pierwsze sto wpisów, sprawdź, czy typy wytrzymają kolejny etap projektu, bo później każda zmiana to skrypt i okno serwisowe.
FAQ
Czy Contentful ma darmowy plan?
Tak, plan darmowy obejmuje jedną przestrzeń z własnym modelem treści i wystarcza do nauki, prototypu oraz małego projektu. Ograniczenia dotyczą liczby użytkowników, wpisów i wywołań API. Kolejny próg to plan Lite za 300 dolarów miesięcznie, bez opcji pośredniej.
Jak Contentful wypada przy Next.js?
Bardzo dobrze przy generowaniu statycznym z odświeżaniem w tle. Strony powstają przy budowaniu, aplikacja nie odpytuje API przy każdej wizycie, a redakcja widzi zmiany po odświeżeniu ścieżki wywołanym webhookiem. Ten układ jest zarazem najszybszy i najtańszy pod względem limitów.
Co oznacza przejęcie przez Salesforce?
Umowę podpisano w czerwcu 2026 roku. Dla działających wdrożeń nie zmienia to nic natychmiast, ale przy planowaniu na kilka lat warto założyć możliwe zmiany w cenniku i w kierunku rozwoju produktu, typowe po zmianie właściciela.
Kiedy lepiej wybrać coś innego?
Gdy treść dodaje wyłącznie zespół techniczny, gdy budżet nie obejmuje kilku tysięcy dolarów rocznie albo gdy dane muszą zostać we własnej infrastrukturze. W tych przypadkach Strapi albo baza z własnym panelem wychodzą taniej i dają więcej kontroli.
Czy trzeba używać GraphQL?
Nie, interfejs REST pokrywa te same możliwości i ma prostszy klient. GraphQL wygrywa, gdy jedna strona potrzebuje wielu powiązanych typów naraz, bo pozwala pobrać je jednym zapytaniem zamiast kilku, a przy okazji ogranicza rozmiar odpowiedzi do pól, których faktycznie używasz.
Szczegóły interfejsów opisuje dokumentacja Contentful, a aktualne progi cenowe znajdziesz na stronie cennika.