Nightly Lighthouse (PageSpeed Insights) and agent-readiness monitoring for a watchlist of priority Webflow.com pages. For each page it tracks per-strategy (mobile + desktop) scores over time, classifies status, surfaces recommendations, lets you triage them into tasks, log change markers, and posts drop alerts and 2/7/30-day follow-up comparisons to Slack.
Built with Next.js (App Router) + React. TypeScript throughout.
npm install
npm run dev # http://localhost:3000 (the bundled launch config uses 3100)Other scripts:
npm run build # production build
npm start # serve the production build
npm run lint # eslint
npm test # vitest (unit + integration-focused concurrency tests)
npm run collector:check # type-check the durable Workflow worker
npm run collector:dry-run # bundle/validate the Workflow without deployingAll are optional for local development — the app runs without them.
| Variable | Purpose |
|---|---|
PAGESPEED_API_KEY |
PSI credential. Configure it on the collector Worker; it is also used by the local runner. |
CRON_SECRET |
Shared bearer secret for nightly requests, Workflow dispatch, and collector callbacks. |
COLLECTOR_URL |
Production Workflow endpoint, ending in /jobs. |
COLLECTOR_CALLBACK_URL |
Optional direct app Worker override. By default callbacks use Webflow's automatic ASSETS_PREFIX. |
COLLECTOR_SECRET |
Optional separate dispatch credential; defaults to CRON_SECRET. |
DATASET_MODE |
demo uses the existing sample namespace; live uses an isolated, initially empty namespace. |
BASE_URL |
Webflow Cloud mount path (for example /page-watch); client routes and APIs are prefixed automatically. |
SLACK_WEBHOOK_URL |
Incoming webhook for drop alerts and follow-up reports. |
PSI_MOCK |
Local-only deterministic scores instead of PSI. |
PSI_RUNS |
Samples per strategy (1–5, default 5). |
ANTHROPIC_API_KEY |
Enables post-commit recommendation explanations and Watcher narratives on the app. |
ANTHROPIC_MOCK |
Uses deterministic placeholder AI text without making Anthropic requests. |
Put these in .env.local.
- Collection — baseline, on-demand, and nightly actions first reserve a
durable D1 job and return
202. In production a Cloudflare Workflow performs up to five PSI samples per strategy, uploads the raw JSON to R2, scans agent readiness, and calls the app back with a versioned result. Retries are durable, duplicates coalesce, stale jobs become visible failures, and a run ID can append history only once. - Baselines — ordinary on-demand/nightly runs may store snapshots, history, recommendations, and scan results, but a page stays Pending until the user explicitly captures a baseline. Zero placeholders are never treated or shown as real baselines.
- Nightly job — wire a scheduled job to
POST /api/cron/nightly. It priority-sorts the watchlist and dispatches Workflows; it does not hold a Webflow request open while PSI runs.CRON_SECRETis mandatory. - Storage — a tenant-scoped
DataStore(seesrc/lib/store) mirrors the state snapshot, append-only history/markers, and raw report tiers. Local development uses the filesystem under.data/, with an in-memory fallback for read-only hosts and per-tenant serialized atomic replacement. A deployed OpenNext worker withDBandREPORTSbindings automatically uses D1 plus R2; D1 state updates use version-guarded compare-and-swap retries. - State mutations go through targeted server-side domain endpoints
(
/api/pages,/api/recs,/api/pages/[id]/*) and the store-level atomic update primitive. External PSI/Slack work happens outside that critical section; result commits re-read authoritative state. - Background execution — Cloudflare Workflows own production execution.
The local runner uses Next.js
after()only for development. - Post-commit enrichment — after the scores/raw reports are safely stored, the Workflow separately retries Anthropic recommendation explanations, Watcher narrative refreshes, Slack alerts, and due follow-ups. Optional integrations cannot roll back or mislabel a successful collection.
The interactive app relies on the enclosing site's SSO. There is no second
HTTP Basic layer and no FDE_ACCESS_* configuration. Non-interactive nightly
and collector callback endpoints remain protected by CRON_SECRET.
- Confirm the app has the
DBandREPORTSdeclarations fromwrangler.json. Webflow Cloud applies0003_collection_jobs.sqlduring the GitHub-driven deployment. Production intentionally fails instead of falling back to process memory when either binding is absent. - Deploy
collector-worker/wrangler.jsonc. Configure itsPAGESPEED_API_KEYandCRON_SECRETsecrets. - Set
COLLECTOR_URL,CRON_SECRET, andDATASET_MODEon the Webflow app.BASE_URLandASSETS_PREFIXare supplied automatically by Webflow Cloud; the latter is the direct Worker origin used for SSO-independent callbacks. - Call
/api/healthafter deployment. It returns503if durable storage or the production collector is missing, without exposing secret values.
Use DATASET_MODE=demo for the existing sample state and DATASET_MODE=live
for real URLs. The two modes use separate tenant keys in the same D1 database,
so switching modes is reversible and never overwrites the demo dataset.
- Per-user identity and role-based authorization
- Automated page remediation
The four fixed scoring/scheduling choices for this phase are documented in DECISIONS.md.