Dev.to WebDev ๐Ÿ›  Dev ๐Ÿ‘ 0 ๐Ÿ“– 3 min read

I built a CMS that needs no account, no database, and no API key

Every time I wanted to add some editable content to a small site, the "modern" answer was a headless CMS. And every headless CMS wanted something before it would let me type a single word: an account, a database, an API

I built a CMS that needs no account, no database, and no API key

Every time I wanted to add some editable content to a small site, the "modern" answer was a headless CMS. And every headless CMS wanted something before it would let me type a single word: an account, a database, an API key, a plan tier.

For a personal site or a tiny project, that always felt absurd. I'm renting a company to hold three blog posts โ€” and if I ever want to leave, getting my own words back is an export job.

So I built JustJSON. It wants none of that.

One command, no setup

npx @kdrgny/justjson

That opens an editor in your browser. You design a schema by clicking, write your content, and everything is saved to content/*.json in your own folder. No account. No database. No API key. Nothing is uploaded anywhere โ€” it runs on your machine and stops when you close it.

JustJSON in action โ€” edit a post, save it to disk, restyle the generated site with a live preview

Your content is actually just files

There's no magic store behind it. A collection is a folder, an entry is a file:

content/
  _schema.json          โ† your schema (built in the UI)
  posts/
    hello-world.json    โ† one entry per file
  settings.json         โ† a singleton

It diffs cleanly in git, versions like code, and you can read it however you want. JustJSON only edits the files โ€” it never holds them hostage.

Typed, and wired into your build

Content you can't use is just a pile of JSON. So JustJSON generates a typed loader from your schema:

npx @kdrgny/justjson types
import { loadPosts, loadSettings } from './content'

const posts = await loadPosts()        // fully typed, slug merged in
const settings = await loadSettings()  // Settings | null

Zero dependencies, reads straight from disk โ€” so it works in any Node-based build, including Next.js server code. And for Astro, there's a dedicated loader that turns your content into real content collections in one line:

// src/content.config.ts
import { justjsonCollections } from '@kdrgny/justjson-astro'

export const collections = await justjsonCollections()

getCollection() / getEntry() just work, drafts are skipped, and saving in the editor updates the dev server without a restart.

Catch bad content before it ships

Because it's local, there's no server validating your data โ€” so there's a command for CI:

npx @kdrgny/justjson validate --strict

It walks your content and fails the build on broken relations, duplicate slugs, missing media, and type errors. A typo never makes it to production.

From nothing to a live site

If the folder has no site yet, JustJSON can scaffold a small Astro site wired to your content, let you style it with a live preview, and hand the repo to Vercel or Netlify โ€” all from the editor. Still no account with anyone; the files stay yours.

Try it right now โ€” no install

If npx is too much commitment, the whole thing boots in your browser:

๐Ÿ‘‰ Open the playground on StackBlitz

What it's not (being honest)

JustJSON is deliberately local-first and single-user โ€” git is the collaboration layer, not a multiplayer server. It's MIT, and it's an early solo side project. So if you try it, I'd genuinely love to hear where it breaks or what's missing.

If you're wondering how it stacks up against Keystatic, Decap or Tina, I wrote an honest comparison here: justjson.vercel.app/vs.

Links

If the idea of "own your content โ€” it's just JSON" resonates, a star or a bit of feedback would make my week. ๐Ÿ™

๐Ÿ“ฐ Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.