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:

  1. send_publisher_signal() — sends a single POST to the Publisher's signal endpoint.
  2. publisher_signal_mw() — a middleware wrapper that calls send_publisher_signal() after every successful mutation.

send_publisher_signal()

async function send_publisher_signal(
  publisher_url?: string,
  fetcher?: PublisherFetch,
): Promise<void>;

Reads REEWEB_PUBLISHER_URL from the environment, sends a POST with a 1-second timeout, and catches all errors silently (logs a warning). If the env var is unset, it returns immediately — no signal is sent.

BehaviourDetail
Env varREEWEB_PUBLISHER_URL
MethodPOST
Timeout1 second
On failureLogs [publisher] Render signal failed: <message>
On successSilent — the Publisher acknowledged the signal
When URL is unsetSilent 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:

  1. Was the request a mutation? Only POST, PUT, PATCH, and DELETE requests qualify. GET requests never trigger a signal.
  2. Did the mutation succeed? Only responses with status 201 (Created), 204 (No Content), or 3xx (Redirect) qualify. A 200 with validation errors, a 400, or a 500 do 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 a single environment variable in Reepolee's .env:

REEWEB_PUBLISHER_URL=http://localhost:3011/api/render-signal
VariableExamplePurpose
REEWEB_PUBLISHER_URLhttp://localhost:3011/api/render-signalThe Publisher's signal endpoint

When empty or unset, the signal is disabled entirely — no warnings, no errors, the middleware still runs but skips the signal call. This means you can ship the middleware in every route and control signalling purely through the env var.

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:

// routes/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:

SystemFilePurposeTarget
Publisher signallib/publisher_signal/Trigger a ReeWeb static-site rebuildExternal Publisher (HTTP)
Server notifylib/server_notify.tsReload Reepolee's own in-memory translations or trigger a server restartSelf (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

SymptomLikely causeFix
No rebuilds after savesREEWEB_PUBLISHER_URL not setSet it in .env to the Publisher's signal endpoint
"Render signal failed" in logsPublisher not running or wrong URLVerify bun publisher is running and the URL matches PUBLISHER_PORT
Signal sent but no rebuildPublisher branch mismatchEnsure the Publisher is watching the correct PUBLISHER_BRANCH
GET requests triggering signalsMiddleware order problemEnsure publisher_signal_mw() only wraps mutation routes, not list routes
Signal hangs the requestNetwork issue or slow PublisherThe 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