STAKR CMS
STAKR is a multi-tenant, headless CMS built to power a fleet of client websites from a single editor, using one API and one shared edge infrastructure.
Client sites kept needing the same things: editable pages, a media library, forms, navigation, SEO, and accessibility checks. Rather than rebuild them each time, I built one platform that every site plugs into. Editors get a familiar tool and each site keeps its own design without compromise.


- Visual page builder Drag-to-reorder sections with a live preview that renders the site's own components and CSS.
- Collections & articles Repeatable content types declared by each site, with scheduling, publishing, and SEO fields built in.
- Media library Upload, crop, and tag images with alt text, served with on-the-fly transforms.
- Accessibility scanning WCAG 2.1 AA checks, bulk alt-text fixing, and a conformance report inside the editor.
- Messages, tasks & analytics Contact-form inbox, an internal task board, and traffic overview.
- Roles & tenants Super-admin, tenant-admin, and editor roles with a tenant switcher for multi-site users.


A Hono API on Cloudflare Workers backed by D1 and R2, a React 19 admin, and server-rendered client sites that cache at the edge and purge on publish. Sites teach the CMS their own section types through a JSON schema, so the admin never needs site-specific code.


Every page is a stack of sections: a hero, a gallery, a form, a feed. A site defines the sections it has, and the CMS turns each definition into an editor, so a new section is one JSON entry and one component.
{
"type": "ProjectCapabilities",
"label": "Project Capabilities List",
"hasItems": true,
"itemLabel": "Capability",
"fields": [
{ "key": "eyebrow", "type": "text",
"default": "What editors get —" }
],
"itemFields": [
{ "key": "title", "type": "text" },
{ "key": "body", "type": "textarea" }
]
}// sectionRegistry.js
export const SECTION_REGISTRY = {
ProjectCapabilities: ProjectCapabilitiesSection,
}
// ProjectCapabilitiesSection.jsx
export default function ProjectCapabilitiesSection({ fields, items }) {
return (
<ul className="caps">
{items.map((it) => (
<li key={it.id}>
<strong>{it.fields.title}</strong> {it.fields.body}
</li>
))}
</ul>
)
}- Declared, not hard-coded Each site lists its section types in a JSON file and pushes it with one command. The admin builds its editing forms from that, so it holds no code for any particular site.
- Typed fields Text, rich text, numbers, links, selects, toggles, colors, images, galleries, tags and categories. A section declares which ones it needs.
- Repeatable items A section can hold a list of sub-records, like the rows of a capabilities list or the slides in a gallery, each with its own fields.
- One component, two jobs The React component that renders a section on the live site is the same one the builder previews, so what editors see is what ships.
- Alt text managed once Image fields take their alt text from the media library: one string per asset, updated everywhere that image is used.
- Traceable Every rendered section carries a hidden marker, so an accessibility finding points back to the exact section and field that caused it.