Content that falls back: designing a CMS so an empty field never breaks a page
Build CMS pages so an emptied field never holes the layout. Editors clear heroes, paste over images, and save mid-swap; without fallbacks that is a blank section with full padding. Fallbacks are deliberate secondary content — committed files, sibling fields, schema defaults — not silent magic that hides unfinished work forever.
No single field should be load-bearing
The principle is easy to state. For any field an editor can empty, the page must still render something reasonable. Not a placeholder that says image missing. Something a visitor would not identify as a mistake.
This is a stronger requirement than validation. You can mark fields required in the schema, and we do where it makes sense, but required fields only protect you from the editor who is paying attention. They do not protect you from a migration that mapped a field wrong, a document created by a script, or a field that was optional when it shipped and became load-bearing three features later.
Treating every field as optional at render time is a small tax on component code, and it removes an entire class of production incident — the kind where the site looks broken and nobody can tell whether the CMS, the build, or the editor is at fault.
Images fall back to files that are committed
On Provale Cup — a marketing and commerce site for an FDA-registered metered drinking cup used by people with dysphagia — every image field has a committed counterpart under the public directory. If the CMS field is empty, the component renders the file from the repo. The editor's upload wins when it exists. The repo covers when it does not.
It matters that the fallback is committed rather than remote. A fallback that fetches from the CMS asset CDN shares every failure mode with the thing it is supposed to backstop. A file in the public directory is in the build output, deploys with the code, and cannot be deleted by anyone working inside the CMS.
The pattern itself is one line at the point of use: take the CMS URL if there is one, otherwise the path. What makes it hold up is discipline about where that line lives — in a single image resolver, not repeated across thirty components with thirty chances to forget.
The CMS itself falls back
We take the same idea one level up. Our own site reads its content from Sanity, and it also ships a committed JSON seed of that content in the repo. If the CMS environment variables are not set — a fresh clone, a fork, a preview branch that did not inherit secrets, an outage — the data layer returns the seed and the site renders correctly.
The practical effect is that you can clone the repo, install, run dev, and see the real site with no credentials at all. Onboarding a developer is one command. CI does not need production secrets to build. And a CMS outage degrades the site to slightly stale rather than to a stack trace.
It also forces a useful discipline. The seed is stored in the same shape the CMS query returns, so if the two drift, something is wrong with the mapping between them. That is exactly the kind of bug we have shipped before, and we would much rather find it at build time than in production.

A fallback is not a default
Worth separating two things that look alike. A default is a value the editor is expected to change. It is a starting point, and if it is still there in production, someone forgot. A fallback is a value that is correct to keep. Lorem ipsum is a default. A product photo shipped with the repo is a fallback.
The failure mode of confusing them is a site full of placeholder text that nobody notices, precisely because it renders cleanly. So we do not put prose defaults in the CMS at all. Text fields that are empty render nothing — the section collapses, the heading disappears, the layout absorbs it. Only images, which are structurally load-bearing in a way that sentences usually are not, get a committed fallback.
The corollary is that layouts have to tolerate missing pieces. A grid that assumes three cards and receives two should look intentional. That is a design conversation, and it is far cheaper to have during design than after an editor has already found the hole.
What it costs, honestly
The tax is real. Every optional field is a branch, and branches are where bugs live. A component handling four optional fields has sixteen possible render states, and you are not going to test all of them.
We manage that by pushing the branching down into a small number of resolvers — one for images, one for text blocks that can collapse — rather than scattering it through components. The component asks for a resolved value and receives one. The fallback logic exists in one place per kind of thing, and that place is easy to read and easy to test.
The other cost is subtler: fallbacks hide problems. A site that renders correctly with an empty CMS is a site where you might not notice the CMS is empty. That is a monitoring concern rather than an argument against fallbacks, but it does mean the fallback path should be visible somewhere — a build log line, a health check — so that quiet degradation does not stay quiet indefinitely.
Questions
- What should happen when a CMS editor clears a required-looking field?
- The page should render an acceptable substitute: another field, a committed placeholder asset, or a schema-level default. The failure mode to avoid is empty boxes and padded voids that look like broken production.
- How are fallbacks different from defaults?
- A default is what you ship before anyone edits. A fallback is what you show when an edit removes something load-bearing. Both need design; only fallbacks are tested by destructive editor behavior.
- Where should image fallbacks live?
- Prefer committed files in the repo or stable CMS assets the front end can always resolve — not hotlinked temporaries. The CMS UI can also guide editors with field-level fallbacks so empty is a valid intermediate state.