Why I Built My Own CMS Instead of Using Contentful or Sanity
Headless CMS platforms are powerful, but they abstract away the database, limit your architecture, and introduce vendor lock-in. Here is a build-in-public look at why writing a bespoke CMS on Supabase was the best engineering decision I made.
The Allure of Headless
When I started designing this blogging platform, my first instinct was to reach for a Headless CMS. Contentful, Sanity, and Strapi are the industry darlings right now. They promise a polished admin UI out of the box, a global CDN, and a GraphQL or REST API to fetch your data.
It sounds perfect. You focus on the frontend, they handle the data layer.
But as I began architecting the platform—which required scheduled publishing, intricate related-post algorithms based on tag overlap, and inline security analytics—the cracks in the Headless CMS model started to show.
1. The Loss of SQL
The biggest tradeoff of a SaaS Headless CMS is that you lose direct access to your database.
I am a backend engineer. I think in relational data, indexes, and complex joins.
When I wanted to build a "Related Posts" engine, doing it in Postgres is trivial:
SELECT p.*,
(SELECT count(*) FROM unnest(p.tags) t WHERE t = ANY($1)) as overlap_score
FROM blog_posts p
WHERE p.id != $2 AND p.published = true
ORDER BY overlap_score DESC, published_at DESC
LIMIT 3;
Try doing that in a Headless CMS API. You can't. You are forced to either:
- Fetch all posts to the Node.js server and compute the overlap in memory (terrible for scaling).
- Sync the CMS data into your own Postgres database via Webhooks, just so you can query it properly.
If I have to sync data to Postgres just to run advanced queries, why am I paying for a CMS in the first place?
2. Vendor Lock-in and API Limits
Sanity and Contentful are fantastic products, but they are walled gardens. If they raise pricing, deprecate an API version, or suffer an outage, you are entirely at their mercy.
By building on top of Supabase (PostgreSQL), my data is mine. I can dump the SQL at any time. I can move it to AWS RDS, Heroku, or a Raspberry Pi.
3. The Custom Editor Experience
I needed a very specific writing workflow. I wanted a dual-pane Markdown editor that felt like VS Code, with real-time SEO scoring, character limits, and secure, inline image uploads direct to Cloudflare R2.
Most Headless CMS platforms allow UI plugins, but building a React plugin for Sanity Studio felt like learning a proprietary framework just to build a text area.
By building my own admin dashboard in Next.js, I had total control. I integrated @uiw/react-md-editor, hooked it up to a Server Action for R2 uploads, and built exactly the workflow I wanted in a matter of days.
4. Total Typesafe End-to-End
With a bespoke CMS built on Next.js and Supabase, I can use tools like supabase-js to generate TypeScript types directly from my SQL schema.
supabase gen types typescript --local > src/types/database.ts
My database schema, my server actions, and my React components all share the exact same type definitions. If I drop a column from the blog_posts table, my Next.js build fails immediately. Achieving this level of type safety with a third-party CMS often requires brittle code-generation steps and massive GraphQL schemas.
The Architecture I Settled On
Instead of a Headless SaaS, I built a "Bespoke Monolith" utilizing modern cloud primitives:
- Database & Auth: Supabase (PostgreSQL + GoTrue)
- Framework: Next.js App Router
- Storage: Cloudflare R2 (S3 compatible, zero egress fees)
- Hosting: Vercel
The Admin Panel is simply a protected route group ((admin)/...) within the Next.js app. It shares the same components, design system, and tailwind configuration as the public site.
Was it worth it?
Building a CMS takes time. You have to handle authentication, RBAC, form validation, and asset storage yourself.
But as an engineer, owning your stack is liberating. I can add a new feature—like a custom engagement analytics dashboard—in a few hours, writing pure SQL and React, without ever having to read documentation on how to configure a Headless CMS schema migration.
If you are a non-technical founder, use Contentful. If you are an engineer building your own platform, build it yourself. You'll learn more, and you'll own your data.
Tags
Related Blogs
Migrating from TanStack Start to Next.js App Router: An Architecture Post-Mortem
A deep dive into why we moved our entire CMS away from Vite SSR and TanStack Router, the performance implications of Server Components, and the hydration traps we had to fix.
Debugging SSR Runtime Crashes on Vercel: Node vs Edge
When your Next.js app works perfectly on localhost but throws 500s on Vercel. A deep dive into Node runtimes, the Edge network, and how to debug elusive server-side crashes.
Fixing Hydration Errors in Next.js: A Structural Approach
Hydration errors are the bane of Next.js developers. Stop fighting the warnings and learn the structural causes behind 'Text content did not match server-rendered HTML'.