Przejdź do treści

Integracja Astro i Payload CMS: szybka strona pod kontrolą

Zintegruj Astro z Payload CMS przez REST lub SDK. Zadbaj o szkice, paginację, webhooki, media, bezpieczeństwo i przewidywalne koszty utrzymania.

Maciej Sala

Founder StriveLab

9 min czytaniaAktualizacja

Dlaczego rozdzielenie frontendu i CMS-a pomaga kontrolować koszty?

Rozdzielasz dwa koszty, które w modelu są ze sobą sklejone, czyli koszt serwowania strony oraz koszt zarządzania treścią.

Front w Astro może być po buildzie zbiorem statycznych plików HTML serwowanych przez Vercel, Netlify, Cloudflare lub inną sieć . Takie dostarczanie jest zwykle tanie, ale darmowy plan nie jest gwarancją bezpłatnej obsługi dowolnego ruchu. Sprawdź limity transferu, liczby buildów, czasu CI, funkcji, obrazów i warunki komercyjnego użycia wybranej platformy.

Za zarządzanie treścią może odpowiadać Payload uruchomiony na własnej infrastrukturze. Wersja open source nie nalicza opłat od liczby dokumentów czy wywołań , ale nie oznacza to, że płacisz wyłącznie za jeden VPS. Produkcja zwykle potrzebuje bazy PostgreSQL lub MongoDB, trwałego storage'u, kopii zapasowych, poczty transakcyjnej, TLS, monitoringu i CDN dla mediów. Kosztem jest również czas aktualizacji oraz reagowania na awarie.

W czystej architekturze statycznej Astro pobiera treść podczas builda, a odsłony publicznych stron nie muszą trafiać do Payload. CMS nadal obsługuje jednak redaktorów i każde zapytanie wykonywane przez preview, wyszukiwarkę, formularz, dynamiczną trasę lub media pod jego domeną. Przed doborem serwera rozrysuj wszystkie te ścieżki zamiast zakładać, że ruch użytkowników nigdy nie dotknie CMS-a.

Przy dużej liczbie odsłon statyczny frontend może ograniczyć obciążenie CMS-a. Częste publikacje mogą jednak generować wiele pełnych buildów, a tysiące stron zwiększają czas CI i liczbę zapytań do API. Koszt przesuwa się wtedy z requestów użytkowników na proces publikacji.

Samodzielny hosting pozwala wybrać region i dostawców przetwarzających dane, co może pomóc w projektach regulowanych. Nie zapewnia jednak automatycznie zgodności z . Nadal potrzebujesz podstawy prawnej, polityki retencji, umów powierzenia, kontroli dostępu, szyfrowania, obsługi praw osób oraz procedur kopii zapasowych i incydentów.

Jak wygląda przepływ danych

Najważniejsza różnica względem klasycznego WordPressa polega na tym, że użytkownik końcowy nie odpytuje bazy danych. Payload pracuje w tle jako zaplecze redakcyjne, a Astro wypala gotowy frontend podczas builda.

Praktyczny przepływ wygląda tak:

  1. Redaktor edytuje treść w Payload CMS uruchomionym na wybranej infrastrukturze, a zmiana trafia do bazy danych, np. PostgreSQL albo MongoDB.
  2. Payload zleca przebudowanie frontendu przez zabezpieczony deployment hook platformy takiej jak Vercel, Netlify albo Cloudflare Pages.
  3. Astro uruchamia build, odpytuje Payload przez REST albo GraphQL i pobiera aktualne treści.
  4. Wygenerowane HTML, CSS i assety trafiają na CDN.
  5. Użytkownik pobiera gotowy plik z CDN-u, a VPS z Payload nie bierze udziału w obsłudze każdej wizyty.

Dzięki temu zasoby dla Payload dobierasz głównie do pracy redakcji, zapytań wykonywanych podczas builda i funkcji dynamicznych, a nie do każdej odsłony statycznego frontendu. Rozmiaru instancji nie warto jednak określać bez pomiaru liczby redaktorów, wielkości kolekcji, przetwarzania obrazów i obciążenia bazy.

Jak Astro komunikuje się z Payload CMS

Payload domyślnie wystawia trzy API: REST, i lokalne API (to ostatnie działa tylko wewnątrz aplikacji Node, więc dla zewnętrznego frontu Astro używasz REST lub GraphQL).

