No CMS does not mean no content model
4 min read Augustyn Głowacki
When a no-CMS website becomes painful to edit, the root cause is almost never the missing CMS - it is the missing content model that got dropped alongside it. A CMS enforces a schema as a side effect of existing: field types, required fields, a fixed vocabulary of section types. Remove the CMS without rebuilding that enforcement in code and every page becomes an unstructured document that only its last author understands. This site runs with no CMS on a content model of 20 typed section types, validated on every build, and any section repeated across pages exists in exactly one file that pages reference.
Why does editing a no-CMS site get painful?
Because the structure that made editing predictable left with the CMS. Over the past few months, several consultancy requests reached me through Bejamas, the agency I cooperate with, and they shared one shape: a company ships its site as markdown or YAML files, no CMS, and within months editing has become slower than it was before. The symptoms repeat across those requests - nobody is sure which file owns a given piece of copy, the same section exists as four slightly diverged copies, and a “content change” routinely turns into a developer task because only a developer can tell what a file’s fields mean.
The teams asking for help usually frame the problem as tooling: which editor, which git UI, which preview setup. In every case I have looked at so far, the tooling question was downstream of a modeling question nobody had asked - what is a page allowed to contain, and who enforces that?
What was the CMS actually doing for you?
Enforcing a content model, whether or not anyone called it that. A CMS makes an editor pick a content type before writing, restricts each entry to that type’s fields, marks fields required, and offers a closed list of section or block types to compose a page from. Teams that leave a CMS tend to keep the files and drop all four of those constraints at once, because none of them looks like a feature - they look like friction, until they are gone.
Plain files enforce nothing by default. A YAML page definition accepts any key, a markdown file accepts any structure, and the first typo in a field name fails silently instead of loudly. The discipline has to be rebuilt in code, and the tools for that already sit in the stack most no-CMS sites are built on.
What does a content model look like in plain files?
A schema the build refuses to pass without. On this website, every content collection validates against a Zod schema at build time, and Astro generates TypeScript types from those schemas, so component props and content files cannot drift apart (Astro content collections docs). Three decisions carry most of the weight:
- A page’s sections are a closed vocabulary: one
z.discriminatedUnionover 20 section types, from hero to lead form. A page composes from that list and nothing else. - Every object is a
z.strictObject, which rejects unknown keys instead of ignoring them (Zod docs) - a typo liketitelfails the build with a named error rather than shipping a section with a silently missing title. - Every field carries a
.describe()string saying what it is for, so the schema doubles as the documentation an editor - human or AI agent - reads before touching a file.
One more constraint closes the loop on the rendering side: the renderer’s component map is typed satisfies Record<Section["type"], unknown>, so adding a section type to the schema without adding its component is a compile error, not a blank spot on a page.
When should a repeated section be defined once instead of pasted?
Only when it is genuinely identical across pages - and then exactly once. The default on this website is inline: a section lives inside the page that uses it, because most sections that look similar are not the same content and will diverge. A section that is truly identical everywhere it appears - the same call-to-action block, the same trust strip - moves into a sharedSections collection as one YAML file, and each page replaces the pasted copy with a pointer: { type: "sectionRef", ref: "<id>" }. Editing that one file updates every page that references it.
Two rules keep the mechanism from growing into its own maintenance problem. A sectionRef cannot point at another sectionRef - the resolver never recurses, because a chain of pointers buys nothing over pointing at the real target. And a broken reference fails validation at build time, so a page can never ship pointing at a shared section that was renamed or deleted. Inline versus reference is the whole decision, and it is the same decision a CMS makes you take when you choose between a one-off block and a reusable entry - dropping the CMS did not make the decision go away, it made it invisible.
What does this not solve?
The editing interface for a non-technical team. A strict schema makes files safe to edit, and it is what makes this site editable through an AI coding agent - the agent reads the schema’s descriptions, writes YAML against it, and the build rejects anything malformed. It does not give a marketing team of ten a visual editor, a preview button, or a publishing workflow, and for that team, a CMS remains the right tool. The honest framing for anyone choosing: no CMS is a decision about the editing interface, and it was never a permission to skip the content model. The model is the part that was mandatory all along.