Run Speed Doctor yourself
Speed Doctor is a pnpm monorepo with a Next.js front-end, a NestJS API, and a background worker that drives Playwright, Lighthouse and AI. This guide takes you from clone to a working local install, and on to deployment.
What's inside
The project is split into three runnable apps and a set of focused packages:
apps/web— Next.js 15 front-end (this site).apps/api— NestJS + Fastify REST API that accepts audit requests and streams progress.apps/worker— NestJS worker that runs the scan pipeline off a BullMQ queue.packages/*— scanner (Playwright), lighthouse-engine, dom-analyzer, root-cause-engine, priority-engine, ai-engine, db (Drizzle), queue (BullMQ) and shared-types.
Prerequisites
- Node.js 20+ and pnpm 11+ (
corepack enablewill provision pnpm). - A PostgreSQL database — a free Neon project works out of the box.
- A Redis instance — Upstash (cloud) or the bundled Docker Compose service for local dev.
- Chrome/Chromium — installed automatically by Playwright; needed by the worker.
- Optional: an OpenRouter API key for AI explanations (without it, built-in templates are used).
Quick start
- 1
Clone and install.
terminalgit clone https://github.com/dev-tanvu/Speed-Doctor.git cd Speed-Doctor pnpm install - 2
Create your environment file from the template and fill in the values.
terminalcp .env.example .env # Windows: copy .env.example .envSee Environment variables below for what each one means.
- 3
Start Redis (skip if you use Upstash and set
REDIS_URLaccordingly).terminaldocker compose up -d redis - 4
Create the database schema against your Postgres database.
terminalpnpm --filter @speed-doctor/db exec drizzle-kit push - 5
Run everything (web, API and worker together).
terminalpnpm devThe app is now at
http://localhost:3000, the API athttp://localhost:3001.
Environment variables
All variables live in a single root .env. The web app only needs NEXT_PUBLIC_API_URL; the rest are read by the API and worker.
| Variable | Required | Purpose |
|---|---|---|
| DATABASE_URL | Yes | Postgres connection string (append ?sslmode=require for Neon). |
| REDIS_URL | Yes | Redis connection (use rediss:// for Upstash TLS). |
| OPENROUTER_API_KEY | No | Enables AI explanations; falls back to templates if unset. |
| OPENROUTER_MODEL | No | Override the model (default openai/gpt-4o-mini). |
| WORKER_CONCURRENCY | No | How many audits the worker runs at once (default 1). |
| ALLOWED_ORIGINS | No | Comma-separated CORS allowlist (default http://localhost:3000). |
| RATE_LIMIT_MAX | No | Max audit requests per IP per minute (default 10). |
| NEXT_PUBLIC_API_URL | Yes | Public URL of the API the browser calls. |
.env, which is git-ignored. Commit changes to .env.example (placeholders) instead. See the security policy.Using the app
- 1
Open
http://localhost:3000and enter a website URL. - 2
Pick a test environment: Both (mobile + desktop), Mobile, or Desktop.
- 3
Watch the live progress as the page is scanned, measured, analysed and diagnosed.
- 4
Read the report: category scores, Core Web Vitals, the heaviest assets, and each issue with a plain-English and a developer view. In “Both” mode, switch devices with the tabs at the top.
How an audit works
Submitting a URL creates an audit run and enqueues a job. The worker then:
- Scans the page with Playwright (real Chromium), capturing HTML, assets and timings.
- Measures Core Web Vitals with Lighthouse, in an isolated child process.
- Analyses the DOM with six detectors (images, fonts, JS, video, third-party, DOM size).
- Correlates findings to the metrics they hurt and ranks root causes.
- Explains each issue via AI (or templates) and saves the report.
For Both mode the front-end fires two runs (one per device) and shows a combined progress view, then device tabs.
Deployment
Front-end → Vercel. Import the repo, set the project root to apps/web, add NEXT_PUBLIC_API_URL pointing at your deployed API, and Vercel builds and hosts it automatically on every push.
apps/api and apps/worker there, then point the Vercel front-end at the API URL via ALLOWED_ORIGINS and NEXT_PUBLIC_API_URL.- apps/api —
pnpm --filter @speed-doctor/api buildthenpnpm --filter @speed-doctor/api start. - apps/worker —
pnpm --filter @speed-doctor/worker buildthenpnpm --filter @speed-doctor/worker start.
Troubleshooting
- “performance mark has not been set” — Lighthouse runs in a child process to prevent this; make sure the worker was restarted after pulling changes.
- Next dev shows missing-chunk / ENOENT errors — only run one
next devat a time; the dev script uses Turbopack to avoid cache corruption. - CORS errors in the browser — set
ALLOWED_ORIGINSon the API to include your web origin. - Scores look different from PageSpeed Insights — that's expected; see Why the gap?
Next steps
Want to help improve Speed Doctor? Read the contribution guide or open an issue on GitHub.