Zanim Astro zacznie pobierać dane, trzeba zdefiniować w Payload strukturę treści. Payload jest podejściem code-first: kolekcje opisujesz w kodzie, więc schemat da się wersjonować razem z projektem.

Minimalna kolekcja wpisów może wyglądać tak:

Code
import type { CollectionConfig } from 'payload'
 
export const Posts: CollectionConfig = {
  slug: 'posts',
  admin: {
    useAsTitle: 'title',
  },
  access: {
    read: ({ req }) => {
      if (req.user) return true
 
      return {
        _status: {
          equals: 'published',
        },
      }
    },
  },
  versions: {
    drafts: true,
  },
  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    },
    {
      name: 'slug',
      type: 'text',
      required: true,
      unique: true,
    },
    {
      name: 'content',
      type: 'richText',
      required: true,
    },
    {
      name: 'excerpt',
      type: 'textarea',
      required: true,
    },
    {
      name: 'publishedAt',
      type: 'date',
      required: true,
      admin: {
        position: 'sidebar',
      },
    },
  ],
}

Taka kolekcja automatycznie wystawia endpoint https://cms.example.com/api/posts, który Astro może odpytać podczas budowania strony. Niezalogowany klient zobaczy tylko dokumenty ze statusem _status: published, natomiast redaktor po uwierzytelnieniu może pobrać także szkic. To rozdzielenie trzeba sprawdzić testami API — jeden test bez cookie lub tokenu i drugi z kontem redaktora.

Domyślny limit odpowiedzi Payload wynosi 10 dokumentów, dlatego produkcyjny klient nie powinien zakładać, że jedno zapytanie pobierze całą kolekcję. Przygotuj wspólną funkcję, która obsługuje paginację, ogranicza depth, wybiera potrzebne pola i zatrzymuje build przy błędzie API:

Code
// src/lib/payload.ts
type PayloadPage<T> = {
  docs: T[]
  hasNextPage: boolean
  nextPage: number | null
}
 
export type PostSummary = {
  id: string
  title: string
  slug: string
  excerpt: string
  publishedAt: string
}
 
const apiUrl = import.meta.env.PAYLOAD_API
 
export async function getAllPosts(): Promise<PostSummary[]> {
  const posts: PostSummary[] = []
  let page = 1
 
  do {
    const params = new URLSearchParams({
      limit: '100',
      page: String(page),
      sort: '-publishedAt',
      depth: '0',
      'select[id]': 'true',
      'select[title]': 'true',
      'select[slug]': 'true',
      'select[excerpt]': 'true',
      'select[publishedAt]': 'true',
    })
 
    const response = await fetch(`${apiUrl}/posts?${params}`, {
      signal: AbortSignal.timeout(15_000),
    })
 
    if (!response.ok) {
      throw new Error(`Payload zwrócił HTTP ${response.status}`)
    }
 
    const result = (await response.json()) as PayloadPage<PostSummary>
    posts.push(...result.docs)
 
    if (!result.hasNextPage || result.nextPage === null) break
    page = result.nextPage
  } while (true)
 
  return posts
}

Strona indeksu korzysta teraz z jednej, testowalnej warstwy danych:

Code
---
// src/pages/blog/index.astro
import { getAllPosts } from '../../lib/payload'
 
const posts = await getAllPosts()
---
 
<ul>
  {posts.map((post) => (
    <li>
      <a href={`/blog/${post.slug}`}>
        <h2>{post.title}</h2>
        <p>{post.excerpt}</p>
      </a>
    </li>
  ))}
</ul>

Dla statycznych stron pojedynczych wpisów tę samą funkcję wykorzystaj w getStaticPaths(). Sam indeks nie utworzy plików /blog/[slug].html; każda planowana ścieżka musi znaleźć się w wyniku getStaticPaths() albo być renderowana na żądanie przez adapter Astro.

Code
---
// src/pages/blog/[slug].astro
import { getAllPosts } from '../../lib/payload'
 
export async function getStaticPaths() {
  const posts = await getAllPosts()
 
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post },
  }))
}
 
const { post } = Astro.props
---
 
<article>
  <h1>{post.title}</h1>
  <p>{post.excerpt}</p>
</article>

W prawdziwym wpisie potrzebujesz jeszcze pełnego pola content. Możesz pobrać je jednym paginowanym zapytaniem podczas builda albo przygotować osobną funkcję getPostBySlug(). Drugi wariant zmniejsza pojedynczą odpowiedź, lecz dla tysięcy stron generuje wiele zapytań, dlatego decyzję oprzyj na czasie builda i limitach bazy, a nie na samym rozmiarze JSON-a.

