← Insights
2 June 2026 Insights EN

zrodelko.org — how I built a full-stack nursery website with WCAG 3.0, ElevenLabs TTS and a GSC pipeline

zrodelko.org — how I built a full-stack nursery website with WCAG 3.0, ElevenLabs TTS and a GSC pipeline

A nursery in the Beskidy mountains needed a website to replace Canva — accessible, bilingual, with AI-narrated articles and automated Facebook sync. Here is how I built it from scratch on Next.js 15, Payload CMS 3 and Supabase, with a full GSC analytics pipeline.

When Źródełko — a nursery and kindergarten in Gruszowiec in the Beskid Wyspowy mountains — reached out for a new website, I had a clear picture of where we were starting: a site built in Canva, no CMS, no accessibility, no bilingual support. And no way to grow.

I could have deployed WordPress and called it done in a week. Instead, I made a decision I stand behind: a custom full stack that could scale for years.

What took shape over several months became one of the technically most interesting projects I have delivered.

Stack: why Next.js 15 + Payload CMS 3

The key architectural decision: I wanted a CMS embedded in the Next.js application, not a separate process. Payload CMS 3 makes exactly that possible — a single Vercel deployment, admin panel at /admin, PostgreSQL database on Railway.

Full stack:

Layer Technology
Framework Next.js 15.4 + React 19, App Router, TypeScript 5.7
CMS Payload CMS 3 (embedded, Lexical editor, PL/EN localisation)
Database PostgreSQL (Railway), 8 collections + 2 globals
Styling Tailwind CSS 3 + CSS custom properties (:root variables)
AI / TTS ElevenLabs (article audio generation) + Media Session API
Integrations Facebook Graph API v19 (ISR 2h, cron revalidation)
SEO analytics Google Search Console API + Supabase Edge Functions + GitHub Actions
Python Pillow, OpenCV, face_recognition (HOG), rembg, FFmpeg
Infrastructure Vercel (SSR + CI/CD) + Railway (PostgreSQL) + Supabase (Edge Functions)

WCAG 3.0 — accessibility toolbar built from scratch

Most institutional websites pretend to be „accessible” — they have alt text on images and call it done. I implemented a full WCAG 3.0 accessibility toolbar with nine independent parameters controlled by CSS custom properties on :root.

The AccessibilityToolbar component lets users control:

Typography
Text size (0.8×–1.6×)
Line height
Letter and word spacing
Dyslexia-friendly font

Visual
High contrast
Dark mode (CSS invert)
Reduce motion
Enhanced focus ring

Every parameter works through a CSS variable set on document.documentElement. For example, text size is font-size: calc(16px * var(--text-size-scale)) on html — changing one variable scales everything on the page proportionally. State is persisted in localStorage and restored on every visit.

Why this matters for a public benefit organisation: OPP entities have a legal obligation to provide digital accessibility (WCAG 2.1 AA as a minimum). Źródełko welcomes children with disabilities — it makes sense that the website is accessible to all parents.

ElevenLabs TTS + AudioPlayer + Media Session API

The Parent Zone is a hub of expert articles on parenting, child development, and Pikler methodology. I decided that key articles should have an audio version — narrated by a female voice generated in ElevenLabs.

The pipeline:

1
Audio generation (Python)
A Python script calls the ElevenLabs SDK with a multilingual TTS model, converting article text into a high-quality MP3 served statically by Next.js.
2
AudioPlayer component (React)
A custom player with a progress bar, play/pause button, timer, and click-to-seek. Zero external libraries — 125 lines of TypeScript.
3
Media Session API
OS-level integration: the article title and narrator avatar appear on the phone lock screen. Play/pause and ±10s skip controls work from headphones or Bluetooth.

Result: a parent can start an article, pocket their phone, and listen while cooking lunch.

Facebook ISR — news feed without an admin panel

Źródełko actively posts on Facebook — it is the natural communication hub with the community. Instead of asking staff to copy-paste posts twice, I built an automatic sync.

