ReeWeb Publisher Signal
The publisher signal is a Reepolee middleware that automatically pings a ReeWeb Publisher instance after every successful content mutation. When an editor saves a record in the Reepolee admin panel — creates, updates, or deletes — the middleware fires a fire-and-forget POST to the Publisher's signal endpoint, which triggers a static-site rebuild. No cron job, no manual deploy, no polling.
The result is a hands-off content pipeline: Reepolee holds the data → editors make changes → Reepolee signals the Publisher → the Publisher re-renders the static site → you review and deploy.
How It Works
Two pieces in lib/publisher_signal/index.ts:
send_publisher_signal()— sends a singlePOSTto the Publisher's signal endpoint.publisher_signal_mw()— a middleware wrapper that callssend_publisher_signal()after every successful mutation.
send_publisher_signal()
async function send_publisher_signal(
publisher_url?: string,
fetcher?: PublisherFetch,
): Promise<void>;
Checks REEWEB_PUBLISHER_ENABLED (the explicit opt-in switch) and REEWEB_PUBLISHER_URL, sends a POST with a 1-second timeout, and catches all errors silently (logs a warning). If either is unset, it returns immediately — no signal is sent. The URL alone is not enough: a developer keeps a URL configured while the Publisher is not running, so REEWEB_PUBLISHER_ENABLED=true is the explicit opt-in.
| Behaviour | Detail |
|---|---|
| Env vars | REEWEB_PUBLISHER_ENABLED + REEWEB_PUBLISHER_URL |
| Method | POST |
| Timeout | 1 second |
| On failure | Logs [publisher] Render signal failed: <message> |
| On success | Silent — the Publisher acknowledged the signal |
| When disabled / unset | Silent skip — no signal, no warning |
The signal is fire-and-forget. If the Publisher is down, restarting, or unreachable, the user's mutation completes normally and Reepolee logs a warning. The user's request is never delayed by the signal.
publisher_signal_mw()
function publisher_signal_mw(
send_signal?: () => Promise<void>,
): Middleware;
Wraps any route handler. After the handler returns a response, the middleware checks two conditions:
- Was the request a mutation? Only
POST,PUT,PATCH, andDELETErequests qualify.GETrequests never trigger a signal. - Did the mutation succeed? Only responses with status
201(Created),204(No Content), or3xx(Redirect) qualify. A200with validation errors, a400, or a500do not trigger a signal.
Only when both conditions are met does the middleware call send_signal(). Failed validations, rejected logins, and server errors never trigger a rebuild.
Configuration
Set two environment variables in Reepolee's .env:
REEWEB_PUBLISHER_ENABLED=true
REEWEB_PUBLISHER_URL=http://localhost:3011/api/render-signal
| Variable | Example | Purpose |
|---|---|---|
REEWEB_PUBLISHER_ENABLED | true | Explicit opt-in - the URL alone does not enable signalling |
REEWEB_PUBLISHER_URL | http://localhost:3011/api/render-signal | The Publisher's signal endpoint |
When the switch is off or the URL is unset, the signal is disabled entirely — no warnings, no errors, the middleware still runs but skips the signal call. This means you can keep the URL configured while the Publisher is stopped, and flip the switch on when it's running.
Wiring Into Routes
Add publisher_signal_mw() to the middleware chain of any route whose mutations should trigger a rebuild. The most common pattern is to wrap entire CRUD modules:
// apps/main/routes.ts
import { publisher_signal_mw } from "$lib/publisher_signal";
import { my_table_crud } from "./my_table/index";
export default [
...mount_prefix("/my-table",
// ...my_table_crud routes,
publisher_signal_mw(), // fires after every create/update/delete
),
];
The middleware composes with existing guards — put it after auth so unauthenticated requests don't trigger a signal:
...mount_prefix("/admin",
require_module_mw("admin"), // auth first
publisher_signal_mw(), // signal only after authenticated mutations
);
For hand-written routes, wrap individual handlers:
import { publisher_signal_mw } from "$lib/publisher_signal";
const signal = publisher_signal_mw();
export async function post(req: Request): Promise<Response> {
return signal(req, async () => {
// ... your mutation logic
return Response.redirect("/success", 303);
});
}
Because the middleware only fires after 201, 204, or 3xx responses, a hand-written handler that returns 200 with JSON will NOT trigger a signal — return a redirect or a 201/204 to opt into signalling.
End-to-End Flow
Reepolee Admin ReeWeb Publisher Cloudflare
────────────── ──────────────── ──────────
1. Editor saves record
2. POST /my-table returns 201 ──→ POST /api/render-signal (1s timeout)
3. Debounce (collapses bursts)
4. git pull (fetch fresh data)
5. bun ssg (render static site)
6. Diff candidate vs deployed
7. Preview available at :3012
8. [Manual] Click Deploy ──────→ wrangler deploy → live
Steps 3-7 happen automatically once Reepolee sends the signal. Step 8 (deploy) is manual by default — the Publisher shows a Deploy button and a diff of what changed. You can wire deploy into an automated flow if you prefer fully hands-off publishing.
Relationship to Server Notify
Reepolee has a separate internal notification system in lib/server_notify.ts for in-process reloads:
| System | File | Purpose | Target |
|---|---|---|---|
| Publisher signal | lib/publisher_signal/ | Trigger a ReeWeb static-site rebuild | External Publisher (HTTP) |
| Server notify | lib/server_notify.ts | Reload Reepolee's own in-memory translations or trigger a server restart | Self (local process) |
Server notify is used by the generator (reeman) to reload translations or restart after CRUD scaffolding. Publisher signal is used by route middleware to trigger an external ReeWeb rebuild. They serve different purposes and different targets — you may have both active simultaneously.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No rebuilds after saves | REEWEB_PUBLISHER_ENABLED off or REEWEB_PUBLISHER_URL not set | Set REEWEB_PUBLISHER_ENABLED=true and point REEWEB_PUBLISHER_URL at the Publisher's signal endpoint |
| "Render signal failed" in logs | Publisher not running or wrong URL | Verify bun publisher is running and the URL matches PUBLISHER_PORT |
| Signal sent but no rebuild | Publisher branch mismatch | Ensure the Publisher is watching the correct PUBLISHER_BRANCH |
| GET requests triggering signals | Middleware order problem | Ensure publisher_signal_mw() only wraps mutation routes, not list routes |
| Signal hangs the request | Network issue or slow Publisher | The 1-second timeout prevents this — if you see slow mutations, check network latency to the Publisher |
See Also
- ReeWeb Publisher — the Publisher dashboard, its API, and how it processes render signals
- ReeWeb & Reepolee — the broader data-source model: fetch-loaders, dynamic asset sync, and how a ReeWeb site pairs with a Reepolee backend