Przejdź do treści

Jak połączyć Astro z Sanity CMS? Przewodnik po ultra-szybkim blogu

Lighthouse 100/100 w Astro i Sanity? Tak, to możliwe. Poznaj architekturę wysp i GROQ, dzięki którym Twój blog będzie działał błyskawicznie.

Maciej Sala

Founder StriveLab

9 min czytaniaOpublikowano 8 czerwca 2026 (Aktualizacja 7 lipca 2026)

Astro i Sanity CMS — dlaczego warto połączyć te technologie

Kombinacja tych dwóch narzędzi jest mocna, ponieważ każde z nich robi coś innego i ich role się nie nakładają. Sanity dostarcza ustrukturyzowaną treść, a Astro zamienia ją w czysty HTML bez zbędnego JavaScriptu.

Sekret tkwi w (Astro Islands). Klasyczny framework typu wysyła do przeglądarki cały JavaScript potrzebny do zbudowania strony, nawet jeśli to zwykły artykuł, który nic nie robi po załadowaniu. Astro odwraca tę logikę: domyślnie renderuje wszystko do statycznego HTML-a i nie wysyła żadnego JavaScriptu. Interaktywność dokładasz tylko punktowo, używając do tego celu wyspy (np. menu mobilne czy formularz), które się , podczas gdy reszta strony pozostaje czystym, lekkim HTML-em.

Dla strony opartej o treść to idealne dopasowanie, ponieważ treść z Sanity i tak nie potrzebuje interaktywności — to po prostu tekst, obrazy i nagłówki. Astro pobiera te dane raz, podczas builda, a potem zmienia je w gotowe pliki HTML. W efekcie strona ładuje się błyskawicznie, a Lighthouse pokazuje wyniki w okolicach 100/100 praktycznie od razu. Dzieje się tak ponieważ nie ma czego ładować poza HTML-em i CSS-em.

Jak połączyć Astro z Sanity CMS krok po kroku

Sanity utrzymuje oficjalną integrację @sanity/astro, więc nie wymyślasz koła na nowo. Instalacja to jedna komenda:

Code
npx astro add @sanity/astro @astrojs/react

@astrojs/react jest potrzebny, jeśli chcesz korzystać z wizualnej edycji albo osadzić Sanity Studio na trasie w projekcie Astro. Warto przy okazji dograć pakiety pomocnicze:

Code
npm install astro-portabletext @sanity/image-url groq
  • astro-portabletext — renderuje Portable Text (format tekstu sformatowanego z Sanity) do HTML-a,
  • @sanity/image-url — buduje URL-e obrazów z transformacjami z -u Sanity,
  • groq — eksportuje defineQuery do typowanych zapytań.

Konfigurację dodajesz w astro.config.mjs:

Code
// astro.config.mjs
import { defineConfig } from 'astro/config'
import sanity from '@sanity/astro'
 
export default defineConfig({
  integrations: [
    sanity({
      projectId: 'YOUR_PROJECT_ID', // znajdziesz na sanity.io/manage
      dataset: 'production',
      apiVersion: '2026-03-01', // wymagane dla przewidywalnych zapytań
      useCdn: false, // false przy buildzie statycznym i draftach
    }),
  ],
})

W produkcji projectId, dataset i tokeny trzymaj w zmiennych środowiskowych. Sam projectId nie jest sekretem, ale jeden spójny sposób konfiguracji ogranicza ryzyko pomyłek między środowiskiem testowym i produkcyjnym.

Integracja wystawia gotowego klienta Sanity jako wirtualny moduł sanity:client, którego importujesz w dowolnym komponencie i nie musisz ręcznie konfigurować połączenia w każdym pliku. Dla TypeScript warto dodać też deklarację typów:

Code
// src/env.d.ts
/// <reference types="astro/client" />
/// <reference types="@sanity/astro/module" />

Gdzie mieszka Sanity Studio i schemat treści

Zanim przejdziemy dalej, muszę zwrócić uwagę na jedną sprawę: Sanity Studio, czyli panel, w którym redaktor tworzy treść, to osobna aplikacja i trzeba zdecydować, gdzie będzie funkcjnować. Istnieją dwie drogi.