The FacebookPosts component is a Server Component with ISR (Incremental Static Regeneration) — Next.js builds the page with fresh data from Facebook Graph API and holds the cache for 2 hours. Post content, image, and date are fetched automatically; the title is extracted from the first line, the rest becomes an excerpt. Dates are localised to the site’s language. A separate endpoint allows cache invalidation via an external cron.

GSC pipeline: GitHub Actions → Supabase Edge → email

I built a fully serverless analytics pipeline for Google Search Console — so the client receives a weekly email with visibility data every Monday, and historical data is available for analysis.

Supabase Edge Function (Deno)
Runs daily, fetching clicks, impressions, CTR and position from the GSC Search Analytics API — broken down by query, page and date. Everything lands in Supabase PostgreSQL with history and weekly summary views.
📧
GitHub Actions: weekly report
Every Monday a workflow generates an HTML email with top queries and pages from the last 7 days and delivers it via SMTP. Historical data is committed to the repository.
🗄
PostgreSQL: history and views
Data is stored with uniqueness per day and query/page. SQL views aggregate weekly summaries ready for display or inclusion in the report.

Cost of this pipeline: zero. The entire setup runs on free tiers — GitHub Actions, Supabase Edge Functions and PostgreSQL all fit within the free limits at this traffic scale.

Python: automated children’s photo processing

The nursery has thousands of photos of children from activities. Problem: GDPR requires that recognisable children’s faces not be published without individual parental consent.

Solution: a Python script using face_recognition (dlib HOG) for face detection and cv2.GaussianBlur to blur them. A separate script uses rembg to remove backgrounds from staff photos. FFmpeg compresses video to a sensible size — photos don’t balloon on the server.

Registration form: 5 steps, age validation, email to parent

The RegistrationForm component is 617 lines of TypeScript handling the full enrolment flow: choose nursery/kindergarten → child details → parent details → health information → GDPR consents.

One feature that works particularly well: auto-suggest for the start date. Based on the child’s date of birth and the selected facility type, the system automatically proposes the nearest recruitment term (March or September) when the child will be in the correct age bracket. Nursery: 10–36 months. Kindergarten: 30–72 months.

On submit, the endpoint saves data to Payload CMS, sends a confirmation email to the parent, and an internal email to administration — all through SMTP configured via @payloadcms/email-nodemailer.

JSON-LD and LLMO/GEO: optimising for AI search engines

Standard SEO is not enough if you care about visibility in AI Overviews (Google SGE) and ChatGPT/Perplexity answers. LLMO (Large Language Model Optimisation) and GEO (Generative Engine Optimisation) mean structuring content for language models — not just crawlers.

What was implemented:

  • Organization schema with type ChildCare + LocalBusiness, geolocation (621 m a.s.l.), and areaServed for Dobra commune and Limanowa county
  • Article schema with author (Person), dateModified, inLanguage, and mainEntityOfPage for every article in the Parent Zone
  • FAQPage schema on the homepage and fees page — questions and answers that AI can serve directly
  • BreadcrumbList on every sub-page
  • Answers written in encyclopaedic style — complete sentences, no fragments stripped of context

Result: the site has a real chance of appearing in AI answers to queries like „nursery Beskidy mountains”, „nursery Dobra Limanowa”, or „nursery WCAG accessible” — not only in traditional search results.

Summary

zrodelko.org started as „a new website for a nursery”. It ended as a project that taught me several things:

  • Payload CMS 3 embedded in Next.js is a genuinely mature solution — no separate backend, one repo, one deploy
  • WCAG 3.0 via CSS custom properties is an elegant approach — one point of change, the entire UI responds
  • The Media Session API is deeply underrated — it raises audio UX by an order of magnitude at zero cost
  • A serverless GSC pipeline (Edge Functions + GitHub Actions) runs reliably and practically for free

The project is live at zrodelko.org. If you run a small organisation and are wondering whether this stack makes sense — yes, it does. Especially when you want a site that grows, and that the client can manage independently.

// Let's talk //

Working on AI, automation or VR in your organisation? I'd be happy to discuss your case.

Book a call →