Co dają i czym są Content Collections w blogu Astro?
Content Collections stanowią powiązane grupy wpisów, ładowane ze wskazanego źródła i przekazywane do szablonów za pośrednictwem dedykowanego API Astro. Wykorzystany loader glob() odpowiada za odczyt niezależnych plików, podczas gdy file() potrafi rozbić pojedynczy plik JSON na osobne rekordy. Zadeklarowany schemat Zod precyzuje oczekiwaną strukturę i typy pól, na podstawie których Astro automatycznie inferuje statyczne typy dla właściwości post.data.
Dla bloga najważniejsza korzyść to jedno miejsce definiujące kontrakt redakcyjny, ponieważ przykładowo, jeśli karta, artykuł i RSS korzystają z tego samego pola description, brak opisu można łatwo wykryć przed publikacją. Zapobiega to konieczności utrzymywania trzech osobnych parserów nagłówków frontmatter. Warto jednak pamiętać o ograniczeniach samej walidacji, ponieważ nawet poprawny składniowo ciąg znaków może przecież zawierać literówki, merytoryczne błędy czy treść skopiowaną z innego wpisu – schemat nie zweryfikuje jakości danych.
| Potrzeba | Właściwy mechanizm |
|---|---|
| Wymagany tytuł i poprawna data | Schemat Zod |
| Podpowiedzi pól w kodzie szablonu | Typy wygenerowane przez Astro |
| Wykrycie nieistniejącego pola użytego w kodzie | astro check |
| Ukrycie szkiców i zaplanowanych wpisów | Filtr danych używany w trasach i listach |
| Czytelny artykuł oraz poprawność faktów | Redakcja i weryfikacja źródeł |
Założenia i pliki przykładu
Przykład jest przeznaczony dla Astro 7, które korzysta z Zod 4 i importu z z astro/zod. Zachowaj konfigurację astro/tsconfigs/strict oraz wersję Node obsługiwaną przez tę linię Astro, co najmniej 22.12.0. Jeżeli dopiero zakładasz projekt, zacznij od poradnika o pierwszej stronie w Astro. Różnice migracyjne opisuje przewodnik migracji do Astro 7, a najważniejsze zmiany tej wersji omawiam w przeglądzie Astro 7.
Zbudujemy dwa adresy, czyli /blog/ z listą i /blog/pierwszy-wpis/ z artykułem. Plik konfiguracji leży bezpośrednio w src i nie będziemy używać tu starszej konwencji src/content/config.ts z type: 'content'. Podczas aktualizacji wyodrębniłem poniższe pliki do osobnego projektu z Astro 7.3.2 i TypeScriptem 6.0.3. astro check i astro build przeszły poprawnie, sprawdziłem także wariant MDX z integracją 8.0.1 oraz błędne dane opisane w sekcji testów.
Zaczynamy od Markdownu, żeby sam blog nie wymagał komponentów UI ani dodatkowych integracji. Wzorzec loadera obejmie też .mdx, ale przed dodaniem pierwszego takiego pliku zainstaluj zgodne z Astro 7 @astrojs/mdx w wersji 8 i zarejestruj mdx() w integrations w astro.config.mjs. Sam wzorzec glob() nie włącza obsługi MDX. Od Astro 7 pliki .md i .mdx domyślnie przetwarza Sätteri. Jeśli korzystasz z pluginów remark lub rehype, doinstaluj @astrojs/markdown-remark, żeby zachować dotychczasowy pipeline, albo przenieś pluginy na Sätteri. Osadzanie komponentów opisuję w poradniku o MDX w Astro.
Jeden schemat Zod dla wpisów i autorów
Poniższa konfiguracja stanowi wspólny fundament dla całego przykładu. Schemat wymusza redakcyjne limity długości dla tytułu i opisu, unifikuje tagi w ramach jednej listy oraz definiuje pole author jako relację do osobnej kolekcji. Warto zauważyć, że każdy nowy wpis traktowany jest domyślnie jako wersja robocza — jego oficjalna publikacja wymaga więc jawnego ustawienia draft: false.
Limity 100 i 200 znaków oczywiście nie są wymogiem Google, ale jakąś umowną wartością przyjętą w redakcji. Pozwalają ograniczyć przypadkowo długie pola, ale też trzeba pamiętać, że Google nie publikuje limitów znaków dla tytułu i opisu, a w wynikach może dobrać inny fragment strony jako snippet. Data ma tu postać pełnego timestampa zapisanego jako string. Najpierw walidujemy sam format tekstu, a dopiero po pozytywnej weryfikacji przekształcamy go w obiekt Date. To bardziej jednoznaczny kontrakt niż samo z.coerce.date(), które dopuszcza szerszy zestaw konwersji. Poza tym nie odrzucamy przyszłych dat, ponieważ przydadzą się do planowania publikacji.
Metoda z.object() domyślnie usuwa nieznane pola z wyniku parsowania i taka jest jej rola, ale nie wyłapie automatycznie literówki w nazwie opcjonalnej metadanej. Jeżeli zespół redakcyjny wymaga rygorystycznego odrzucania wszystkich nieoczekiwanych kluczy, warto rozważyć zastosowanie z.strictObject(), pamiętając jednak przy tym o wcześniejszej weryfikacji pól wstrzykiwanych automatycznie przez używany loader lub integracje Astro.
Frontmatter, który przechodzi tę walidację
Najpierw zapisz profil w src/content/authors.json. Klucz id identyfikuje wpis dla loadera file() i referencji autora.
Następnie dodaj src/content/blog/pierwszy-wpis.md. Ten opis spełnia zadany limit, data zawiera strefę, a autor odpowiada ID w JSON-ie.
Jednolity schemat metadanych pozwala przechwycić brakujące lub błędne pola jeszcze przed oficjalną publikacją artykułu. Sama treść wpisu zaczyna się od nagłówka drugiego poziomu (##), ponieważ główny nagłówek <h1> generowany jest automatycznie przez szablon na podstawie metadanych. Zapobiega to powstawaniu podwójnych znaczników H1 na stronie.
Nazwa pliku (np. pierwszy-wpis.md) służy w tym loaderze do wygenerowania identyfikatora pierwszy-wpis. Należy pamiętać, że ID definiuje strukturę danych, podczas gdy ostateczny URL kształtuje się w warstwie routingu. Zmiana nazwy pliku na produkcji zmienia ten identyfikator, a w konsekwencji adres wpisu — dlatego po publikacji wymaga zaplanowania odpowiedniego przekierowania. W bardziej rozbudowanych architekturach logikę tworzenia ID można dostosować za pomocą funkcji generateId w loaderze.
Szkice i przyszłe publikacje pod kontrolą
Wszystkie publiczne widoki powinny używać tej samej funkcji pobierania wpisów. Poniższy helper odrzuca szkice, pomija przyszłe daty i sortuje od najnowszych. Przy jednakowych datach porządkuje wyniki według ID.
W przypadku witryny statycznej ostateczny wynik renderowania zostaje utrwalony w momencie kompilacji. W związku z tym samo nadejście wyznaczonej daty nie spowoduje automatycznej publikacji wpisu, więc jeśli artykuł ma ukazać się jutro o 9:00, musisz zaplanować przebudowanie oraz ponowne wdrożenie aplikacji po tej godzinie. Kanał RSS i mapa witryny powinny opierać się na tym samym mechanizmie filtrowania, przy czym integracje generujące sitemapę na podstawie gotowych ścieżek wyjściowych muszą uwzględniać wyłącznie faktycznie zbudowane adresy. Pamiętaj również, że wersja robocza nadal pozostaje częścią kolekcji. Flaga draft: true nie zwalnia pliku z wymogów walidacji Zod i w związku z tym błędne pola w szkicu nadal zablokują proces synchronizacji. Wreszcie, ochrona poufnych materiałów wymaga ścisłej kontroli repozytorium oraz artefaktów wyjściowych (pamiętajmy, że sam filtr wykluczający wpis z listy nie stanowi mechanizmu autoryzacji).
Generowanie strony i rozwiązanie referencji autora
W src/pages/blog/[...id].astro pobieramy wpisy, tworzymy trasy i przekazujemy dane przez props. Typ CollectionEntry<'blog'> opisuje props szablonu, a getEntry() pobiera profil wskazany referencją.
To kompletny, prosty dokument, bez zależności od niepokazanego layoutu. W projekcie z istniejącym layoutem przenieś do niego <html> i <head>, zachowując jawnie typowane propsy. reference('authors') zmienia identyfikator w obiekt wskazujący kolekcję i wpis. Bezpośrednie sprawdzanie powiązania z polem author zablokuje wdrożenie w przypadku braku profilu danego twórcy. Sprawdzian ten omija jednak autorów przypisanych do szkiców, ponieważ niepublikowane treści nie trafiają do funkcji getStaticPaths. Kompletnie spójny audyt relacji wymaga przejrzenia całej kolekcji wpisów przed odfiltrowaniem wersji roboczych. Astro 7 zgłasza w logu błąd nieprawidłowej referencji także dla szkiców, ale nie przerywa przez to builda.
Z kolei nazwa parametru jest dowolna, ale musi jedynie być spójna. Nazewnictwo pliku routingu wymusza strukturę parametrów: [...id].astro wymaga dostarczenia params.id, a [...slug].astro — params.slug. Składnia z wielokropkiem pozwala dodatkowo obsłużyć zagnieżdżone ścieżki. O finale adresu kanonicznego przesądza jednak mechanika loadera i zmapowana logika, a nie samo użycie słowa kluczowego id.
Lista artykułów bez drugiej konfiguracji
Strona listy używa tego samego helpera. Nie powielamy w niej schematu ani reguł publikacji.
Na tym etapie działają lista, publiczny artykuł, autor oraz filtrowanie publikacji. To wystarczająca baza do dodania stylów i kolejnych wpisów. Rozwijaj model przez rozszerzanie istniejącego schematu, aby przy dodawaniu np. powiązanych artykułów nie usunąć przypadkiem pola draft używanego przez szablony.
Walidacja Zod i TypeScript w CI
Walidacja danych kolekcji odbywa się przy synchronizacji treści, m.in. podczas astro dev i astro build. Loader plikowy śledzi zmiany lokalnych plików w trybie developerskim. Zewnętrzne API wymaga własnego sposobu odświeżania; nie zakładaj, że zmiana w CMS od razu przebuduje stronę.
astro build nie zastępuje kontroli typów. Zainstaluj @astrojs/check i typescript@6, a w CI uruchom:
Wersja TypeScriptu ma tu znaczenie, ponieważ TypeScript 7 z natywnym kompilatorem nie udostępnia jeszcze API, z którego korzysta astro check, więc polecenie kończy się błędem. @astrojs/check 0.9.10 obsługuje TypeScript 5 i 6, dlatego instalujemy szóstkę.
astro check sprawdza kod i korzysta z wygenerowanych typów kolekcji, a edytor może podpowiadać post.data.title, ale walidacja YAML w pliku Markdown jest osobną funkcją edytora. Jeśli po zmianie konfiguracji schematu typy TypeScript pozostają nieaktualne, uruchom polecenie astro sync i odśwież serwer językowy w swoim edytorze. Pamiętaj jednak, że udana kompilacja (green build) zapobiegnie błędnemu wdrożeniu tylko wtedy, gdy Twój pipeline CI/CD rygorystycznie uzależnia publikację od powodzenia tego etapu. Poniższe scenariusze testowe warto przeprowadzić na kopii pliku, ponieważ dają one znacznie większą pewność niż zwykła weryfikacja wizualna w przeglądarce.
| Zmiana testowa | Oczekiwany wynik |
|---|---|
Usunięcie title albo opis krótszy niż 50 znaków | Błąd walidacji kolekcji |
date: 'jutro rano' | Błąd formatu daty |
tags: [Astro] zamiast tags: [astro] | Błąd wartości spoza enum |
draft: true albo data daleko w przyszłości | Brak trasy artykułu i linku na liście |
| Nieistniejący autor publicznego wpisu | Build zatrzymany podczas rozwiązywania referencji |
post.data.titel w szablonie | Błąd wykryty przez astro check |
| Usunięcie profilu autora szkicu | Potrzebny osobny audyt relacji, jeśli chcesz blokować również takie zmiany |
Kiedy rozszerzyć kolekcję o CMS, obrazy lub dane live?
Przy kilku autorach JSON jest prosty do przeglądu w Gicie. Gdy redakcja potrzebuje panelu, uprawnień i obiegu akceptacji, rozważ headless CMS w Astro. Sprawdź, czy wybrany pakiet faktycznie udostępnia loader Content Layer, czy tylko klienta API albo komponenty. API i wersje loaderów zależą od dostawcy. Dla lokalnych obrazów użyj helpera image() w funkcji schematu, jeżeli chcesz korzystać z metadanych i przetwarzania obrazów Astro. Zwykły z.string() sprawdza tekst, nie istnienie pliku. Dla zewnętrznych adresów waliduj URL i dozwolony protokół; samo startsWith('/') przepuści także adres zaczynający się od //.
Build-time collections pasują do treści aktualizowanych wraz z wdrożeniem. Live Content Collections pobierają dane podczas obsługi zapytania do kolekcji i wymagają świadomej obsługi błędów oraz cache. W Astro 7 loader danych live może dołączyć do wyniku podpowiedź dla cache, czyli tagi do unieważniania i czas ostatniej modyfikacji. Aby dane zmieniały się pomiędzy wizytami bez przebudowy strony, potrzebujesz renderowania na żądanie i odpowiedniego adaptera. Nie są konieczne do tego bloga; szerszy kontekst znajdziesz w omówieniu Astro 6.
Co Content Collections zmieniają w SEO?
Generalizując, spójne dane ułatwiają generowanie tytułów, opisów, RSS i danych strukturalnych, pomagając pośrednio w indeksacji. Szablon musi oczywiście osadzać metadane bezpośrednio w kodzie HTML, choć o ostatecznym uwzględnieniu konkretnych adresów w indeksie i tak decydują nieubłagane algorytmy Google. Przed samą publikacją upewnij się, że skonfigurowałeś docelową domenę, wdrożyłeś spójną politykę adresów kanonicznych oraz zweryfikowałeś poprawność mapy witryny i kodów odpowiedzi HTTP. Tworząc dane strukturalne BlogPosting, dbaj o to, by dokładnie odzwierciedlały widoczną treść artykułu. Ścieżki do obrazów zawsze przekształcaj w adresy bezwzględne za pomocą konstruktora new URL(path, site), co zapobiegnie błędom podwójnego doklejania domeny do istniejącego adresu URL. Dodatkowo, jeśli osadzasz JSON-LD przy użyciu dyrektywy set:html, zabezpiecz proces serializacji przed ryzykiem przedwczesnego zamknięcia znacznika <script> przez niezweryfikowane znaki (np. zamieniając < na sekwencję \u003c).
Szczegóły znajdziesz w poradniku o technicznym SEO w Astro. Schemat powinien wspierać te mechanizmy, a jego limity wynikać z potrzeb publikacji. Google nie określa preferowanej liczby słów. Skracaj powtarzalny kod po to, żeby czytelnik łatwiej wdrożył rozwiązanie, nie po to, by osiągnąć domniemany współczynnik tekstu do kodu.


