When I first built the editor behind my site, I assumed the public version would be simple: require sign-in and let people use the actual product.
At first, that felt like the obvious approach. The editor already handled authentication, revision history, publishing controls, and persistence. If someone wanted to explore it, they could just create an account and walk through the real workflow.
But that turned out to be the wrong way to think about it.
The issue was not only access control. It was about defining the right product boundary.
Putting a sign-up gate in front of the production editor would have fixed one issue while introducing another. It would have opened a workflow designed for real drafting and publishing to people who were only there to explore, while also adding unnecessary friction to something meant to be quickly understood. I wanted to keep that distinction clear: the production editor is for private content work, while a public demo should help someone understand the workflow without touching real infrastructure or real content.
So instead of exposing the production system, I built a public demo route powered by seeded in-memory data.
The demo lets visitors create and edit documents, browse revision history, test publishing controls, and move through the editor workflow end to end, but every change stays inside the browser session and disappears on refresh.
That tradeoff was deliberate.
I could have created a separate demo database and demo accounts, but for this project, that would have added operational overhead without meaningfully improving the experience. A demo-only environment still needs seed data, reset behavior, lifecycle management, and guardrails to stay stable. That is real complexity for something whose purpose is to demonstrate workflow, not provide durable collaboration.
The more valuable architectural decision was separating the editor UI from the system underneath it.
I introduced a workspace adapter contract for the editor’s core operations: loading documents, creating drafts, updating content, publishing, unpublishing, loading revisions, and uploading images. The production editor uses the real API-backed adapter. The public demo route uses an in-memory adapter seeded with sample content. The workflow UI stays the same, while the backing implementation changes depending on the boundary I want to enforce.
The important part was that this was not just a demo wrapper around the existing UI. The editor had to support two different backing models without forking the workflow itself. That forced me to make the editor’s core operations explicit, identify where persistence assumptions were leaking into the interface, and move those concerns behind a stable adapter boundary. The result was not only a safer public demo, but a cleaner internal design.
That gave me a cleaner system in several ways.
First, the demo uses the same workspace shell and interaction model as the production editor instead of relying on a disposable mock.
Second, the production boundary stays intact. Public traffic never reaches the live database, private content, or authenticated document APIs.
Third, the editor became easier to test and easier to evolve. Once the UI depended on an adapter rather than a single hardcoded backend path, the separation between interface and persistence became much clearer.
The demo is intentionally limited. It does not try to prove durability, multi-user behavior, or production persistence guarantees. Its job is narrower than that: let people explore the workflow honestly, without exposing the real system behind it.
That ended up being the main lesson for me: when you want to expose a private workflow publicly, the right solution is not always putting authentication in front of the real feature. Sometimes, the better design is creating a separate public surface that preserves the interaction model while keeping the system boundary clean.