How I Built a Drag-and-Drop CMS Without a Third-Party Platform
A client needed non-technical staff to rearrange page sections and swap images without calling a developer, but also needed to own their code outright — no vendor lock-in to a page-builder SaaS. That combination pushed toward building a lightweight, purpose-built CMS instead of reaching for an off-the-shelf platform.
Scoping it down from "CMS" to the actual need
"Build a CMS" is a trap — it invites building a general-purpose tool for a specific problem. The actual requirement was: reorder a fixed set of section types (hero, testimonial grid, image gallery, FAQ block) on a handful of pages, and edit their content. That's a much smaller, well-defined problem than a general page builder, and scoping it that tightly is what kept the build achievable.
The data model that made it simple
Each page is an ordered array of section objects, each with a type and a content JSON blob matching that type's schema. The admin UI is a drag-to-reorder list of section cards, each rendering a type-specific form (Zod-validated, same schema on client and server). The public site maps that array to React components by type, in order — no special-casing, no page-specific templates to maintain.
Drag-and-drop, without a heavy library
For reordering a flat list, the native HTML5 drag events plus a small state update were enough — no need for a full DnD library's complexity budget (draggable containers, sortable trees, touch support) when the actual requirement was "reorder items in a list." Adding @dnd-kit or react-beautiful-dnd would have been reasonable at a larger scope, but wasn't proportionate here.
What made it maintainable
New section types are additive: a new entry in the type registry (schema + admin form + public renderer), not a change to existing pages' data or the reordering logic. That's the design decision that made "the client wants a new section type next quarter" a small task instead of a rearchitecture.
What I'd reconsider next time
Versioning/undo. The client asked for it after the fact — "can I see what this section looked like yesterday" — and retrofitting content history onto a system that overwrites JSON blobs in place was more work than building it in from the start would have been.
Takeaway
A from-scratch CMS made sense here because the actual editing surface was small and well-defined, and the client's requirement for code ownership ruled out a SaaS builder outright. It wouldn't have been the right call for a client who needed genuinely open-ended page layouts — that's what the page-builder platforms are actually built for.