I love Jamstack. I've built a lot of sites with Next.js — personal projects, client work, side products. The pattern clicks for me: static by default, dynamic where it matters, deployable anywhere.
But every time I needed to manage content, I hit the same wall.
The problem with headless CMS tools
The cloud-based options — Contentful, Sanity, Prismic — are genuinely good. But they come with pricing tiers that feel arbitrary when you're a solo developer with three projects and a limited budget. You start free, you grow a little, and suddenly you're deciding whether a blog is worth $99/month.
The open-source alternatives (Strapi, Payload, Directus) are powerful, but they require a server and a database. That's not a complaint — it's just not what I wanted. I didn't want to provision infrastructure to publish a blog post.
What I actually wanted was simple: a free, cloud-native headless CMS with no server to run and no credit card required.
Then I thought: wait. I already have GitHub.
GitHub is already a content store
Think about what a GitHub repository gives you out of the box:
- File storage (Markdown, JSON, images)
- Version history on every change
- A complete REST API to read any file
- Access control via tokens
- Zero hosting cost
It's not a CMS. But it has everything a CMS needs to be built on top of it.
So I built @asahi-fj/structcms — a small npm package that fetches and transforms content stored in a GitHub repo into structured data your app can consume.
npm install @asahi-fj/structcms
import StructCms from "@asahi-fj/structcms";
const cms = StructCms({
token: process.env.GITHUB_TOKEN!,
owner: "your-org",
repo: "your-content-repo",
});
const posts = await cms.getAll();
A token, an owner, a repo. You get back typed content. That's the whole setup.
Why not just use the existing tools?
There are projects that do something adjacent — Outstatic, Decap CMS, Contentlayer. I looked at all of them.
Outstatic requires the content to live in the same repo as your app. Decap CMS needs Netlify or a Git Gateway. Contentlayer reads from the local filesystem — great for monorepos, but useless if you want to share content across projects.
None of them let you point at an arbitrary GitHub repo, owned by anyone, and just fetch the content with a token.
That specific combination — separate content repo, private repo support, zero infrastructure — is what I wanted. So I built it.
Content structure
Content lives under a contents/ directory. Each entry is a slug-named folder with two files:
your-content-repo/
└── contents/
├── hello-world/
│ ├── content.md
│ └── meta.json
└── getting-started/
├── content.md
└── meta.json
meta.json handles structured metadata:
{
"title": "Hello World",
"publishedAt": "2026-04-11",
"draft": false,
"tags": ["typescript", "jamstack"]
}
content.md is plain Markdown. No frontmatter gymnastics, no special syntax. Write it anywhere, commit it, and it's live.
The unexpected use case
After I published this on npm and wrote about it on Zenn, someone pointed out a use case I hadn't thought of: AI-generated content pipelines.
The idea is to point a Claude Code agent at the content repo. It reads existing posts, learns the writing style from the commit history and file structure, then generates new articles. Those articles get pushed back to the repo. The next generation has more examples to learn from.
Because structcms keeps the content repo separate from the app repo, you can run this loop without touching production code at all. The agent only writes to contents/. The app calls cms.getAll() and stays completely unaware of how the content got there.
I didn't design for this. But it makes sense. Git is already the right place to review AI-generated content — you can diff it, revert it, approve it before it goes live. The infrastructure was already there.
What this has to do with being a solo founder
I'm building another product in parallel — Ordia, a Slack-native tool for engineering teams. It monitors Jira and GitHub to surface stalled tickets and blocked PRs, proactively, without requiring engineers to change how they work.
structcms started as something I needed for my own projects. But building it taught me something I keep coming back to with Ordia too: the best tools are the ones that disappear.
structcms has almost no API surface. Two methods. No config files, no plugins, no admin UI. It does one thing — gets content out of a GitHub repo — and gets out of the way. I had to resist the urge to add more. A webhook system. A preview mode. A caching layer. All of it would have made it heavier and harder to trust.
The same instinct drives how I'm building Ordia. No new UI to adopt. No per-user accounts to set up. It works through Slack, which engineers already have open, and surfaces information they were going to look for anyway. The goal is always to reduce friction, not just change what the friction is.
Requirements and links
- Node.js 18+
- GitHub Personal Access Token with read access to the content repo
npm install @asahi-fj/structcms
Source: github.com/asahi-fj/structcms
If you're building on Jamstack and want content management without the infrastructure overhead, give it a try. And if you're also thinking about how to make developer tooling less interruptive — I'd love to hear what you're working on.
Top comments (0)