Klasycznym rozwiązaniem jest osobny projekt Studio. Uruchamiasz npm create sanity@latest, kreator zakłada projekt w Sanity (dostajesz projectId) i generuje aplikację Studio z folderem na schematy. Wtedy plik sanity/schemas/post.js z następnej sekcji trafia właśnie tam, a Studio wystawiasz osobno (np. npx sanity deploy daje darmowy hosting pod *.sanity.studio).

Studio osadzone w projekcie Astro. Bardzo wygodne, gdy chcesz mieć wszystko w jednym repozytorium. Integracja @sanity/astro montuje Studio na wskazanej trasie przez opcję studioBasePath (to do tego potrzebny był @astrojs/react):

Code
// astro.config.mjs — fragment integracji
sanity({
  // ...konfiguracja jak wyżej
  studioBasePath: '/admin',
}),

Przy tym wariancie w korzeniu projektu Astro tworzysz jeszcze sanity.config.ts, który definiuje Studio i rejestruje schematy:

Code
// sanity.config.ts
import { defineConfig } from 'sanity'
import { structureTool } from 'sanity/structure'
import post from './sanity/schemas/post'
 
export default defineConfig({
  projectId: 'YOUR_PROJECT_ID',
  dataset: 'production',
  plugins: [structureTool()],
  schema: { types: [post] },
})

Dla małego bloga wariant osadzony jest najprostszy, czyli będziemy mieli w ten sposób panel pod /admin, schematy w tym samym repo co frontend. Osobne Studio wybierz, gdy nad treścią pracuje większy zespół albo z jednego Sanity korzysta kilka frontendów. Tylko wtedy ma to sens.

Minimalny model wpisu w Sanity

Zanim Astro zacznie pobierać dane, Sanity musi wiedzieć, jak wygląda wpis blogowy. Minimalny model powinien zawierać tytuł, slug, opis, datę publikacji, obraz główny i treść w Portable Text:

Code
// sanity/schemas/post.js
import { defineField, defineType } from 'sanity'
 
export default defineType({
  name: 'post',
  title: 'Wpis',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      title: 'Tytuł',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: { source: 'title', maxLength: 96 },
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'excerpt',
      title: 'Opis SEO',
      type: 'text',
      rows: 3,
    }),
    defineField({
      name: 'publishedAt',
      title: 'Data publikacji',
      type: 'datetime',
    }),
    defineField({
      name: 'mainImage',
      title: 'Obraz główny',
      type: 'image',
      options: { hotspot: true },
      fields: [
        defineField({
          name: 'alt',
          title: 'Tekst alternatywny',
          type: 'string',
        }),
      ],
    }),
    defineField({
      name: 'body',
      title: 'Treść',
      type: 'array',
      of: [{ type: 'block' }, { type: 'image' }],
    }),
  ],
})

To jest dość prosty schemat, ale w zupełności wystarczający dla bloga. W większym projekcie dołożysz autora, kategorie, tagi, kanoniczny URL, status publikacji, powiązane wpisy albo pola pod Open Graph. Zrób to najlepiej od razu, by nie rozgrzebywać tego tematu dwa razy — szkoda czasu.

Jak pobierać dane z Sanity CMS w Astro za pomocą GROQ

Tu wkracza , czyli język zapytań Sanity. Jest trochę jak dla grafu dokumentów JSON: filtrujesz, sortujesz, robisz projekcje i joiny w jednym zapytaniu. Sanity wystawia też , jeśli masz takie preferencje, ale GROQ jest natywny i zwykle zwięźlejszy. Wybór należy do Ciebie.

W Astro pobierasz dane bezpośrednio we komponentu, czyli w bloku między ---, który wykonuje się na serwerze podczas builda i nigdy nie trafia do przeglądarki:

Code
---
// src/pages/blog/index.astro
import { sanityClient } from 'sanity:client'
import BaseLayout from '../../layouts/BaseLayout.astro'
 
const posts = await sanityClient.fetch(
  `*[_type == "post" && defined(slug.current)] | order(publishedAt desc) {
    title,
    "slug": slug.current,
    excerpt,
    publishedAt
  }`
)
---
 
