The CMS this site runs on
From static markup to editable content
Build the UI freely
No templates, no theme system, no constraints. Next.js, Tailwind and whatever the design needs.
Declare what is editable
Wrap a section in ContentCardManager, name the fields and their limits. That is the entire integration.
The backend just stores it
One universal Content entity absorbs every section site-wide. New field, new section - no migration.
Fetch via GraphQL with ISR
Pages are statically generated and cached by tag, with JSON fallbacks so the site renders even if the backend is down.
Edit live, revalidate instantly
Saving in the panel triggers on-demand revalidation of exactly the pages that use that content key.
Build the UI freely
No templates, no theme system, no constraints. Next.js, Tailwind and whatever the design needs.
Declare what is editable
Wrap a section in ContentCardManager, name the fields and their limits. That is the entire integration.
The backend just stores it
One universal Content entity absorbs every section site-wide. New field, new section - no migration.
Fetch via GraphQL with ISR
Pages are statically generated and cached by tag, with JSON fallbacks so the site renders even if the backend is down.
Edit live, revalidate instantly
Saving in the panel triggers on-demand revalidation of exactly the pages that use that content key.
What is actually in the box
Universal content layer
A single Content entity - title, subtitle, description, customData, media, key, order - backs every section of every page. Adding a section means picking a new key, not writing a model, an API and an admin form.
TypeORMPostgreSQLGraphQLcustomData as the escape hatch
A JSONB column holds anything a section needs: CTA buttons, tag lists, stat rows, nested objects, feature flags. Extending a block never requires a schema migration.
JSONBSchema-freeValidatedRich text and JSON editing
Tiptap for description fields with a sanitizing paste pipeline, plus a validating JSON editor for customData - so structured content stays structured even when edited by hand.
TiptapSanitizerInline editingMedia pipeline
Uploads land in MinIO with rollback on failure, drag-and-drop reordering on desktop and directional buttons on mobile, and permanent public URLs that browsers can cache.
MinIOS3 APIDrag & dropAuth and access control
Google OAuth2, dual JWT with access and refresh tokens in HttpOnly cookies, refresh-token rotation in the database, and role-based guards - editing is gated behind the admin role.
OAuth2JWTRBACISR and on-demand revalidation
Content is fetched with Next.js cache tags, so saving a block revalidates only the pages that consume that key. Static performance, live editing, no rebuild.
Next.js ISRCache tagsSWR
The admin panel
The decisions behind it
UI-first instead of schema-first
Traditional CMSes force the design to fit the data model. Here the model absorbs whatever the design needs, so a new section is a key and a JSON fallback - not a migration, a resolver and an admin form.
Fallbacks that keep the site alive
Every section ships with a JSON fallback compiled into the bundle. If the backend or database is unreachable, pages still render fully - the CMS degrades to a static site instead of a 500.
One entity instead of N models
Multiple live sections across three pages share a single table. Adding a fourth page costs no backend work at all - the content engine already handles it.
Tested where it actually breaks
Unit tests cover content and media services including MinIO rollback paths; Playwright drives the real admin panel through create, edit, delete, reorder and upload against a live database.
Let's talk
Phone / WhatsApp