Jeśli wolisz bardziej precyzyjnie kontrolować, jakie pola pobierasz i unikać pobierania zbyt dużej ilości danych (over-fetching), Payload wystawia też GraphQL pod adresem w formacie https://cms.example.com/api/graphql:

Code
---
// fragment frontmattera komponentu .astro
const PAYLOAD_API = import.meta.env.PAYLOAD_API
 
const query = `
  query {
    Posts(limit: 100, sort: "-publishedAt") {
      docs { title slug excerpt publishedAt }
    }
  }
`
 
const res = await fetch(`${PAYLOAD_API}/graphql`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query }),
})
if (!res.ok) throw new Error(`GraphQL zwrócił HTTP ${res.status}`)
 
const { data, errors } = await res.json()
if (errors?.length) throw new Error(errors[0].message)
 
const posts = data.Posts.docs
---

REST także obsługuje select, populate, depth, where, sortowanie i lokalizację, więc GraphQL nie ma monopolu na precyzyjne zapytania. Payload udostępnia ponadto oficjalny pakiet @payloadcms/sdk, który odwzorowuje operacje Local API na typowanym kliencie REST. Możesz przekazać mu wygenerowany typ Config z payload-types.ts, najlepiej przez wersjonowany pakiet współdzielony między repozytoriami.

Typy chronią podczas kompilacji, ale nie potwierdzają odpowiedzi otrzymanej w runtime. Jeżeli frontend i CMS są wdrażane niezależnie, dodaj walidację kluczowych pól na granicy API oraz test kontraktowy uruchamiany przed publikacją.

Renderowanie rich text z Payload w Astro

Pole richText nie jest bezpiecznym fragmentem HTML, który można bezpośrednio przekazać do set:html. Domyślny edytor Lexical zwraca ustrukturyzowane dane, dlatego potrzebujesz renderera mapującego obsługiwane węzły na komponenty Astro lub kontrolowane HTML. Uwzględnij nagłówki, listy, linki, relacje, media i własne bloki, a nieznane typy loguj zamiast pomijać bez śladu.

Jeżeli konwertujesz rich text do HTML po stronie CMS-a, nadal traktuj wynik jak dane wejściowe: ogranicz dozwolone elementy i atrybuty, testuj linki oraz nie renderuj surowego HTML-u pochodzącego od nieuprawnionego użytkownika. Rich text wymaga osobnego kontraktu, tak samo jak REST API.

Jak aktualizować statyczny frontend po zmianie treści

Statyczna strona nie zmieni się sama w chwili publikacji wpisu w CMS-ie i dlatego potrzebny jest webhook, czyli sygnał wysyłany z Payload do platformy hostującej frontend.

Nie wysyłaj deployment hooka bezpośrednio z każdego afterChange. Autosave i szybka seria edycji mogą wtedy uruchomić kilka buildów, a chwilowa awaria dostawcy zgubi zdarzenie. Hook kolekcji powinien jedynie dodać zadanie do kolejki:

Code
hooks: {
  afterChange: [
    async ({ doc, previousDoc, req }) => {
      const wasPublished = previousDoc?._status === 'published'
      const isPublished = doc._status === 'published'
 
      if (wasPublished || isPublished) {
        await queueFrontendSync(req, {
          documentId: doc.id,
          reason: isPublished ? 'publish' : 'unpublish',
        })
      }
 
      return doc
    },
  ],
  afterDelete: [
    async ({ doc, req }) => {
      if (doc._status === 'published') {
        await queueFrontendSync(req, {
          documentId: doc.id,
          reason: 'delete',
        })
      }
 
      return doc
    },
  ],
}

queueFrontendSync() jest tu celowo abstrakcją aplikacyjną. Jej worker powinien scalać zdarzenia z krótkiego okna czasowego, wywołać chroniony sekretem deployment hook, sprawdzić response.ok, ponowić próbę z backoffem i zapisać wynik. Payload ma wbudowaną kolejkę zadań, ale możesz też użyć zewnętrznego systemu zgodnego z infrastrukturą projektu.

Osobno przetestuj publikację, zmianę opublikowanego wpisu, cofnięcie publikacji i usunięcie. Jeżeli włączasz autosave lub scheduled publish, filtruj zdarzenia wersji roboczych tak, aby zwykły zapis szkicu nie przebudowywał strony. Przy dużym serwisie rozważ częściową regenerację lub okresowy build zamiast pełnego wdrożenia po każdej zmianie.

