A reusable, self-hostable blog engine. Write Markdown in a web editor, store it in Postgres, deploy anywhere Next.js runs (built Vercel-first). Dark academia out of the box; restyle it with an AI agent in minutes (see Customizing with an AI agent).
Stack: Next.js (App Router, TypeScript) · Tailwind CSS v4 · Drizzle ORM + Neon Postgres · Auth.js (GitHub OAuth) · remark/rehype Markdown pipeline with syntax highlighting.
Features
- Markdown posts with GFM (tables, fenced code + syntax highlighting) and safe
inline HTML (
<script>is always stripped). - Homepage search (title, content, tags) and sorting (created/modified, asc/desc) via GET params — results are linkable.
- Pinned posts always sort first, marked with a wax seal.
- Auth-protected
/admineditor: live preview, tag input, pin toggle. - Single-table Postgres schema; no filesystem storage, serverless-friendly.
-
Clone and install
git clone https://github.com/CrazyWillBear/blogman && cd blogman npm install
-
Create a Neon database at neon.tech (free tier is fine) and copy the connection string.
-
Create a GitHub OAuth app at GitHub → Settings → Developer settings → OAuth Apps. Callback URL for local dev:
http://localhost:3000/api/auth/callback/github(add a second app or update the URL for production). -
Configure env:
cp .env.example .env.localand fill it in.ADMIN_GITHUB_LOGINSis a comma-separated list of GitHub usernames allowed into the editor. GenerateAUTH_SECRETwithnpx auth secretoropenssl rand -base64 32. -
Create the schema
npm run db:migrate
-
Run
npm run dev
Sign in at
/adminand write your first post. -
Make it yours: edit
blog.config.ts(name, description, footer links).
Import the repo in Vercel, set the same env vars (DATABASE_URL,
AUTH_SECRET, AUTH_GITHUB_ID, AUTH_GITHUB_SECRET,
ADMIN_GITHUB_LOGINS), and point your production GitHub OAuth app's callback
at https://<your-domain>/api/auth/callback/github. No other config — there
is no filesystem storage or long-lived process.
The engine is built to be restyled and reconfigured by AI coding agents
(Claude Code, etc.). CLAUDE.md carries the project rules so an agent knows
the checks to run. Paste-ready prompts:
Rebrand this blog: update
blog.config.tswith my blog name "…" and a description about …, and update the footer links to point at my GitHub.
Restyle the blog from dark academia to a [minimal light / brutalist / retro-futuristic] theme. The full design system lives in
app/globals.css(CSS variables, ornaments, prose styles) and the fonts inapp/layout.tsx. Keep the layout structure and accessibility intact, then runnpm run checkandnpm run build.
Add an RSS feed at
/feed.xmlusing the existinglib/posts.tsquery helpers, following the conventions in CLAUDE.md.
Useful knobs: blog.config.ts (identity), app/globals.css (palette,
texture, type scale), app/layout.tsx (fonts), components/ (cards, seals,
controls), lib/markdown.ts (markdown features and sanitization).
npm run dev # dev server
npm run check # lint + typecheck + tests (Vitest)
npm run build # production build
npm run db:generate # generate SQL migration after schema changes
npm run db:migrate # apply migrationsCI runs the same checks on every push.