<BaseLayout title="Blog">
  <h1>Blog</h1>
  <ul>
    {posts.map((post) => (
      <li>
        <a href={`/blog/${post.slug}`}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </a>
      </li>
    ))}
  </ul>
</BaseLayout>

To zapytanie GROQ czyta się tak: weź wszystkie dokumenty typu post, które mają sluga, posortuj od najnowszego i zwróć tylko te cztery pola. Cała ta logika wykonuje się raz, podczas builda, a potem użytkownik dostaje gotowy HTML.

Strona pojedynczego wpisu z getStaticPaths

Lista wpisów to dopiero połowa bloga, podczas gdy druga połowa to dynamiczna trasa src/pages/blog/[slug].astro, która podczas builda generuje osobną stronę dla każdego sluga z Sanity:

Code
---
// src/pages/blog/[slug].astro
import { PortableText } from 'astro-portabletext'
import { sanityClient } from 'sanity:client'
import BaseLayout from '../../layouts/BaseLayout.astro'
import { urlFor } from '../../lib/sanityImage'
 
export async function getStaticPaths() {
  const posts = await sanityClient.fetch(
    `*[_type == "post" && defined(slug.current)] {
      title,
      "slug": slug.current,
      excerpt,
      publishedAt,
      body,
      mainImage {
        ...,
        alt
      }
    }`
  )
 
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post },
  }))
}
 
const { post } = Astro.props
 
const imageUrl = post.mainImage
  ? urlFor(post.mainImage).width(1200).height(630).fit('crop').auto('format').url()
  : null
---
 
<BaseLayout title={post.title} description={post.excerpt}>
  <article>
    <h1>{post.title}</h1>
 
    {imageUrl && (
      <img
        src={imageUrl}
        alt={post.mainImage?.alt ?? ''}
        width="1200"
        height="630"
        loading="eager"
        fetchpriority="high"
      />
    )}
 
    <PortableText value={post.body} />
  </article>
</BaseLayout>

To jest moment, w którym widać jak dobry jest Astro. getStaticPaths() pobiera wszystkie wpisy jednym zapytaniem GROQ i przekazuje każdy z nich do właściwej strony przez props — dzięki temu build nie odpytuje Sanity osobno dla każdego sluga (klasyczny problem N+1), co przy setkach artykułów wyraźnie skraca czas budowania. Każda strona wpisu powstaje jako gotowy HTML: użytkownik nie czeka na Sanity, bo w trybie statycznym CDN serwuje gotowe pliki i nie odpytuje CMS-a przy każdej wizycie, a crawler dostaje treść od razu w odpowiedzi.

Portable Text i obrazy z Sanity

Treść z Sanity nie jest Markdownem ani HTML-em, ponieważ domyślny edytor zapisuje ją jako Portable Text, czyli strukturalny JSON. Dzięki temu treść jest przenośna, ale musisz ją wyrenderować po stronie Astro.

Najprostszy wariant to komponent PortableText:

Code
---
import { PortableText } from 'astro-portabletext'
 
const body = post.body
---
 
<PortableText value={body} />

Do obrazów użyj @sanity/image-url, żeby korzystać z transformacji Sanity CDN i nie wstawiać surowych assetów bez kontroli rozmiaru:

Code
// src/lib/sanityImage.js
import imageUrlBuilder from '@sanity/image-url'
import { sanityClient } from 'sanity:client'
 
const builder = imageUrlBuilder(sanityClient)
 
export function urlFor(source) {
  return builder.image(source)
}

Wtedy w komponencie możesz wygenerować obraz dopasowany do layoutu:

Code
<img
  src={urlFor(post.mainImage).width(1200).height(630).fit('crop').auto('format').url()}
  alt={post.mainImage.alt ?? ''}
  width="1200"
  height="630"
/>

To drobiazg, który ma duże znaczenie dla Core Web Vitals. Jeśli wrzucisz oryginalny obraz 4000 px z biblioteki mediów, Astro nadal będzie szybkie, ale LCP może ucierpieć przez zbyt ciężki asset.