Czego brakuje w tym połączeniu

Nie ma frameworkowego adaptera Payload dla Astro działającego jak integracje osadzone bezpośrednio w jednym środowisku uruchomieniowym. Astro publikuje przewodnik integracji, a Payload oferuje oficjalny @payloadcms/sdk, REST i GraphQL. Nadal samodzielnie projektujesz warstwę danych, renderowanie rich text, preview i mechanizm przebudowy.

Lokalne API Payload pozostaje po stronie Next.js. Oddzielny frontend Astro komunikuje się przez sieć, więc nie korzysta bezpośrednio z Local API ani React Server Components panelu Payload. Nie oznacza to utraty wszystkich typów: SDK i współdzielony payload-types.ts zachowują kontrolę podczas kompilacji, a walidacja runtime chroni granicę między niezależnie wdrażanymi aplikacjami. Jeśli zależy Ci na jednej aplikacji i Local API bez HTTP, zobacz Payload 3.0 i Next.js.

Samodzielne hostowanie przenosi odpowiedzialność na zespół. Aktualizacje, monitoring, bezpieczeństwo, kopie zapasowe i odtwarzanie po awarii muszą mieć właściciela oraz procedurę. Zarządzana usługa może ograniczyć ten zakres, ale nie zwalnia z kontroli dostępu i ochrony danych.

O czym pamiętać na produkcji

Sama integracja przez API to dopiero początek. W produkcyjnym projekcie trzeba zadbać co najmniej o następujące obszary.

Bezpieczny preview dla redaktorów. W modelu SSG wpis jest widoczny publicznie dopiero po buildzie. Podgląd szkiców wymaga trasy renderowanej na żądanie przez adapter Astro albo osobnej usługi preview. Link powinien zawierać krótko ważny, podpisany token lub prowadzić do chronionej sesji. Nie udostępniaj publicznie draft=true; odpowiedź oznacz noindex i no-store, a dostęp sprawdzaj po stronie serwera.

Trwały storage dla mediów. Trzymanie zdjęć i PDF-ów wyłącznie na lokalnym dysku procesu jest ryzykowne, a na platformie z efemerycznym systemem plików może w ogóle nie działać trwale. Użyj adaptera storage, np. do Amazon S3, Cloudflare R2 albo DigitalOcean Spaces, oraz osobnych kopii zapasowych. Skonfiguruj domeny obrazów, CORS, warianty rozmiarów i CDN zgodnie ze sposobem renderowania mediów w Astro.

Produkcja wymaga procesu utrzymania. Potrzebujesz aktualizacji zależności i systemu, TLS i bezpiecznych cookies, silnego PAYLOAD_SECRET, ograniczenia prób logowania, testów kontroli dostępu, monitoringu oraz alertów. Automatyzuj backup bazy i uploadów, ale przede wszystkim regularnie testuj odtworzenie. Zaplanuj też migracje schematu i kolejność wdrożeń CMS–frontend, aby nowa wersja API nie zepsuła trwającego builda.

Kiedy połączenie Astro i Payload jest najbardziej efektywne?

Astro i Payload CMS są skuteczne razem tam, gdzie spotykają się trzy rzeczy: duża ilość treści, duży ruch oraz wymóg kontroli nad danymi.

A tak, mniej ogólnie, a bardziej konkretnie:

  1. Duże portale informacyjne posiadające setki czy nawet tysiące artykułów, ogromny ruch, przy niewielkim koszcie serwowania dzięki statyce na CDN-ie.
  2. Bazy wiedzy i dokumentacje produktowe z ustrukturyzowaną treścią oraz szybkim HTML-em dostępnym bez wykonywania JavaScriptu. Ułatwia to crawling, ale samo użycie Astro nie gwarantuje indeksacji przez Google ani systemy AI.
  3. Strony korporacyjne z wymogami prawnymi, w których organizacja potrzebuje kontroli nad regionem bazy, retencją, dostawcami lub wdrożeniem on-premises.

W tych przypadkach statyczne Astro może ograniczyć koszt obsługi odsłon, a Payload daje kontrolę nad modelem danych i miejscem uruchomienia. Nadal płacisz za infrastrukturę i jej skalowanie, nawet jeśli licencja open source nie nalicza opłat od każdego wywołania API.

