Source code from timomeh.de. Stack:
- Next.js
- Content stored in a separate (private) GitHub Repo
- Keystatic as CMS
- Drizzle with SQLite for caching and querying the content from GitHub
- hosted on Railway 🚞
Is this a bit overkill for a blog? Probably. Is it fun? Absolutely.
All posts and pages, including images, are stored in a private GitHub repository as MDX. I use Keystatic as a CMS to manage this content. I enjoy the editing experience in a WYSIWYG editor in my browser, with support for drag-and-drop file uploads. Keeping the content repository private allows me to work on drafts in private.
Keystatic only supports fetching content by a slug or fetching everything. No filtering, no joins, no sort—you have to do that in JavaScript. This can result in a bunch of requests to the GitHub API.
A fetch cache could fix that, but in theory I could still hit the GitHub API Rate Limits—especially when dependabot opens or rebases a bunch of PRs, and E2E tests constantly fetch new data from GitHub.
That's why I cache it. Caching it in a SQL database additionally gives me filters, joins, sort. Whenever content in the private repository changes, GitHub triggers a webhook that updates the corresponding caches.
Images are also stored in the private GitHub repository. To make them publicly accessible, I have a reverse proxy on an private Railway service, with a public imgproxy in front of it for image optimization.
Videos are simply uploaded to YouTube, and YouTube links in posts are automatically converted to embeds.
- Fill in env variables
pnpm db:pushto prepare databasepnpm dev- Visit https://localhost:3000/webhooks/nuke to populate database
Pushing to the main brach automatically triggers a new release:
- it builds a new docker image as release candidate
- runs e2e tests against it
- publishes the release it to ghcr.io
- redeploys the Railway service
- executes a database migration container
This is total overkill and I do it because it's fun. You might not need what I used:
- Next.js
- hosted on Railway
- Cloudflare CDN
- imgproxy for image optimization
- Keystatic as CMS
- SQLite and Drizzle
- a libSQL server (only for production, so CI can connect to it during a deployment to migrate the database)
- Umami
- Shiki
- mdx with cached rendered output
- Tailwind CSS
- Playwright & Argos CI
- LogLayer