An AI-operated personal blog platform. Write and publish from a clean multilingual admin — or hand the keys to an AI agent and let it write, publish, and even deploy for you.
🌐 Live demo · Get your own copy · Let an AI run it · Architecture · Roadmap · License
The demo at manhhung.me is the author's personal blog — a live instance to see the platform in action, not a content showcase (ignore what it says, look at how it works).
An open-source (MIT), single-owner blog built for people who just want to write. The public site is statically cached so it loads insanely fast on mobile and desktop, and it's tuned around readable typography — a clean reading experience first. Everything is easy to tweak from the admin (palettes, type, menu, fonts) with no hardcoded values anywhere, so you make it yours without touching code.
All the writing happens in a polished /admin (or over MCP). Text lives in Postgres (Supabase on Vercel, or a bundled Postgres when you self-host); binaries (images, files, icons) go through a pluggable storage driver — Vercel Blob by default, or the local filesystem on Docker. No git push to publish, no CMS to wrangle.
| Area | What you get |
|---|---|
| 🖋️ Editor | TipTap 3 + Markdown · responsive sharp images (original + AVIF/WebP variants) · 3-version time machine · 60s autosave |
| 🎨 Look | 6 customizable light+dark palettes · one tunable type system (per-role size/leading/tracking, no hardcoded sizes) · upload a custom font per weight |
| 🌍 i18n | Admin + site in en · vi · de · ja · zh · ko |
| 🔍 Reading | instant local + Postgres full-text search · ToC · related posts · reading time · progress bar |
| 📈 Built-in | cookieless analytics (views / visitors / top pages, no PII) · activity log · soft-delete Trash (nothing auto-purges) |
| 🔎 SEO | sitemap · RSS · robots.txt · llms.txt · dynamic OG images — all toggleable |
| 💾 Backups | one-click full snapshots (DB + all binaries) to Google Drive, scheduled + restore |
| 🤖 MCP | a remote endpoint that lets an AI agent write & manage the blog with the same rules as the admin |
| 📱 PWA | installable, launches standalone |
| 🔐 Auth | NextAuth v5 · Google sign-in · single authorized owner · edge-guarded admin/API |
| 🚀 Deploy | Vercel + Supabase (+ Vercel Blob), or self-host with Docker — bundled Postgres + local storage, no cloud — same codebase |
Built on Next.js 16 (App Router, React 19, strict TS) + Tailwind v4, deployed on Vercel or self-hosted with Docker.
Who it's for — one person who wants a fast, good-looking, fully self-owned blog, runs it on Vercel + Supabase or self-hosts it with Docker, and likes the idea of letting an AI agent help run it. Not for — multi-author teams / publications needing roles and editorial workflows. vibeblog is single-owner by design (one authorized email); multi-tenant lives in the planned SaaS, not here.
Three ways to stand up your own blog — pick one. Each ends with a live site at your domain.
1️⃣ Do it yourself — ~10 minutes in the dashboards
- Fork this repo (so Vercel deploys your copy).
- Database — create a Supabase project → SQL Editor → paste
scripts/schema.sql→ Run (idempotent). Copy the Project URL +service_rolekey (Settings → API). - Import to Vercel — Add New → Project → import your fork.
- Blob store — Storage → Create → Blob, connect it to the project. This injects
BLOB_READ_WRITE_TOKENautomatically. - Env vars (Settings → Environment Variables — see the table):
SUPABASE_URL,SUPABASE_SERVICE_ROLE_KEY,AUTH_SECRET(npx auth secret),AUTHORIZED_EMAIL,AUTH_GOOGLE_ID,AUTH_GOOGLE_SECRET. - Deploy. Then on your Google OAuth client add the redirect URI
https://<your-domain>/api/auth/callback/google. - Open
https://<your-domain>/admin, sign in asAUTHORIZED_EMAIL, set your title / palette / menu in Settings, and start writing.
2️⃣ Hand it to an AI agent — Claude, OpenAI Codex, OpenClaw, Hermes…
Give an agent Vercel + Supabase + GitHub access (tokens / CLI / MCP), then paste:
Deploy my own copy of github.com/joiha-steven/vibeblog:
1. Fork the repo to my account.
2. Create a Supabase project and run scripts/schema.sql in its SQL editor.
3. Create a Vercel project from the fork; add a Vercel Blob store (this sets BLOB_READ_WRITE_TOKEN).
4. Walk me step by step through creating a Google OAuth "Web" client
(you can't log into my Google account, so guide me through the Cloud Console:
create the project, configure the consent screen, create the OAuth client,
and tell me exactly what to click), then collect the resulting
AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET from me.
5. Set env vars: SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY (Supabase → Settings → API),
AUTH_SECRET (generate with `npx auth secret`), AUTHORIZED_EMAIL=<my email>,
AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET.
6. Deploy, then tell me to register https://<domain>/api/auth/callback/google
as a redirect URI on the Google client.
7. Return the live URL.
The agent does everything else; for the Google OAuth app it walks you through the clicks (it can't log into your Google account) and takes the client ID/secret back from you.
3️⃣ Self-host with Docker — your own server, no Vercel
Fully self-contained — no cloud accounts. The stack bundles Postgres + PostgREST (replaces Supabase) and the local filesystem store (replaces Vercel Blob), plus a cron sidecar. Everything runs on your host; only Google sign-in reaches the internet. Data lives in ./data/postgres (text) + ./data/uploads (binaries) — back up those two folders.
git clone https://github.com/joiha-steven/vibeblog.git && cd vibeblog
cp .env.docker.example .env.docker
node scripts/docker/gen-keys.mjs >> .env.docker # DB password + JWT secret + service key
# then fill AUTH_SECRET, AUTH_GOOGLE_ID/SECRET, AUTHORIZED_EMAIL, SITE_URL, CRON_SECRET
docker compose up -d --build # app on :3000 + db + rest + cronThen point a reverse proxy / TLS at port 3000, and register <SITE_URL>/api/auth/callback/google (and <SITE_URL>/api/backup/callback for Drive backups) on your Google OAuth client. The image needs no backend env to build — secrets are supplied at runtime. The bundled DB applies scripts/schema.sql automatically on first boot. Prefer a managed database? Point SUPABASE_URL at any Supabase project and drop the db/rest services — see .env.docker.example. The Vercel path is unchanged (Supabase + Blob).
Tip
Vercel: two vercel.json knobs to make yours — regions (defaults to sin1/Singapore — set your nearest) and maxDuration: 60 for uploads (the free Hobby plan caps function time, so trim it or upgrade to Pro for big photos). Docker: large uploads have no 4.5 MB cap (the browser posts straight to the server), so big photos just work.
vibeblog ships a remote MCP server, so a second AI agent can run your blog — drafting, editing, tagging, and publishing straight to the live site. No git, no deploy: content goes into Supabase + Blob through the same data layer (and same slug/revision/soft-delete rules) the admin uses.
- Turn it on — Admin → Settings → Advanced → MCP, generate a named token (shown once, hashed at rest, expires in 180 days).
- Connect your agent to the endpoint
https://<your-domain>/api/mcpwithAuthorization: Bearer <token>(OAuth connectors are supported too). - Prompt it, e.g.:
Using the vibeblog MCP server, write a 600-word post titled
"What I learned shipping a blog with an AI agent", give it the tags
"ai" and "writing", set a friendly excerpt, and publish it.
The post is live in seconds. Sensitive settings are blocked over MCP, and you stay the sole authority — revoke any token from the admin and it's gone.
See .env.example. The essentials:
| Variable | Required | What it is · where to get it |
|---|---|---|
AUTH_SECRET |
✅ | NextAuth secret — generate with npx auth secret |
AUTHORIZED_EMAIL |
✅ | The only email allowed into /admin — your email |
AUTH_GOOGLE_ID / AUTH_GOOGLE_SECRET |
✅ | Google OAuth "Web" client (admin sign-in + optional commenter login) — Cloud Console → Credentials |
SUPABASE_URL |
✅ | Supabase project API URL — Supabase → Settings → API. Docker: auto-set to the bundled PostgREST (http://rest:3000) |
SUPABASE_SERVICE_ROLE_KEY |
✅ | Supabase service_role key (secret, server-only) — same page. Docker: generate with node scripts/docker/gen-keys.mjs |
POSTGRES_PASSWORD / SUPABASE_JWT_SECRET |
◻️ Docker | Bundled-DB secrets for the local Postgres + PostgREST — produced by gen-keys.mjs alongside the key above |
BLOB_READ_WRITE_TOKEN |
✅ Vercel | Vercel Blob token — auto-injected when you connect a Blob store; also derives the public Blob URL. Not used on Docker (local filesystem driver) |
STORAGE_DRIVER |
◻️ Docker | vercel-blob (default) or local. The Docker image bakes in local; binaries go to STORAGE_LOCAL_DIR (default /app/uploads) |
SITE_URL |
◻️ Docker | Canonical public URL of the instance (Vercel infers this automatically). Used for OG/sitemap/auth callbacks when self-hosting |
CRON_SECRET |
◻️ optional | Protects /api/cron (keep-alive + scheduled backup) — any random string. On Docker the cron sidecar sends it |
MCP_OAUTH_SECRET |
◻️ optional | Signs MCP OAuth codes — random; falls back to AUTH_SECRET |
| Turnstile / Facebook keys | ◻️ optional | Comment anti-spam (Cloudflare Turnstile) + Facebook commenter login — enter these in Admin → Settings (stored server-side). The matching env vars (TURNSTILE_*, AUTH_FACEBOOK_*) still work as a fallback |
MCP tokens and the Google Drive backup connection are created in the admin, not via env. Secrets stay in .env.local (gitignored) + Vercel (vercel env pull) — or .env.docker when self-hosting; your blog content lives in Supabase + Blob (or the bundled Postgres + local volume on Docker), never in git. The full self-host set is in .env.docker.example.
git clone https://github.com/joiha-steven/vibeblog.git && cd vibeblog
npm install
cp .env.example .env.local # fill in the values above
npx auth secret # AUTH_SECRET
npm run dev # http://localhost:3000/adminPoint the same Supabase + Blob at local, and add http://localhost:3000/api/auth/callback/google to your Google client. npm run check:all must pass before any change is done (typecheck + lint + invariant checks + the vitest seam tests; offline, no creds); npm run build for a release.
/— public blog (published, date-reached posts) ·/category/<slug>,/tag/<slug>(slugified) · header search overlay · path-based pagination (/page/2)./admin— dashboard, editor, media, analytics, settings (owner only).- Settings is one form / one Save, three tabs — General (site, menu, reader features, SEO), Appearance (palettes, custom font, the per-role text-size table), Advanced (MCP, backups, custom CSS). Everything is injected as CSS variables, so changes apply site-wide with no redeploy.
- Performance: public pages are ISR-cached; every admin save purges exactly the affected pages through one place (
src/lib/revalidate.ts), so edits are live next request without ever serving stale. Full design + the why inARCHITECTURE.md.
Docker self-host shipped (local filesystem storage); next up: an S3/MinIO storage driver + a published GHCR image, publishing from Markdown note apps (Obsidian → Craft), and optional AI assist in the editor. See ROADMAP.md.
Two separate layers — keep them distinct:
- Code (this repo) — MIT. Free and open source: use, modify, redistribute, or sell it for any purpose, no obligation to credit (MIT only asks the license text travels with copies of the source). Fork it and run your own blog.
- Content — © all rights reserved. The writing published with vibeblog (articles, images on an operator's site, e.g. manhhung.me) belongs to its author, is not covered by MIT, does not live in this repo, and may not be reused without permission.
In short: the software is open for anyone; the author's writing is not.