Client-acquisition website for Phil Greene's local business growth services. The primary offer is a Local Lead Recovery System that combines conversion-focused pages, lead capture, follow-up, CRM organization, review requests, and reporting.
- Vite 5, React 18, and TypeScript
- Wouter client-side routing
- Tailwind CSS 3 plus project-specific CSS design tokens
- Express for local development and self-hosted production serving
- Vercel static deployment plus TypeScript serverless functions in
api/ - Mailtrap for audit-request email delivery
- Cloudflare Turnstile for website-scanner abuse protection
- Optional Plausible analytics
/focused Lead Recovery System landing page/projectsselected technical work organized by capability/website-checkupautomated local-service website lead checkup/contactlead-loss audit form/privacy-policy,/terms,/cookie-policy,/refund-policy,/disclaimer,/accessibility
Legacy URL aliases are preserved in the client router and Express server.
npm ci
npm run devThe Express development server listens on PORT, defaulting to 5000.
npm run lint
npm run check
npm test
npm run test:e2e
npm run buildThe production client output is written to dist/public. The Express bundle is written to dist/index.js.
Copy .env.example to the environment used by the deployment and configure:
MAILTRAP_API_KEY: required for production email deliveryMAILTRAP_FROM_EMAIL: required and must use a verified Mailtrap sending domainVITE_TURNSTILE_SITE_KEY: public key for a managed Turnstile widget allowingphilgreene.netandwww.philgreene.netTURNSTILE_SECRET_KEY: matching private Turnstile secret, used only by server functionsSCAN_REPORT_SECRET: random value of at least 32 characters used to encrypt 30-minute report tokensVITE_PLAUSIBLE_DOMAIN: optional; when present, the client loads Plausible for this domain
Audit requests are sent to me@philgreene.net. The form includes browser validation, server-side Zod validation, escaped email output, a honeypot field, explicit success and error states, and a direct email fallback. Missing Mailtrap configuration returns an error rather than a false success.
The website checkup fetches one public homepage with an 8-second timeout and 2 MB response limit. It blocks credentials, IP literals, private/reserved addresses, unusual ports, unsafe DNS results, and unsafe redirects. The free preview is returned immediately; the complete report is held in a short-lived encrypted token rather than stored in a database. Client-rendered application shells are labeled as limited scans and are not given a misleading conversion score.
When Plausible is configured, the site emits:
hero_cta_clickedaudit_form_startedaudit_form_submittedpilot_cta_clickedemail_link_clickedservices_viewedworkflow_viewedscanner_openedscan_started,scan_succeeded,scan_failedreport_gate_started,report_requested,full_report_viewedscanner_audit_clicked,no_website_selected
phone_link_clicked is defined in the typed event catalog but is not emitted because no public business phone number is configured. Add it only when a verified phone number is available.
vercel.json keeps the existing Vercel shape:
- build command:
npm run build - static output:
dist/public - API functions:
/api/contact,/api/scan, and/api/scan-report - SPA fallback: all non-API routes rewrite to
/index.html
For self-hosting, run npm run build followed by npm start and provide the same server-side variables. Local development automatically uses Cloudflare's official passing test keys when production Turnstile keys are absent, but production never falls back to test credentials.
The coordinated concept images used for implementation and visual QA live in docs/design/.