A review of what we're actually building in the ranker tool: how it scores, what's live versus theatrical, where the bugs and security exposure sit, and the gap to a product we can stand behind.
Restaurant Ranker is a public, no-signup tool that takes a single restaurant URL and runs a ~52-second cinematic sequence — terminal crawl, radar scan, review collage, competitor map, neural-net animation, AI-engine probes — ending in two headline scores (SEO Health and GEO Readiness), an estimated $/month lost figure, a 5-axis radar, three copy-paste quick wins, and a long detailed report. It is a sales funnel for Primi Digital, not a precision analytics product — and that framing is the right one to judge it by.
The live path runs in the Cloudflare worker (computeScores). The fallback path ships a hardcoded sample. The detailed report below the fold is static regardless of which path runs.
| Check | Pass / warn / fail logic |
|---|---|
| HTTPS | https → ok, else bad |
| Server-rendered HTML | body word-count > 200 → ok, else warn |
| Meta title | missing → bad; >65 or <25 chars → warn; else ok |
| Meta description | missing → bad; <60 or >200 chars → warn; else ok |
| Canonical / Viewport / Robots | present-and-sane → ok |
| Open Graph | all 3 (title+desc+image) → ok; any → warn; none → bad |
| H1 | exactly 1 → ok; >1 → warn; 0 → bad |
| Alt-text coverage | >0.7 → ok; >0.3 → warn; else bad |
| Internal links | ≥5 → ok; ≥1 → warn; else bad |
| Schema present | Restaurant/LocalBusiness/FoodEstablishment → ok; any JSON-LD → warn; none → bad |
Restaurant schema +10, AggregateRating +8, Menu +4, OpeningHours +4, server-rendered +4, meta description +3, canonical +2 (clamped 0–100). The base of 50 means a site with literally nothing still scores 50/100 — defensible as a demo, not as a real metric.
No Restaurant schema +$800, no AggregateRating +$250, no meta description +$300, incomplete OG +$150, low alt coverage +$150, h1≠1 +$100, no canonical +$100, not server-rendered +$200. A heuristic with no documented basis — fine for a hook, but we should never present it as a measured figure.
GET /api/restaurant-grader/audit?url=…. It fetches the target server-side (12s timeout, 600 KB cap), regex-parses the <head>/body for title, meta, canonical, OG, H1/H2, image-alt, JSON-LD and links, and returns structured JSON.run() races a live fetch against a 6s timeout; on success it swaps live SEO/GEO/$ into the header and verdict; on failure it silently keeps the Boqueria sample and shows a "LIVE FETCH FAILED" badge.Header pills and the verdict update from live data, but the entire detailed report body stays hardcoded to Boqueria — reviews ("2,195"), competitor map (Pil Pil, Zoi…), engine snippets and the $2,050 breakdown. A live audit of another restaurant produces a confident report whose body contradicts its own live header. The radar chart even mixes live and sample axes.
copyShareUrl() copies a hardcoded dead link (restaurant-grader.demo/audit/boqueria-ues-x7k2) for every user — there's no share/persistence backend, yet the CTA promises "Share it, keep it."
Real AI-engine mention probes — the core promise — don't exist. This needs API keys + budget (Places/Yelp + LLM calls), stored as Worker env secrets. The architecture is correctly set up for that; the feature simply isn't wired.
The worker fetches an arbitrary user URL server-side and validates only the protocol — no block on localhost/private/link-local hosts (low severity on Workers, but a classic SSRF shape and an open scraper others can abuse). No rate limiting. Regex HTML parsing is fragile on reordered/multiline attributes and counts any href containing the host as "internal".
No client-side secrets — a full search returned only a CSS mask-image false positive. The proxy uses no API keys, content-type gating and a 600 KB body cap. This directly honours our standing rule: never embed tokens in client HTML. The .wrangler/ account cache is untracked — keep it gitignored so it never lands in history.
Two H1s once the report renders; no canonical and no SoftwareApplication/Organization schema on the tool's own page (ironic for a GEO tool); prefers-reduced-motion is computed but unused (52s of animation with only a Skip button); dead constants (hardcoded radar legend, stale report date).
env secrets, never client-side), add SSRF + rate-limit guards, and give the tool page its own SoftwareApplication schema and canonical. Sequenced this way, Restaurant Ranker goes from a convincing demo to something we can put a client's name into without caveats.
copyShareUrl(); reconcile the "real probes" hero claim.env secrets).