Kiedy Źródełko — żłobek i przedszkole w Gruszowcu w Beskidzie Wyspowym — zwróciło się do mnie z prośbą o nową stronę, miałem jasny obraz punktu startowego: strona zbudowana w Canva, bez zarządzania treścią, bez dostępności, bez dwujęzyczności. I bez żadnej drogi do rozbudowy.
Mógłbym postawić WordPress i skończyć w tydzień. Zamiast tego podjąłem decyzję, której nie żałuję: pełny custom stack, który dałoby się rozwijać latami.
To co powstawało przez kilka miesięcy stało się jednym z technicznie ciekawszych projektów, które zrealizowałem.
Stack: dlaczego Next.js 15 + Payload CMS 3
Kluczowa decyzja architekturalna: chciałem CMS-a wbudowanego w aplikację Next.js, a nie osobnego procesu. Payload CMS 3 umożliwia dokładnie to — jeden deployment na Vercel, panel admina pod /admin, baza PostgreSQL na Railway.
Pełny stack:
| Warstwa | Technologia |
|---|---|
| Framework | Next.js 15.4 + React 19, App Router, TypeScript 5.7 |
| CMS | Payload CMS 3 (embedded, Lexical editor, lokalizacja PL/EN) |
| Baza danych | PostgreSQL (Railway), 8 kolekcji + 2 globale |
| Stylowanie | Tailwind CSS 3 + CSS custom properties (:root variables) |
| AI / TTS | ElevenLabs (generowanie audio artykułów) + Media Session API |
| Integracje | Facebook Graph API v19 (ISR 2h, cron revalidation) |
| Analityka SEO | Google Search Console API + Supabase Edge Functions + GitHub Actions |
| Python | Pillow, OpenCV, face_recognition (HOG), rembg, FFmpeg |
| Infra | Vercel (SSR + CI/CD) + Railway (PostgreSQL) + Supabase (Edge Functions) |
WCAG 3.0 — toolbar dostępności zbudowany od zera
Większość stron instytucjonalnych udaje, że „jest dostępna” — ma alt w obrazkach i na tym koniec. Zdecydowałem się zaimplementować pełny toolbar dostępności zgodny z WCAG 3.0, z dziewięcioma niezależnymi parametrami sterowanymi przez CSS custom properties na :root.
Komponent AccessibilityToolbar pozwala użytkownikowi regulować:
Wysokość linii
Odstęp liter i słów
Czcionka dyslektyczna
Tryb ciemny (CSS invert)
Redukcja animacji
Wzmocniony focus ring
Każdy parametr działa przez zmienną CSS ustawianą na document.documentElement. Np. rozmiar tekstu to font-size: calc(16px * var(--text-size-scale)) na html — zmiana jednej zmiennej skaluje wszystko na stronie proporcjonalnie. Stan zapisywany jest w localStorage i przywracany przy każdej wizycie.
Dlaczego to ważne dla OPP: Organizacje pożytku publicznego mają prawny obowiązek zapewnienia dostępności cyfrowej (WCAG 2.1 AA jako minimum). Źródełko przyjmuje dzieci z niepełnosprawnościami — rozsądne, żeby strona była dostępna dla wszystkich rodziców.
ElevenLabs TTS + AudioPlayer + Media Session API
Strefa Rodziców to hub z artykułami eksperckimi o wychowaniu, rozwoju dziecka i metodyce Pikler. Postanowiłem, że kluczowe artykuły będą miały wersję audio — czytaną przez lektorkę z głosem wygenerowanym w ElevenLabs.
Pipeline wygląda tak:
Efekt: rodzic może uruchomić artykuł, schować telefon do kieszeni i słuchać podczas gotowania obiadu.
Facebook ISR — aktualności bez panelu admina
Źródełko aktywnie publikuje na Facebooku — to naturalne centrum komunikacji ze społecznością. Zamiast prosić obsługę o podwójne wklejanie postów, zbudowałem automatyczną synchronizację.
Komponent FacebookPosts to Server Component z ISR (Incremental Static Regeneration) — Next.js buduje stronę ze świeżymi danymi z Facebook Graph API i trzyma cache przez 2 godziny. Treść posta, zdjęcie i data są pobierane automatycznie; tytuł wyciągany z pierwszej linii, reszta staje się excerptm. Daty lokalizowane są do języka strony. Dodatkowy endpoint pozwala wymusić odświeżenie przez zewnętrzny cron.
Pipeline GSC: GitHub Actions → Supabase Edge → email
Zbudowałem pełny, bezserwerowy pipeline analityczny dla Google Search Console — żeby klient dostawał co poniedziałek email z raportem widoczności strony, a dane historyczne były dostępne do analizy.
Koszt tego pipeline’u: zero. Cały setup działa na darmowych tierach — GitHub Actions, Supabase Edge Functions i PostgreSQL mieszczą się w bezpłatnych limitach przy tej skali ruchu.
Python: automatyczne przetwarzanie zdjęć dzieci
Żłobek ma tysiące zdjęć dzieci z zajęć. Problem: RODO wymaga, żeby nie publikować rozpoznawalnych twarzy dzieci bez osobnej zgody każdego z rodziców.
Rozwiązanie: skrypt Python używający face_recognition (dlib HOG) do wykrywania twarzy i cv2.GaussianBlur do ich rozmycia. Dodatkowy skrypt z rembg do usuwania tła ze zdjęć pracowników. FFmpeg kompresuje filmy do rozsądnego rozmiaru — zdjęcia nie puchną na serwerze.
Formularz zapisu: 5 kroków, walidacja wieku, email do rodzica
Komponent RegistrationForm to 617 linii TypeScript obsługujących pełny proces rekrutacji: wybór żłobek/przedszkole → dane dziecka → dane rodzica → zdrowie → zgody RODO.
Jeden element, który naprawdę działa dobrze: auto-suggest daty rozpoczęcia. Na podstawie daty urodzenia dziecka i wybranego typu placówki system automatycznie proponuje najbliższy termin rekrutacyjny (marzec lub wrzesień), kiedy dziecko będzie w odpowiednim przedziale wiekowym. Żłobek: 10–36 miesięcy. Przedszkole: 30–72 miesiące.
Po submicie endpoint zapisuje dane do Payload CMS, wysyła email potwierdzający do rodzica i email wewnętrzny do administracji — przez SMTP skonfigurowany przez @payloadcms/email-nodemailer.
JSON-LD i LLMO/GEO: optymalizacja pod AI search engines
Standardowe SEO to za mało, jeśli zależy ci na widoczności w AI Overviews (Google SGE) i odpowiedziach ChatGPT/Perplexity. LLMO (Large Language Model Optimization) i GEO (Generative Engine Optimization) to optymalizacja struktury treści pod modele językowe — nie tylko pod crawlery.
Co zostało zaimplementowane:
- Organization schema z typem
ChildCare + LocalBusiness, z geolokalizacją (621 m n.p.m.) iareaServeddla gminy Dobra i powiatu limanowskiego - Article schema z
author(Person),dateModified,inLanguageimainEntityOfPagedla każdego artykułu w Strefie Rodziców - FAQPage schema na stronie głównej i podstronie planu owego — pytania i odpowiedzi, które AI może serwować bezpośrednio
- BreadcrumbList na każdej podstronie
- Odpowiedzi pisane w stylu encyklopedycznym — kompletne zdania, bez fragmentów wyrwanych z kontekstu
Efekt: strona ma szansę pojawiać się w odpowiedziach AI na zapytania o „żłobek Beskidy”, „żłobek Dobra Limanowa” czy „żłobek WCAG” — nie tylko w tradycyjnych wynikach wyszukiwania.
Podsumowanie
zrodelko.org zaczęło się jako „nowa strona dla żłobka”. Skończyło się jako projekt, który nauczył mnie kilku rzeczy:
- Payload CMS 3 embedded w Next.js to naprawdę dojrzałe rozwiązanie — zero osobnego backendu, jedno repo, jeden deploy
- WCAG 3.0 przez CSS custom properties to eleganckie podejście — jeden punkt zmiany, cały UI reaguje
- Media Session API jest bardzo niedoceniana — podwyższa UX audio o rząd wielkości bez żadnego kosztu
- Bezserwerowy pipeline GSC (Edge Functions + GitHub Actions) działa niezawodnie i praktycznie za darmo
Projekt jest wdrożony produkcyjnie na zrodelko.org. Jeśli prowadzisz małą organizację i zastanawiasz się, czy taki stack ma sens — tak, ma. Zwłaszcza jeśli zależy ci na rosnącej stronie, którą klient może obsługiwać samodzielnie.