Live URL: ppc.io/buyer-journey-simulator
Shared results: ppc.io/buyer-journey-simulator/r/[id] (noindex)
Free tool that takes any website URL, scrapes it, extracts business context + buyer personas via Claude, then simulates each persona’s complete purchase journey in first person. Outputs gap analysis, competitor switch detection, and a full Google Ads playbook with per-persona media plans.
prerender = false) + React 19 (client:only="react")@astrojs/cloudflare adapter@anthropic-ai/sdk (server-side)SESSION namespace) for shareable results (30-day TTL)src/pages/api/journey/)| File | Method | Purpose |
|---|---|---|
scrape.ts | POST | Takes URL, scrapes homepage + 4 subpages via Firecrawl, returns markdown |
extract.ts | POST | Takes scraped content, Claude extracts business context + personas |
simulate.ts | POST | Takes business + personas, Claude streams journey simulation via SSE |
save.ts | POST | Saves results to KV, returns short ID for sharing |
load.ts | GET | Loads saved results by ID |
src/components/JourneySimulator/)| File | Purpose |
|---|---|
JourneyApp.tsx | Root state machine - orchestrates all screens + API calls |
InputScreen.tsx | URL input with 3 feature bullets (matches swipe-wall pattern) |
LoadingScreen.tsx | Progress steps + orbital animation, two phases (scrape/simulate) |
ContextScreen.tsx | Business intel review - positioning, competitors, trust signals, editable fields, extra context box |
JourneyScreen.tsx | Timeline with color-coded stages, search terms, channel badges, ad hints per stage, sticky persona sidebar |
ResultsScreen.tsx | 3-tab dashboard: Persona Debrief, Google Ads Playbook (campaigns, keywords, ad copy, media plans, budget), Cross-Persona Summary |
UsageGateModal.tsx | 3-run limit modal with waitlist CTA |
types.ts | All TypeScript interfaces |
useUsageGate.ts | localStorage-based usage tracking (3 free runs) |
| File | Purpose |
|---|---|
buyer-journey-simulator.astro | Main page - CSS vars, atmosphere orbs, noise texture, scroll reveal |
buyer-journey-simulator/r/[id].astro | Shared results page (noindex, nofollow) |
Both are set in wrangler.jsonc as vars (private repo):
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY | Claude API (rate-limited key) |
FIRECRAWL_API_KEY | Firecrawl scraping API |
cd ppc-io-frontend
npm run deploy # builds + wrangler deploy
Requires wrangler login first (OAuth). Deploy is manual, not auto-triggered by git push.
prerender = false - This page MUST be server-rendered (API routes need server-side env vars). Cannot be static.
not_found_handling: "none" in wrangler.jsonc - Changed from "single-page-application" because the SPA fallback was serving index.html (homepage) for SSR routes like this one. This was the deployment blocker that took multiple attempts to diagnose.
client:only="react" - React components hydrate client-side only. An earlier approach using manual createRoot in a <script> tag failed because Astro processes scripts differently.
Cache-Control: no-store - Set in middleware.ts for /buyer-journey-simulator* routes to prevent Cloudflare CDN caching SSR responses.
CSP connect-src - API calls to Firecrawl and Anthropic happen server-side (Worker), so no CSP changes needed for those domains.
Usage gate - localStorage-based, trivially bypassable. Just a soft gate for the free tool launch.
Streaming - The simulate endpoint uses SSE (Server-Sent Events) to stream Claude’s response. The client accumulates text chunks and parses the complete JSON when done. This is NOT progressive rendering - it just prevents timeout on long responses.
Matches the swipe-wall design system:
#0a0b10#12141c#a78bfa (primary accent)#34d399 (hits/success)#fbbf24 (warnings/competitors)#fb7185 (gaps/issues)