A block-based CMS for React Router

A content system you own

Not a service. Not a headless API. Not a dashboard you log into somewhere else. Vinum is a handful of packages you import: its admin mounts inside your route tree, its loaders run in your server, and your content lives in your own store, on your own disk. There is no instance to keep alive and no key to rotate.

// app/vinum.server.tsexport const vinum = registerVinum(  defineVinum({    site:  { name: "My Site" },    store: denoKvStore({ path: "./data/kv.sqlite" }),    media: fsMedia({ dir: "./data/media" }),    auth:  { cookieName: "site_admin", secret: env("SESSION_SECRET") },  }),);
That is a complete CMS: an admin at /admin, pages at /<slug>, drafts, revisions, scheduled publishing, media, forms and redirects.

What we commit to

Five commitments

  1. Your data outlives our code

    Keys are strings, values are plain JSON. No ORM, no migrations framework, no proprietary encoding — anyone with a SQLite viewer can read your content. Lock-in is a bug.

  2. Boring on purpose

    Web-standard Request and Response. Web Crypto. A key-value interface with five methods. Every dependency we add is an upgrade you will one day have to perform, so we add almost none.

  3. Safe by construction, not by discipline

    Nothing authored is trusted — not from an editor, not from an import file, not from an agent holding a tool. Every write passes through a pure sanitizer that drops what it does not recognise. No raw HTML from data reaches output, ever.

  4. The CMS has no taste — it has one place to put yours

    Vinum ships structure and a styling contract; you ship the values. An editor chooses what a block says and which variant it wears, never its colour or its spacing. That boundary is why a client can edit all afternoon without breaking the layout.

  5. Editing happens where the content lives

    On the page it renders on, in the layout it will actually have, with your real components. Not a form on the left and a preview on the right. The inline editor and the public renderer are one code path in two modes.


The escape route

You can leave

This section exists because the answer elsewhere is often "you cannot". Wix's own help centre states a Wix site needs to be hosted and operated on Wix's servers; you can export a CMS collection as CSV and nothing else, so leaving means rebuilding.

Vinum exports everything as one JSON file — from the admin, or one call in code. It is not a special export mode. It is the same code path the Backup screen uses every day.

{  "version": 1,  "exportedAt": 1754481600000,  "entries": [    { "key": ["page", "about"], "value": { "slug": "about", "title": "About" } },    { "key": ["rev", "about", 1754000000000], "value": {} }  ]}
The whole format: a flat list of key/value pairs, using the same documented keys the running site uses. Read it before you commit to Vinum rather than after — an escape route you have not checked is not one.

Deliberate omissions

Saying who a tool is not for is how the right person recognises themselves. None of these is a roadmap item.

What it refuses to be

A headless API?
No. If you want to query content over GraphQL from a separate frontend, Vinum is the wrong tool. It is deliberately coupled to the app that renders it — that coupling is what buys inline editing and one-deployment operations.
Multi-tenant SaaS?
No. One install, one site, one filesystem. Anything else changes every security assumption in the codebase.
A plugin marketplace?
No. Extension happens in your own codebase, in TypeScript, with types. There is no plugin loader and there will not be one. Of 11,334 WordPress vulnerabilities disclosed in 2025, 91% were in plugins.
A page builder with sixty blocks?
No. Core ships the blocks nearly every site needs, as a stock set you pick from and edit in place — not a box of primitives, and not a palette that needs a search field. Your weird hero belongs in your repo, registered through the same public API our own blocks use.
A commerce platform?
No. Vinum grew inside a watch shop and the shop code stayed there. Vinum knows about pages, not products.

Where it sits

An honest comparison

Vinum is small on purpose, and small has costs. The last row is the one that matters most if you are choosing between these.

VinumWordPress
Extending itTypeScript in your own repo, through the same API the core blocks use.A plugin ecosystem — and 91% of 2025's 11,334 disclosed vulnerabilities were in plugins.
Getting outOne JSON file with documented keys, importable into a fresh install.Exporters exist, with fidelity that depends on which plugins wrote your content.
Where you editOn the page, in its real layout, with your components.Gutenberg. Sentiment across 340+ opinions is roughly a coin flip.
OperationsNone. It deploys with your app because it is part of your app.A PHP host, core and plugin updates, and a security surface to watch.
EcosystemAlmost none. If you need a feature nobody wrote, you write it.Enormous, and a real advantage. Whatever you need probably exists already.

Start here

Install it

npm install @vinumcms/core @vinumcms/adapters \  @vinumcms/server @vinumcms/react @vinumcms/admin npx vinumcms doctor   # checks the three settings that fail silently
Then wire fifteen lines, mount the routes, and sign in at /admin. The setup guide lives with the admin package.

The setup guide — install, wiring, the three build settings, and the eighteen theme variables.

The packages — core, server, react, admin, adapters, and mail if you send any.

Vinum is small on purpose. If it ever feels big, something has gone wrong.

Lees dit in het Nederlands