Skip to content
Build 01: Product surface

A multi-tenant content platform.

A block editor, a public renderer, an end-user account app and a control plane, over one shared package layer. Four surfaces that have to agree about a document, a tenant and a permission, which is the whole engineering problem.

TypeScriptNext.jsReactPostgreSQLDrizzle ORMTurborepo
01: The problem class

Four surfaces, one truth.

The user edits a document. A browser is eventually served a page. Those are different artifacts, produced by different code, at different times, and a builder is trustworthy exactly to the degree it keeps them in agreement. A preview that drifts, an undo that restores a state the model should never have allowed, a template edit that silently rewrites documents authored against the previous version: each is small alone, and together they are why people abandon a builder and go back to writing markup.

Multi-tenancy takes that same problem and repeats it in every other dimension. Who may edit this page, which workspace does this custom domain resolve to, which plan is this workspace on, and does this API key see anything it should not. Each is a place where two representations of one fact can disagree.

So the interesting work is not the editing experience. It is where each fact is allowed to live, and whether the design makes the wrong answer unreachable rather than merely discouraged.

02: What it demanded

The requirements that came out of that.

Not a feature list. These are the properties the system had to hold for the category to work at all.

  • A block model the editor and the renderer cannot drift apart on
  • Every save recoverable, not just the last one
  • Permissions decided in one place rather than checked at each call site
  • Tenant isolation that a forgotten WHERE clause cannot defeat
03: The decisions

Where each fact was made to live.

Four calls that shaped the platform, each with a defensible opposite that plenty of products choose.

Stated as decisions rather than features, because each has a cost. What follows is which way this one went and what it bought.

  1. Blocks are declared once and the union is generated

    A block declares its type, its validation schema, its defaults, its editor and its renderer in one definition. A manifest lists which blocks a build includes, and code generation turns that into the schema union everything else validates against. The alternative, a hand-maintained type union, drifts the first time somebody adds a block and updates three of the four places that needed it.

  2. Every save writes a version, not an overwrite

    Saving appends a numbered version row carrying a full snapshot of the document and its metadata, with the author and the time. History, diffing and rollback then read from a record that already exists rather than from an undo stack that dies with the browser tab. It costs storage, which is the cheapest thing being traded here.

  3. Permissions resolve through one rule engine

    Roles, policies and feature rollout are evaluated in a single place instead of being re-asked at each call site. Scattered checks are individually readable and collectively impossible to audit: nobody can answer what a role may actually do without reading every route that mentions it.

  4. Tenancy is a schema property, not a convention

    Workspaces, organizations, teams and domain routing are modelled as real relations with real constraints, so isolation is enforced by the database rather than by every author remembering the same filter. The opposite approach works perfectly until the one query that forgets.

04: The trade

The decision that mattered.

One call per build, taken with a real alternative on the table. The alternative is named here, and so is what choosing against it cost.

A constrained document model, not a free canvas.

The model only permits documents the renderer can render. There is no way to author a page in the editor that the publisher cannot serve, because the shapes that would produce one are not expressible in the first place.

It is a decision about what the product refuses to do, taken before any of the editing experience existed, and it is what makes the four calls above affordable. Preview parity, deterministic output and forward-compatible migrations are each tractable on a bounded set of document shapes and each open-ended on an unbounded one.

The alternative
A free canvas: absolute positioning, any element anywhere. It is the shape most builders sell and the first thing an author asks for. It is genuinely easier to build, it demonstrates better in the first five minutes, and it puts no constraint on the person using it at all.
What it cost
Expressiveness, permanently. Layouts a free canvas allows are not reachable here, and for some of them the honest answer is no rather than a workaround. The limit is also met first by the authors most willing to push the tool, which is the worst group to disappoint.
Why the trade holds
On a free canvas every document is a special case, so preview parity, responsive behaviour and schema migration each become per-document problems with no general solution. Bounding the model turns three open-ended problems into three finite ones, and a finite problem can be closed. The cost is paid once, in what the tool will not do. The alternative bills forever, in what it cannot promise.
No published measurement

The comparison above is an argument about which failures each option makes possible, not a benchmark. Nothing has been run against a fixed evaluation set, so there is no number to report here, and nothing above should be read as implying one.

03: Next step

Ask about any of these.

The specifics are not on this page. Ask for them directly, or send the problem you actually need solved.