01
Your domain, your database
Deployed to your hostname. SQLite volume mounted into your container, owned by your filesystem. The reader sees your brand; the operator sees your storage.
Blog · A branded blog deployed to your domain
The blog is the consumer end of a content pipeline. A thin Next.js renderer with Prisma + SQLite, deployed to your domain, that accepts signed publishes from a content engine you also own. No editor in the product, no content team required to keep it fed.
Your domain. Your database volume. Your HMAC secret to rotate.
Surface signal
Status
LIVE
Tenancy
Per-domain
Reference
blog.eleven11.pro
Why this exists
Most of what gets sold as a "branded blog" is one of two things: a managed CMS that locks you into someone else's editor and database, or a static-site repo that quietly demands a content team to keep it fed. Neither shape fits a serious operating company that wants a blog on its own domain without inheriting a second product to administer.
The blog you are reading about is the consumer end of a content pipeline. The producer (e11-pr) writes; the consumer renders. The split is the point — a thin renderer is a small surface to own, and a content engine that targets it is what removes the content team.
Self-sustained by design
A branded blog is a load-bearing marketing surface. It should not be the kind of thing whose pricing model, plugin store, or auth provider can change under you on a Tuesday.
01
Your domain, your database
Deployed to your hostname. SQLite volume mounted into your container, owned by your filesystem. The reader sees your brand; the operator sees your storage.
02
A secret you can rotate
Every signed publish carries an HMAC-SHA256 over the raw body. Rotate `PR_PUBLISH_SECRET` (or the row in `SiteSettings`) and the producer is locked out until you re-share. Sovereignty is a function of which key you hold.
03
No editor, no surprise
There is no in-product compose UI, no draft autosave, no plugin surface. Posts arrive over a single signed endpoint. That is the whole write path. Smaller surface, fewer ways to be surprised.
04
SEO is yours to edit
`metaTitle`, `metaDescription`, `canonicalUrl`, `keywords`, `tags`, JSON-LD blocks — all editable through a bearer-gated admin endpoint and preserved across re-publishes via `preserveBlogSeo`. The renderer holds the authoritative SEO state.
05
A renderer you can read in an afternoon
Next.js App Router, Prisma, two ingest routes, one upsert path, one schema renderer. No queue, no worker, no headless platform. The whole consumer fits in your head.
The primitive
The blog is built on a small, opinionated trio — a row to store the post, a signed endpoint to write it, a public route to render it. There is no fourth thing. Anything that looks like a fourth thing is a server action that mutates one of those three.
01 · Source of record
BlogPost (Prisma)
One row per slug. Body, excerpt, cover, status — plus the SEO columns and the cached score. Indexed on status and publishedAt because the list query is the hot path.
02 · Signed write
POST /api/internal/pr-dispatch
Two routes, one upsert. `pr-dispatch` takes the publish-worker shape; `pr-publish` keeps the flat-shape legacy path. Both verify `X-Pr-Signature: sha256=<hex>` against the raw body and refuse anything else.
03 · Public surface
GET /blog/[slug]
Markdown or HTML in, JSON-LD graph out. Internal-link rewriting, related posts, cover image, RSS at `/feed.xml`, sitemap and robots as Next metadata routes. Cached at the edge where it makes sense.
How it fits the fleet
The blog renders. pr produces. Sold and operated as a system, the two repos are how a customer ends up with editorial output on their domain without hiring a content team or buying a CMS. The other tools in the fleet wire into the same pipeline.
pr
The engine. Profile + factBundle + LLM compose the post; the publish worker signs the body and POSTs `pr-dispatch`. pr is the producer — this blog is the consumer end of that pipeline. Sold and operated as one system.
operator
Where the blog is administered. Per-instance HMAC visibility, secret rotation, target wiring. Operator is how a deployed blog gets configured without SSH-ing into the box for every change.
alerts
`blog.ingest.signature_invalid` is the highest-severity event the renderer emits — a wrong-key publish reads as either a misconfigured rotation or a probe. `blog.ingest.received` and `blog.ingest.failed` are the per-post heartbeat.
discovery
When the blog is part of a customer site rebuild, discovery's intelligence layer can read the published surface back as a tech fingerprint — so the audit engine knows what the rebuild actually shipped.
architect
Posts that originate from architect matters carry the `publishIntentId` back through the pipeline, so a draft locked into a session has a traceable line to the URL it eventually became.
phoenix
When phoenix rebuilds a WordPress site into a static Astro shell, the editorial stream that used to live in WP can move here — same domain, same canonical URLs, no more plugin attack surface.
Surfaces & contracts
Three reader-facing routes, two signed ingest paths, one bearer-gated admin patch. The smallest surface that runs a serious branded blog.
GET /blog
Post list
Newest first, status-indexed, paginated when it has to be. The default reader's entry point.
GET /blog/[slug]
Post detail
Markdown/HTML render, JSON-LD schemas in document order, internal-link rewriting against known slugs, related posts (up to four), cover image with OG and Twitter cards.
GET /feed.xml
RSS feed
Hand-rolled RSS, fifty most recent posts, one-hour edge cache. Readers and aggregators that don't want to crawl the HTML get a clean stream.
POST /api/internal/pr-dispatch
Signed ingest (native)
Publish-worker shape — top-level `publishIntentId`/`runId`/`artifactId`, post fields nested under `payload`. HMAC-verified, idempotent on slug, emits an alert on every outcome.
POST /api/internal/pr-publish
Signed ingest (flat)
The flat-shape legacy path. Same HMAC, same upsert, same response contract — kept for publishers that haven't moved to the wrapped envelope yet.
PATCH /api/admin/blog/[slug]
SEO admin
Bearer-gated edits to the SEO columns and JSON-LD blocks, recomputing the cached SEO score on every write. Combined with `preserveBlogSeo: true` on ingest, edits survive re-publishes.
Senior engineering, visible
Five decisions visible in the ingest code, the secret resolution path, the entrypoint script, and the absence of a logging line where one would have been convenient — not adjectives, design choices.
Constant-time HMAC
`verifyPrPublishSignature` uses constant-time comparison. Signature checks should not be where a timing oracle lives.
DB-first secret resolution
`getPrPublishSecret()` reads `SiteSettings.prPublishSecret` first, then falls back to the env var. Rotation is a runtime PATCH, not a container restart and a redeploy.
Migrations run at boot
`prisma migrate deploy` runs in `docker-entrypoint.sh` before the server binds. The build stage runs the same against a throwaway DB so Prisma Client compiles. There is no manual schema step on the customer host.
Two ingest paths, one upsert
`pr-dispatch` and `pr-publish` parse different envelopes and call the same `upsertBlogPostFromIngestPayload`. Behavioral parity is enforced by code share, not by hope.
Body never logged
Neither ingest endpoint writes the request body to the application log. Drafts and unpublished content do not leak into your logging stack while the pipeline is in flight.
Who this is for
The blog earns its keep when search traffic and editorial reputation are operating assets — and when the cost of administering someone else's CMS starts to look like the cost of owning the renderer instead.
FAQ
From e11-pr, the content engine. pr composes posts from a profile and a fact bundle, signs the body, and POSTs to the renderer. The producer/consumer split is the point — sold and operated as one pipeline.
Rotate `PR_PUBLISH_SECRET` or clear the `prPublishSecret` row in `SiteSettings`. Every subsequent publish fails the HMAC check until you re-share. The customer holds the kill switch.
Yes. With `preserveBlogSeo: true` on ingest, the upsert keeps existing `metaTitle`, `metaDescription`, `canonicalUrl`, `keywords`, `tags`, `category`, `author`, and JSON-LD blocks — and only rewrites body, title, excerpt, and cover. Your SEO manager's edits are not overwritten by the engine.
Blog scale is hundreds of posts, not millions. SQLite gives a single file the customer can back up, restore, and inspect with one tool. There is no second service to administer, no connection pool to tune, and the renderer stays a thin shell.
Discuss the blog
The blog is sold and operated as the consumer half of the pr pipeline. Talk to us about a deployed blog on your domain, migrating off a legacy CMS, or wiring the producer side into an existing editorial workflow.
Direct line
Consultation requests stay owned. We reply from e11 after reviewing fit and timing.