Skoro gramy pod wyniki w Google, domknij też warstwę SEO: dołóż oficjalną integrację @astrojs/sitemap (jedna pozycja w astro.config.mjs generująca sitemap.xml przy buildzie), a link kanoniczny i tagi Open Graph generuj w layoutcie z pól wpisu — obraz OG masz już gotowy dzięki transformacjom urlFor w rozmiarze 1200×630.

SSG czy SSR w Astro z Sanity CMS — który tryb wybrać?

Wybór sposobu renderowania wpływa na to, kiedy treść z Sanity trafia na stronę:

(domyślny) — każde zapytanie GROQ wykonuje się raz, podczas builda, a strony serwowane są jako statyczne pliki. Idealny, gdy treść nie zmienia się co minutę i możesz pozwolić sobie na chwilę zwłoki między publikacją a przebudową (zwykle wyzwalaną z Sanity). To wybór dla większości blogów i stron firmowych, ponieważ jest najszybszy i najtańszy.

Renderowanie na żądanie () — strony renderują się przy każdym żądaniu, więc zapytania GROQ działają w czasie rzeczywistym i zmiany widać natychmiast, bez przebudowy. Potrzebujesz do tego adaptera pod swój hosting (np. Vercel). Sięgasz po niego, gdy potrzebujesz podglądu draftów, wizualnej edycji albo treści personalizowanej.

W praktyce najczęściej wybierzesz model mieszany — i w nowszych wersjach Astro nie wymaga on przełączania całego projektu na tryb serwerowy. Zostajesz przy domyślnym buildzie statycznym, a trasom, które mają renderować się na żądanie (podgląd draftów, Visual Editing), wyłączasz prerendering wpisem export const prerender = false we frontmatterze (plus adapter). Publiczny blog pozostaje statyczny i nie płacisz wydajnością całej strony tylko dlatego, że redakcja potrzebuje wygodnego podglądu.

Przy okazji domknij regułę useCdn z konfiguracji: dla zapytań wykonywanych w buildzie zostaw false (najświeższe dane prosto z API), ale jeśli masz trasy SSR odpytujące opublikowaną treść w runtime, tam ustaw true — odpowiedzi lecą wtedy z CDN-u Sanity, szybciej i taniej niż bicie prosto w API przy każdym żądaniu.

Webhook po publikacji w Sanity

Statyczna strona nie zaktualizuje się sama po kliknięciu „Publish" w Sanity. Potrzebuje sygnału, który uruchomi nowy build i najprostszym jest wariant webhook z Sanity do platformy hostingowej:

  1. W panelu Sanity wejdź w projekt i sekcję API / Webhooks.
  2. Utwórz webhook dla dokumentów typu post.
  3. Jako adres podaj build hook z Vercela, Netlify albo Cloudflare Pages.
  4. Ustaw trigger na publikację, aktualizację i usunięcie wpisu.
  5. Dodaj sekret albo podpis żądania, jeśli platforma na to pozwala.

Ten mechanizm domyka cały przepływ, ponieważ redaktor publikuje wpis w Sanity, narzędzie wysyła webhook, platforma uruchamia build Astro, a CDN dostaje świeży HTML. Przy małym i średnim blogu to zwykle prostsze, tańsze i stabilniejsze niż renderowanie każdej strony na żądanie.

Ograniczenia integracji Astro z Sanity CMS

@sanity/astro jest lżejsze niż next-sanity i celowo nie ma kilku jego funkcji.

Nie działa to jak prosty przełącznik „włącz live preview". next-sanity ma bardzo głęboką integrację z Next.js, Live Content API i rewalidacją cache. W Astro standardowy, najprostszy model to statyczny build plus webhook. Visual Editing jest możliwy, ale wymaga osobnego setupu: tokenu do draftów, trybu podglądu, Presentation Tool, stega encoding i tras działających po stronie serwera.

Dla bloga czy strony firmowej nie jest to większy problem, bo publikacja z kilkudziesięciosekundowym opóźnieniem po buildzie jest akceptowalna. Problem pojawia się, gdy budujesz aplikację, w której redakcja musi widzieć zmiany natychmiast, a frontend ma dużo logiki. Wtedy warto rozważyć Next.js z Payload albo Sanity z Next.js.

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

