We spent 25 years building websites on other people's platforms. WordPress, Joomla, Drupal, a few proprietary systems best left unnamed. We know what works and what doesn't. So when it came time to manage our own content, we built something from scratch. Not because we enjoy reinventing wheels. Because the existing wheels didn't fit.
Our CMS is a Node.js application with an Express backend, a SQLite database, a TipTap rich text editor, and Handlebars templates. It generates static HTML files. The CMS itself never faces the public internet. Visitors get plain HTML pages served directly from the server. No PHP runtime. No database queries on page load. No caching layer needed to make a slow thing feel fast.
This article is about why we made that choice and, more honestly, about the specific conditions where building custom actually makes sense versus where it's an expensive distraction.
The Problem With Off-the-Shelf Admin Panels
We've written about what's broken in the WordPress admin experience. The short version: WordPress was designed to publish blog posts in 2003, and despite decades of iteration, the editing experience still reflects that origin. The block editor (Gutenberg) improved things for content-heavy pages, but it introduced its own frustrations. Eighty-plus block types, a fragmented settings UI, and an editing surface that requires you to think about layout and content at the same time.
For a client website, we solve this with Advanced Custom Fields, custom post types, and a stripped-down admin. We've gotten good at making WordPress feel purpose-built. But there's a ceiling. The underlying architecture is still a PHP monolith backed by MySQL, and every customization fights against a system that wasn't built for your specific workflow.
Page builders are worse. We've covered the technical debt they create in detail. But even the "clean" CMS options have their own friction. Webflow's CMS has a 10,000-item collection limit. Headless platforms like Sanity and Strapi are powerful, but they require you to build a front end from scratch and manage API connections between two separate systems. We explained the trade-offs in our headless CMS breakdown.
None of these were wrong. But none of them matched what we actually wanted: a single tool where we write an article, fill in metadata, hit publish, and a static HTML page appears on the live site. No build pipeline. No deploy step. No external database. No monthly SaaS fee.
So we built it.
What We Actually Built
The stack is deliberately boring.
Node.js and Express handle the server. We already had Node.js running on our hosting for the AI chat assistant, so adding a CMS meant adding routes to an existing application. No new infrastructure.
SQLite through better-sqlite3 is the database. The entire content library, metadata, settings, and relationships live in a single file on disk. SQLite's official documentation puts it plainly: for websites that receive fewer than 100,000 hits per day, SQLite will work fine as the database backend. Our insights hub gets a fraction of that. And because better-sqlite3 runs synchronously in Node.js, there's no connection pooling, no async callback chains, no race conditions. A query runs, returns data, and you move on. The library benchmarks faster than asynchronous alternatives because it avoids the mutex overhead that plagues connection-based database drivers.
TipTap is the rich text editor. It's built on ProseMirror, which is the same foundation that powers editors in Notion, The New York Times, and Atlassian products. TipTap wraps ProseMirror's powerful but steep API in something you can actually configure in an afternoon. We added custom blocks for our key-takeaway boxes, quiz callouts, and comparison tables. The toolbar shows exactly what we need. Nothing more.
Handlebars templates generate the final HTML. We wrote partials for the head, header, footer, and quiz modal. The article template slots in the content, metadata, schema markup, and related articles. When we hit publish, the CMS compiles the template, writes a static HTML file to the public directory, regenerates the insights hub page, and updates sitemap.xml. The whole cycle takes about 200 milliseconds.
The live site knows nothing about Node.js, Express, or SQLite. Visitors get static HTML served by the web server. The CMS is an authoring tool. The website is a filing cabinet full of pre-built pages.
Why the Editing Experience Matters This Much
The CMS we built isn't technically impressive. It's a CRUD app with a rich text editor and a template engine. A junior developer could build a simpler version in a weekend. What makes it worth the effort is how it feels to use.
When we open the editor, we see a clean TipTap writing surface on the left and a metadata panel on the right. Title field at the top with an italic emphasis selector for the serif heading. Slug auto-generates from the title. Category dropdown. Meta description with a character counter that turns red past 160. Publish date, status, read time and word count calculated automatically from the content.
Below that: related article selector (pick two), CTA headline and subtext fields, about entities for the Article schema, and an OG title override for social sharing. Every field we need for a complete, schema-valid, SEO-ready article lives on one screen.
Compare that to WordPress, where the same metadata lives scattered across Yoast's meta box, the featured image sidebar, custom field groups, and the default editor settings. Or to a headless CMS, where the structured fields exist but you can't preview what the published page will look like without opening a separate browser tab pointed at your front end.
We didn't build a CMS because we could. We built it because every time we published an article through an existing tool, we spent more time fighting the interface than writing.
That sounds dramatic. It's not. We tracked it. Publishing an article on our old workflow took about 45 minutes of non-writing work: filling metadata across multiple screens, generating the schema, manually building the HTML, updating the hub page, regenerating the sitemap. Our CMS handles all of that in a single click.
The Static Output Advantage
The CMS generates HTML. It doesn't serve it.
This distinction matters more than it might seem. A WordPress site processes every page request through PHP and MySQL. Even with aggressive caching, there's a layer of abstraction between the content and the visitor. Static HTML has no abstraction. The web server reads a file and sends it. Time to First Byte drops to near-zero. There's nothing to cache because there's nothing to compute.
Security is the other win. Our CMS admin panel runs on a port that's reverse-proxied behind authentication. The public site is flat files. You can't SQL-inject a directory of HTML pages. You can't exploit a PHP vulnerability when there's no PHP running. The attack surface is the web server itself and nothing else.
And the cost story is hard to argue with. SQLite is free. Node.js is free. Express is free. TipTap is MIT-licensed. Handlebars is MIT-licensed. Our CMS has zero ongoing software costs. The hosting was already paid for. The only external cost in our entire stack is the Anthropic API for the chat assistant we built, and that runs $5-20 a month.
When Building Custom Makes Sense
- You have the development skills in-house (or you are the developer)
- Your content structure is specific and predictable, not a catch-all for every page type
- The editing workflow is a competitive advantage, not a chore to tolerate
- You've outgrown the CMS you're using but don't need the complexity of a headless platform
- Your output is static content (articles, docs, marketing pages) rather than dynamic application data
- Zero ongoing software cost matters to your business model
- You want full control over the generated HTML, schema, and page structure with no plugin intermediaries
If fewer than three of these apply, an off-the-shelf CMS is probably the better call. WordPress with a good admin setup handles most business websites well. A headless CMS handles multi-platform delivery. Page builders handle marketing teams who need visual control. Custom only makes sense when the editing experience itself is the differentiator.
When You Absolutely Should Not Do This
We'd be dishonest if we didn't say this clearly: most businesses should not build a custom CMS.
If you're a local service company that needs to update a phone number and publish a blog post once a month, WordPress is the right answer. If you need e-commerce with product variants, inventory management, and payment processing, Shopify or WooCommerce will save you hundreds of hours. If you have a marketing team that needs to launch landing pages without a developer, Webflow or a well-configured WordPress install with structured fields is the move.
Building custom means owning every bug, every feature request, and every security patch. There's no plugin directory. There's no community forum where someone else has already solved your problem. When something breaks at midnight, you're the support team.
We built ours because we publish structured editorial content on a predictable schedule, we have 25 years of development experience to draw from, and the editing workflow directly affects how quickly we can produce the articles that drive our content strategy. The CMS is part of our product. For most businesses, the CMS is a tool that should stay invisible.
The question isn't "can you build a custom CMS?" Any developer with a framework and a weekend can build one. The question is whether the gap between what exists and what you need is wide enough to justify maintaining your own software for years.
For us, the answer was yes. For you, it probably isn't. And that's a fine outcome.
Sources
- SQLite — Appropriate Uses for SQLite — Official documentation on when SQLite is and isn't the right database choice, including the 100,000 hits/day guidance
- better-sqlite3 — GitHub — The synchronous SQLite library for Node.js, with benchmarks comparing performance against async alternatives
- TipTap — The Headless Rich Text Editor — TipTap editor documentation, built on ProseMirror, MIT-licensed
- Handlebars.js — Templating Engine — Official Handlebars documentation for logic-less templating with partials and helpers
- Smashing Magazine — Simplify Your Stack With a Custom-Made Static Site Generator — Case study and walkthrough for building custom static site generators
- ProseMirror — TipTap Core Concepts — How TipTap builds on ProseMirror's document model and transaction system
- Epic Web Dev — Why You Should Probably Be Using SQLite — Kent C. Dodds on SQLite's advantages for web applications, including deployment simplicity and zero-config operation
- Liveblocks — Which Rich Text Editor Framework Should You Choose in 2025? — Comparison of TipTap, Slate.js, Lexical, and other editor frameworks