LagomCMS, part 2: what the last generation got right and wrong

On this page

This is part two of four about LagomCMS, the CMS behind this site. Before designing it I went through the systems of the last fifteen years. Here's what each got right, where each runs out, and what I kept.

Static site generators

Jekyll (2008), Hugo (2013), Eleventy. Content in Git, readable Markdown, no runtime to patch, and the site is a folder you can diff. They all hit the same wall in the same order:

  • Non-developers can't use them. Asking a colleague to clone a repo and open a pull request to fix a typo isn't a workflow.

  • Anything dynamic needs another service. Search, forms, comments. You end up with a static site plus four SaaS products.

  • Builds get slow. Fine at a hundred pages, a fifteen minute wait at fifty thousand.

Git-backed editors like Netlify CMS added the missing way in and leaked constantly: merge conflicts nobody could act on, publishes that silently failed. What I kept: content in files is right. What made the workflow unusable was never the storage, it was the lack of an interface.

Flat-file CMSes

Kirby (around 2012), Statamic, Grav. A page is a folder holding its text file and its images. Deleting a page deletes its images, moving is mv, backing up is tar, content is greppable, history is Git. A Kirby site from 2012 is still readable today. A WordPress site from 2012 is a liability.

Where they run out:

  • Listing and filtering means walking the tree, so past a few thousand pages they need an index. Statamic built its own, the Stache.

  • Two editors saving the same file: last write wins, and the other person's work is gone without an error.

  • A typo in a front matter key quietly becomes a new field.

What I kept: one folder per document, with its media inside. What I changed: the index is designed in from day one, as something disposable.

Headless and API-first

"Headless" only means the CMS doesn't render pages. Past that, the systems disagree a lot:

  • Contentful: hosted, model edited in their UI, you pay per API call for your own writing.

  • Sanity: the body as structured blocks and its own query language. Powerful, not portable.

  • Strapi: self-hosted Node that made self-hosting normal again, with a weak generated admin.

  • Directus: an admin generated over an existing SQL database.

  • Payload: the model is a TypeScript config, and the admin, API and types are generated from it. Access control is functions. The clearest model-as-code system I've used.

The difference that matters most is where the model lives, because it decides who can change your content's shape and whether that change gets reviewed. Every headless setup also hits two costs by month two: preview becomes a feature someone has to build, and menus, redirects and sitemaps move into the frontend.

None of them keeps content in a form that outlives them. Export gives you JSON shaped by their internals. The flat-file systems kept the documents and gave up querying. The API-first systems kept querying and gave up the documents. LagomCMS tries to keep both.

Plugins: WordPress and Neovim

WordPress's roughly sixty thousand plugins are why it runs so much of the web. The mechanism is tiny: do_action and apply_filters at named points, callbacks with a priority. Stable names mean a 2012 plugin still runs. The costs come from the same place: every plugin shares one process and can reach anything, everyone picks priority 10 so order falls back to load order, and nothing declares what a plugin may do.

Neovim makes the same trade (plugins in the same interpreter, no sandbox) but is honest about it, and reloads plugins without a restart. What I kept: a flat hook model, just arrays of callbacks sorted by priority, and the honesty. LagomCMS plugins are trusted code, and the docs say plainly that the restricted environment stops accidents, not attackers.

What actually makes a CMS slow

Not the database. A well-indexed query for one post takes under a millisecond. On a stock WordPress with twenty plugins the time goes to:

  • Bootstrap: loading core, loading every plugin and registering thousands of hooks, on every single request.

  • Query multiplication: ninety tiny queries per page, because every question a template asks is its own query.

  • Everything after the HTML: stylesheets, scripts, fonts, oversized images.

The industry's answer was four cache layers (object, page, CDN, browser), and each one is another reason a published change doesn't show up. The real fix is upstream: don't rebuild the application on every request, and fetch what a page needs in one pass. OpenResty (Lua inside nginx) keeps state in memory between requests and has no per-request startup at all. People put it in front of applications. Almost nobody builds the application in it. That's where LagomCMS starts.

Next: the design.