Często zadawane pytania

Czy Astro z Sanity nadaje się do dużego bloga?

Tak. W trybie statycznym Astro pobiera całą treść z Sanity podczas builda i serwuje gotowy HTML, co skaluje się znakomicie nawet przy setkach artykułów. Przy bardzo dużej liczbie stron warto zadbać o szybkie zapytania GROQ i webhooki wyzwalające przebudowę po publikacji.

Czym jest GROQ i czy trudno się go nauczyć?

GROQ (Graph-Relational Object Queries) to język zapytań Sanity, projektowany pod grafy dokumentów JSON. Ma łagodną krzywą uczenia i szybko staje się produktywny — w jednym zapytaniu robisz filtrowanie, sortowanie, projekcje i joiny, które w REST wymagałyby kilku osobnych żądań. Sanity wystawia też GraphQL, jeśli wolisz.

Czy w Astro można osadzić Sanity Studio?

Tak. Integracja @sanity/astro pozwala zamontować Sanity Studio na wybranej trasie (np. /admin) za pomocą opcji studioBasePath. Wymaga to dodatkowo @astrojs/react. Dzięki temu panel edycyjny i strona żyją w jednym projekcie.

Czy zmiany w Sanity pojawiają się na stronie od razu?

W trybie statycznym nie — treść aktualizuje się przy najbliższej przebudowie, zwykle wyzwalanej webhookiem z Sanity po zapisaniu zmiany. W trybie serwerowym (SSR) zmiany widać po odświeżeniu strony. Sanity ma Visual Editing dla Astro, ale wymaga osobnego setupu: draft mode, tokenów, Presentation Tool i serwerowo renderowanego podglądu.

Co to jest architektura wysp (Astro Islands) i dlaczego jest ważna dla wydajności?

Architektura wysp polega na tym, że Astro domyślnie renderuje całą stronę do statycznego HTML bez JavaScriptu. Interaktywność dodajesz tylko punktowo — jako izolowane wyspy komponentów, które się nawadniają (hydrate) w przeglądarce. Dzięki temu strona contentowa ładuje się błyskawicznie, bo przeglądarka nie musi parsować i wykonywać kilobytów JS przy każdym wejściu.

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend 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 rozwijam własne projekty.

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

Skontaktuj się ze mną

Biblioteka wiedzy na temat Astro

Czytaj dalej

Zobacz więcej wpisów
Połączenie Sanity CMS z Next.js — od instalacji po live preview i Visual Editing

Sanity to headless CMS, w którym schemat treści definiujesz w TypeScript — żyje w repozytorium razem z kodem, a nie w GUI. Sanity hostuje backend i API za Ciebie, więc nie potrzebujesz własnego serwera. W tym przewodniku budujesz pełny setup: schema, zapytania GROQ , on-demand ISR przez webhooki, Draft Mode i Visual Editing z Presentation Tool.

Maciej Sala

Maciej Sala

Founder StriveLab

Payload CMS czy Sanity? Który Headless CMS wybrać w 2026 roku?

Payload czy Sanity? Samodzielne hostowanie czy SaaS ? Świetny edytor kolaboracyjny czy pełna kontrola nad danymi i przewidywalne koszty przy skali? Wybór CMS-a to poważna decyzja architektoniczna i jego zmiana np. w połowie projektu jest dużo droższa niż zmiana warstwy widoku. Po podjęciu decyzji żyjesz z jej konsekwencjami przez przynajmniej kilka następnych miesięcy.

Maciej Sala

Maciej Sala

Founder StriveLab

Astro i Headless CMS: Integracja Sanity, Storyblok, Strapi

Markdown w plikach projektu działa sprawnie, dopóki treść piszą osoby, które swobodnie pracują w Git. W sytuacji, kiedy klient chce sam zmienić cennik, a redaktor poprawia nagłówek w piątek po południu, repozytorium przestaje być wygodnym CMS-em. Wtedy najlepszy będzie headless CMS, czyli panel dla redakcji i API dla Astro.

Maciej Sala

Maciej Sala

Founder StriveLab