A beautifully simple, git-backed blog & memo app. Write Markdown, keep everything in your own Git repo, serve it anywhere. Inspired by tinymind; live at blog.minghe.me.
cici is pure tooling. Your content — posts, memos, config — lives in your repo or
folder. You install cici (npm i cici / npx cici) and point it at that content. This
repo ships only the app plus a tiny sample-content/ demo used for local dev and tests.
Your content lives under a data/ folder — in a Git repo this sits at the repo root,
and it's exactly what a deploy serves:
data/
blog/ # one <slug>.md per post (front-matter + Markdown)
memos.json # short-form memos
cici.json # title, author, social links, analytics (optional)
highlights/ # one <slug>.json per post (optional)
likes.json # like counts (optional)
assets/ # images uploaded from the editor (local mode)
Run cici against a local folder or a GitHub repo — no clone, no build:
# Serve AND edit local content (open /editor to write; changes save to disk).
# --dir points at the folder that contains blog/ — in a content repo that's data/:
npx cici --dir ./data
# Serve a GitHub content repo (reads its data/ automatically, read-only)
npx cici --repo owner/name
# ...with a token, edit it too (commits back to the repo)
npx cici --repo owner/name --token ghp_xxx --port 4000Options: --port, -p (default 3000), --host (default 127.0.0.1). Run npx cici --help.
Your content repo is the thing you deploy — it just depends on cici. No app source, no fork of this repo.
1. In your content repo, add package.json:
{
"private": true,
"scripts": { "build": "cici build" },
"dependencies": { "cici": "^0.6.0" }
}2. Add vercel.json (so Vercel serves cici's output instead of running its own Next build):
{ "framework": null, "buildCommand": "cici build" }cici build emits Vercel's Build Output API (.vercel/output/) — cici's prebuilt server
as one function plus static assets. Vercel serves it directly.
3. Set environment variables on the Vercel project:
| Var | Value |
|---|---|
CICI_REPO |
owner/name of your content repo |
GITHUB_ID / GITHUB_SECRET |
a GitHub OAuth app (callback <site>/api/auth/callback/github) — /editor sign-in |
NEXTAUTH_SECRET |
any random string (signs the session) |
NEXTAUTH_URL |
your site URL, e.g. https://blog.example.com |
Use a public content repo for a hosted deploy: cici reads it anonymously (no token),
/editor requires GitHub sign-in, and only the repo owner can publish — everyone else
just reads.
Don't set
CICI_TOKENon a hosted deploy. cici ≥ 0.6.0 ignores it in OAuth mode — a shared server token would otherwise authorize every visitor to write via/editor.CICI_TOKENis only for the localhost CLI (npx cici --repo … --token …) on your own machine; that's also where a private content repo is edited.
Deploy. cici reads your content from CICI_REPO at request time, so edits (via /editor
or a git push) show up without a redeploy.
Prefer a plain Node host (Railway / Render / Fly / a VPS / Docker)? Skip
cici buildand runcici startwith the sameCICI_*env — it boots the server directly.
Open /editor to write posts and memos. In --dir mode it writes to disk; on the localhost
CLI a --token commits back to the repo. On a hosted deploy, /editor requires GitHub
sign-in and only the repo owner can commit (see the deploy note about CICI_TOKEN above).
cici has built-in support for Umami — privacy-focused analytics with no cookies and no GDPR banner needed.
Enable in data/cici.json (your content repo):
{
"analytics": {
"enabled": true,
"umamiWebsiteId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}That's it — works on Vercel and locally. For the CLI, you can also pass it inline:
npx cici --dir ./data --umami-site xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxThe CLI flag overrides cici.json. Self-hosted Umami? Add "umamiScriptUrl".
Migrating from
site-config.json? Rename it tocici.json. Both names are supported, butcici.jsonis preferred.
git clone https://github.com/metrue/cici.git
cd cici && npm install && npm run dev # serves the bundled sample-content/
npm test- Runtime layer (
lib/runtime/): aContentProviderinterface withLocalProviderandGitHubProvider;getProvider()picks one fromCICI_DIR/CICI_REPOenv. - Render layer (
app/,components/): depends only ongetProvider()— never onfsor Octokit directly. - CLI (
bin/cici.js):--dir/--repo(serve),build(Vercel Build Output),start(boot from env).
→ The Complete cici Guide — setup, customization, GraphQL API, deployment, and tips.