Multi-tenant Headless CMS v Node.js + TypeScript s Supabase jako databází a autentizací.
cms/
├── apps/
│ ├── backend/ # Fastify API
│ ├── admin/ # React admin (Vite + Tailwind)
│ └── web-demo/ # Minimální ukázka veřejného webu (Vite proxy → API)
├── packages/
│ └── shared/ # Sdílené typy (bloky)
└── supabase/ # SQL migrace
- Node.js 18+
- Supabase projekt
npm install
npm run build -w @nase-cms/shared # před prvním spuštěním adminuSUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
ADMIN_BASE_DOMAIN=mojecms.cz
CACHE_TTL_MS=300000
# Edge cache for published GET /api/v1/content, site-info, site-settings (seconds)
PUBLIC_CACHE_S_MAXAGE=60
PUBLIC_CACHE_STALE_WHILE_REVALIDATE=300
PORT=3000
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
VITE_API_URL= # prázdné = proxy na backend (dev)
Pro testování multi-tenancy na localhost přidej do hosts souboru:
Windows: C:\Windows\System32\drivers\etc\hosts
Mac/Linux: /etc/hosts
127.0.0.1 kadernictvi.localhost
127.0.0.1 instalater.localhost
Pak nastav ADMIN_BASE_DOMAIN=localhost a otevři např. http://kadernictvi.localhost:5173.
# Backend (port 3000)
npm run dev
# Admin (port 5173, proxy na /api → backend)
npm run dev:adminVite proxy přeposílá /api na http://localhost:3000.
Ukázkový statický klient volající GET /api/v1/content přes proxy (port 5174, aby nekolidoval s adminem).
API klíč musí sedět s databází: v tabulce tenants je uložený jen SHA-256 hash (api_key_hash). Plaintext klíč dáš do .env, hash do Supabase:
# 1) Zvol tajný řetězec a spočítej hash (stejný algoritmus jako backend)
npm run hash-api-key -- "muj-tajny-klicek"
# 2) V Supabase → SQL: (nahraď hash výstupem z příkazu výše)
# UPDATE tenants SET api_key_hash = '...' WHERE admin_subdomain = 'kadernictvi';
# 3) Do apps/web-demo/.env stejný plaintext:
# VITE_CMS_API_KEY=muj-tajny-klicekcp apps/web-demo/.env.example apps/web-demo/.env
# doplň VITE_CMS_API_KEY podle kroku výše
npm run dev # backend :3000
npm run dev:web-demo # demo :5174Otevři http://localhost:5174 - zobrazí JSON obsahu nebo chybu z API.
Stále 401? Ověř v Supabase (stejný projekt jako SUPABASE_* v apps/backend/.env):
SELECT admin_subdomain, LENGTH(api_key_hash) AS hash_len
FROM tenants;hash_lenmusí být 64 (SHA-256 hex). Pokud jeNULL,UPDATEnezasáhl řádek (špatnýWHERE) nebo měníš jiný Supabase projekt než backend.- Ověření mimo prohlížeč (stejný klíč jako v
.env):
curl -s -H "X-API-KEY: TVUJ_PLAINTEXT" "http://localhost:3000/api/v1/content?lang=cs"Měl bys dostat JSON (HTTP 200). Při 401 je problém v DB / hash / plaintext klíči (ne ve Vite).
Po změně kódu backendu restartuj npm run dev (backend).
- Login – Supabase Auth (email + heslo)
- Dashboard – seznam stránek, vytvoření, mazání
- Editor stránek – Block Editor (Hero, Text Section, Gallery)
- Knihovna médií – upload a výběr obrázků
Bloky se ukládají jako JSON pole. Každý jazyk (CZ/EN) má vlastní sadu bloků.
- Admin (
/api/v1/admin/*): JWT v hlavičceAuthorization: Bearer <token>, tenant z Host subdomény - Content (
/api/v1/content/*): tenant z hlavičkyX-API-KEY
Spusť v Supabase SQL Editoru v pořadí:
supabase/migrations/001_initial_schema.sqlsupabase/migrations/002_media_bucket_and_metadata.sql
Kompletní návod včetně seed dat: docs/TESTING.md
Nasazení stejné šablony na další doménu, DNS, CORS, API klíče a mapování na tenanta: docs/TEMPLATE_MULTI_URL.md