Publisher
The Publisher is a small dashboard server that turns "content changed" into "site
re-rendered and deployed" without you running bun ssg and wrangler deploy by
hand. It watches the git branch you publish from, re-renders when a commit lands
(or when something signals it), shows you exactly which generated files changed,
lets you preview the candidate build, and deploys on a button press.
It exists for the Reepolee-as-data-source setup: a Reepolee app holds the content, and every time an editor saves a record Reepolee pings the Publisher, which re-renders the static site from the fresh data. It works just as well standalone - driven by git commits or a manual button - when there is no Reepolee in the picture.
The Publisher is a development/operations tool you run on a machine that can deploy. It is not part of the served static site.
Running it
bun publisher
That starts the dashboard on PUBLISHER_PORT. Open http://localhost:<PUBLISHER_PORT>/
to see status, the git state, the pending file changes, a candidate-preview link,
and a Deploy button.
To force a render against an already-running Publisher (for example from a script):
bun publisher --render
Configuration
The Publisher reads four ports/branch values from .env; all three ports must be
distinct from each other:
| Variable | Example | Role |
|---|---|---|
PUBLISHER_PORT | 3011 | Port the dashboard and its API listen on |
PUBLISHER_PREVIEW_PORT | 3012 | Port the candidate-build preview (Wrangler) is served on |
PUBLISHER_BRANCH | main | Only clean commits on this branch are eligible for rendering |
PORT | 3000 | The dev server's port - used to trigger a live reload after work |
PUBLISHER_BRANCH is the safety gate: the Publisher refuses to render a release
unless the working tree is clean and HEAD is on that branch, so a
half-committed experiment never becomes a candidate build.
How it works
- Watch. The runner polls git every couple of seconds. When the clean
PUBLISHER_BRANCHhead moves - or a render is requested - it schedules a render after a short quiet period (so a burst of commits collapses into one build). - Render. It runs
bun run ssgintodist/, then diffs the result against the last deployed snapshot (kept in.deployed/). The dashboard lists exactly which generated files were added, changed, or removed - the candidate, not a guess. - Preview. A candidate preview is served on
PUBLISHER_PREVIEW_PORT(via Wrangler, using.dev.vars.release-preview) so you can click through the built site before it goes live. - Deploy. The Deploy button runs
bun run cf:deploy(see Cloudflare Workers) and, on success, updates the.deployed/snapshot so the next diff is measured from what is actually live. Deploy is only enabled when a render isreadyand there are changes to ship.
Rendering never touches dist/ destructively on failure: a failed build restores
the previous dist/ so a broken commit can't leave you with a half-generated
site.
Dashboard API
The dashboard is backed by a tiny HTTP API, which is also what a paired Reepolee app talks to:
| Method + path | Purpose |
|---|---|
GET / | The HTML dashboard |
GET /api/health | Liveness + current status |
GET /api/state | Full runner view (status, branch, head, changes, output) as JSON |
POST /api/render-signal | Request a render (debounced); returns 202. This is the signal endpoint. |
POST /api/render | Force a render now (dashboard button) |
POST /api/deploy | Run cf:deploy and snapshot the deployment |
Wiring a Reepolee app to signal it
A Reepolee app can auto-trigger a re-render whenever content changes. Point
Reepolee at the Publisher's signal endpoint in the Reepolee .env:
REEWEB_PUBLISHER_URL=http://localhost:3011/api/render-signal
With that set, Reepolee's per-route publisher-signal middleware fires a
best-effort POST to the endpoint after every successful mutation
(a create/update/delete that returns 201, 204, or a redirect). The signal is
fire-and-forget with a 1-second timeout: if the Publisher is down, Reepolee logs
a warning and the user's request is unaffected. Leave REEWEB_PUBLISHER_URL
unset to disable signalling entirely.
The result is a hands-off loop: an editor saves a record in the Reepolee admin → Reepolee signals the Publisher → the Publisher re-renders the static site from the updated data → you review the candidate and deploy (or wire deploy into the same flow). See ReeWeb & Reepolee for the data side of that pairing.