Ultraszybkie projekty, łączące lekkość ze skalowalnością.
Astro

Często zadawane pytania

Czy Astro odpytuje Payload przy każdej wizycie użytkownika?

Nie w czystej architekturze statycznej. Astro pobiera treść podczas builda, a CDN serwuje gotowe pliki. Payload nadal obsługuje jednak panel, preview, webhooki i media, jeśli są pod jego domeną. Wyszukiwarka, formularze, personalizacja lub trasy SSR mogą również wykonywać zapytania w czasie wizyty, dlatego koszt zależy od całej architektury.

REST czy GraphQL — co wybrać do integracji Astro z Payload?

Oba działają, a REST także obsługuje select, depth, populate, sortowanie i filtry. Dla większości integracji zacznij od REST lub oficjalnego @payloadcms/sdk, który zapewnia typowane operacje. GraphQL wybierz wtedy, gdy jego schemat i sposób komponowania zapytań realnie upraszczają klienta, a nie wyłącznie po to, by pobierać mniej pól.

Czy istnieje oficjalna integracja Payload dla Astro?

Astro publikuje przewodnik i zasoby społeczności, ale nie ma oficjalnej integracji frameworkowej analogicznej do adaptera renderowania. Payload udostępnia natomiast oficjalny pakiet @payloadcms/sdk do typowanej obsługi REST API. Możesz też użyć zwykłego fetch() albo GraphQL.

Ile kosztuje utrzymanie Astro z Payload CMS przy dużym ruchu?

Nie ma jednej kwoty. Licencja open source nie nalicza opłat za wywołania, ale płacisz za uruchomienie Payload, bazę, trwałe media, kopie zapasowe, pocztę, CDN, monitoring i czas administracji. Hosting statycznego Astro też ma limity transferu, buildów i funkcji. Koszt policz dla własnego ruchu, częstotliwości publikacji i wymaganego SLA.

Kiedy wybrać Astro z Payload zamiast Astro z Sanity?

Payload wybierasz, gdy potrzebujesz kontroli nad infrastrukturą, bazą, rozszerzeniami i kosztami. Samodzielny hosting nie zapewnia jednak automatycznie zgodności z RODO. Sanity bywa wygodniejsze, gdy chcesz oddać utrzymanie platformy dostawcy i cenisz funkcje współpracy redakcyjnej.

O autorze

Maciej Sala

Maciej Sala — konsultant technologiczny produktów cyfrowych i web developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwija własne projekty.

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną
LH.pl – Hosting Mango

Biblioteka wiedzy na temat Astro

Czytaj dalej

Zobacz więcej wpisów
Payload CMS czy Sanity: który headless CMS wybrać?

Payload i Sanity potrafią obsłużyć ten sam katalog treści, lecz przenoszą ciężar projektu w zupełnie inne miejsce. Payload oddaje zespołowi kod, bazę i odpowiedzialność za produkcję. Sanity dostarcza zarządzany Content Lake oraz rozbudowane środowisko współpracy, ale wiąże system z limitami i modelem usługi. Dobre porównanie nie kończy się na ekranie edytora. Musi objąć dzień publikacji, awarię, zmianę schematu, rachunek przy wzroście oraz możliwość wyprowadzenia danych.

Maciej Sala

Maciej Sala

Founder StriveLab

Payload 3.0 i Next.js: rewolucja w budowaniu aplikacji fullstack

Przez lata budowanie aplikacji z headless CMS-em oznaczało to samo, czyli dwa osobne projekty, dwa serwery i sieć pomiędzy nimi. Payload 3.0 kończy z tym podziałem i zamiast stać obok Twojej aplikacji Next.js, żyje w jej środku, w tym samym procesie. Dane pobierasz jak zwykłą funkcję, bez endpointów i zapytań HTTP, zyskując na wydajności i prostocie.

Maciej Sala

Maciej Sala

Founder StriveLab

Migracja z WordPressa do Astro lub Next.js i Headless CMS

Przy migracji z WordPressa do Astro lub Next.js najczęstsza obawa brzmi: czy redakcja straci wygodny panel? Nie, jeśli frontend połączysz z Headless CMS-em takim jak Sanity, Payload albo Storyblok. Poniżej pokazuję, jak zaplanować taką architekturę i przenieść treść bez zmuszania zespołu do pracy w Markdownie.

Maciej Sala

Maciej Sala

Founder StriveLab

LH.pl – Cloud Server 1C4G