Operator guide
Run, test & deploy BrandRadar AI
A plain-language guide to running the app locally, testing it, and the checklist between here and a live launch. Written for the operator and any developer picking it up.
1 · What BrandRadar is
BrandRadar AI checks whether ChatGPT, Perplexity, Gemini, and Google AI Overviews recommend a local business, scores that visibility 0–100, and generates a prioritized playbook of fixes — then re-scans to prove the fixes worked. The first scan is free, with no signup.
2 · The shape of the system
The app is multi-process, and that’s load-bearing: the queue worker runs as a separate long-running process, never inside serverless Next.js. Web and worker share the packages and one Redis.
apps/web Next.js 15 — UI + route handlers (SSE / Stripe / embed)
apps/worker BullMQ workers — scan engine, enrichment, playbook (ALWAYS-ON)
packages/core pure logic: engines, scoring, site audit, action layer
packages/db Drizzle + Postgres schema, tenancy, migrations
packages/queue BullMQ queues + the SSE event bus
packages/cache Redis (rate limits, scan progress, pub/sub)3 · Run it locally (Docker)
The stack runs via Docker Compose — Postgres, Redis, web, worker, Adminer, plus a one-shot migrate container that runs migrations and seeds the admin, then exits.
# from the repo root
PG_PORT=5440 REDIS_PORT=6390 docker-compose up -d- App: http://localhost:3100
- Database UI (Adminer): http://localhost:8090
- Admin login: the seeded operator account (credentials are in the repo’s
docs/OPERATOR-GUIDE.md, kept off this public page).
On this machine use the standalone docker-compose (v2.x) — the docker compose plugin subcommand isn’t wired here.
# rebuild the worker after changing apps/worker or packages/core|db|queue
PG_PORT=5440 REDIS_PORT=6390 docker-compose up -d --build worker
# re-run migrations + seed only
PG_PORT=5440 REDIS_PORT=6390 docker-compose run --rm --build migrateDev gotcha: the first hit to any route compiles in dev (2–20s cold), then it’s fast — production (next build) prefetches and is instant. Don’t mistake cold-compile lag for a bug; it’s the #1 cause of “flaky” test timeouts.
4 · Testing — what the types mean
| Type | Checks | Speed | App running? |
|---|---|---|---|
| Unit | one function in isolation (e.g. the score math) | fast | no |
| E2E | a whole journey through the real app in a real browser | slow | yes |
# typecheck (run before every commit)
pnpm --filter @brandradar/web typecheck
# unit tests
pnpm --filter @brandradar/web test5 · E2E (end-to-end) tests — the full how-to
E2E = end-to-end. Playwright drives a real Chrome browser through whole flows — clicking, typing, navigating — exactly like a user. The core spec, magic-moment.spec.ts, walks the front door → reveal → score → the fix CTA → playbook.
Step 1 — bring the stack up (§3) and confirm the app loads.
Step 2 — enable stub mode so scans finish in seconds
Add this line to .env, then recreate the app containers:
BR_FORCE_STUB=1PG_PORT=5440 REDIS_PORT=6390 docker-compose up -d --force-recreate web workerStep 3 — install the browser once, then run the tests
cd apps/web
pnpm exec playwright install chromium
E2E_BASE_URL=http://localhost:3100 pnpm exec playwright test \
e2e/magic-moment.spec.ts e2e/menu-nav.spec.ts --reporter=lineThe Playwright config defaults to port 3000; this stack is on 3100, so the E2E_BASE_URL=http://localhost:3100 prefix is required.
Reading the result
4 passed→ the flows work.- A failure prints the file:line and saves a video at
apps/web/test-results/…/video.webm— open it to watch where it got stuck. - Add
--headed(a real Chrome window) or--uito watch it live.
Known flakiness (not a bug): against the dev server the first test of a cold run can time out while the route compiles — re-run and it passes. For a clean signal, run the guards in isolation or against a next build app.
⚠️ After testing, remove BR_FORCE_STUB=1 from .env — it fakes the AI engines and must never be on in production.
6 · Pre-deploy checklist
Topology: web (Vercel or Fly) + always-on worker (Fly — separate process, never serverless) + Neon Postgres + Upstash/Fly Redis + Cloudflare R2.
Blockers — must do
- Commit the work + open a PR; keep
maingreen. - Set
APP_URLto the real domain (canonical, sitemap, robots, llms.txt, and social share-image URLs read it). Localhost here = uncrawlable sitemap + broken previews. - Set
BETTER_AUTH_URLto the real domain (magic-link / OAuth callbacks). - Make the reverse proxy forward the real client IP (nginx
proxy_set_header X-Real-IP $remote_addr;) or setBR_TRUSTED_IP_HEADER— otherwise the per-IP free-scan cap won’t bite. - Add real LLM API keys (OpenAI / Anthropic / Gemini / Perplexity).
- Ensure
BR_FORCE_STUBis off in production. - Run database migrations against the production database.
- Set secrets:
SUPERADMIN_EMAIL,LTD_SIGNING_SECRET, auth secret, R2 credentials.
Confirm the intended-state switches
BR_ENABLE_SCHEDULERS— off by default (cost guard); on only when wanted.BR_STRIPE_ENABLED— card billing is locked for launch (AppSumo LTD is the live path).BR_DEEP_SCANS_ENABLED— premium deep-scan models; off unless intended.
Should do before launch
- Run e2e against a
next buildapp with real keys. - Smoke-test the magic moment end-to-end with real keys.
Fuller detail (including the seeded admin credentials and the polish-session changelog) lives in docs/OPERATOR-GUIDE.md in the repository.