vercel-doctor — cost & performance pre-deploy gate for Vercel/Next.js
Runs on a project that already exists and targets Vercel. It wraps the third-party vercel-doctor CLI — which scans a Next.js codebase for patterns that cost money on Vercel — and turns its report into applied fixes, routing each finding to the skill that owns it.
Third-party tool. vercel-doctor is not an official Vercel product — it's an independent
open-source project by Aniket-508, published to npm as
vercel-doctor (bin vercel-doctor, MIT). Verified against the npm registry + repo README
(latest at time of writing: 1.2.0 — pin or re-check, the flag set can change). It reads your
codebase and phones home unless --offline; treat its AI-fix prompts as suggestions to verify, not gospel.
Verified invocation + flags
npx -y vercel-doctor@latest . # scan the project at <path>
npx -y vercel-doctor@latest . --output markdown --report docs/vercel/doctor-report.md
npx -y vercel-doctor@latest . --ai-prompts docs/vercel/doctor-fixes.json
| Flag |
Effect |
--output <human|json|markdown> |
report format (default human) |
--report <file> |
write the report to a file |
--ai-prompts <file> |
export fix prompts as JSON (only issues with a known fix strategy) |
--score |
print only the health score |
--verbose |
list the matching files per rule |
--diff [base] |
scan only files changed vs the base branch |
--project <name> / -y, --yes |
pick workspace project(s) / scan all without prompting |
--no-lint / --no-dead-code |
skip the lint / dead-code analyses |
--offline |
skip telemetry |
-v, --version / -h, --help |
version / help |
If a flag above is rejected, the CLI has moved — run npx -y vercel-doctor@latest --help and use what
it reports rather than guessing.
⚠️ Two behaviours the --help does not tell you (found by running it)
- With uncommitted changes it silently scans only those. You don't have to pass
--diff — if the
working tree is dirty the run prints "Scanning uncommitted changes" and looks at just those files.
A dirty tree can therefore produce a confident "No issues found!" that means nothing. Run it on a
clean tree (commit or stash first) when you want a real audit, and check the line that says either
"Found N source files" (full scan) or "Scanning N changed source files" (diff mode) before you
trust the result.
--offline also disables the score. Telemetry is what computes it (--help: "anonymous, not
stored, only used to calculate score"). Use --offline for a private codebase and accept there is no
number; drop it only when the user is happy for the scan to phone home.
The six cost areas it scans
| Area |
Typical finding |
Who fixes it in dev-flow |
| Caching |
fetch/routes that opt out of the CDN; missing "use cache" / revalidate |
data-fetching (Next 16 Cache Components — "use cache", cacheLife, revalidateTag) |
| Unused code |
dead files, exports, types |
safe cleanup (delete + tsc verify) — mechanical, this skill |
| Function duration |
long-running serverless work, blocking awaits, oversized bundles |
this skill flags; heavy refactors → the owning feature skill |
| Image optimization |
unoptimized images, missing sizes, raster where SVG fits |
design-md-to-app image patterns / next/image config |
| Function invocations |
patterns triggering excessive serverless calls (per-request fan-out) |
data-fetching (lift reads to Server Components, batch) |
| Platform / config |
next.config / vercel.json / region / runtime misconfig |
this skill (config is mechanical) |
Run, then route
Preconditions. meta.json#stack.framework ∈ {"next","monorepo"} and the deploy target is Vercel (stack.deploy = "vercel" or the user says so). Refuse otherwise — the checks are Vercel-specific.
Run npx -y vercel-doctor@latest . --output markdown --report docs/vercel/doctor-report.md at the project root (or apps/web/, or --project <name> in a monorepo). Add --ai-prompts docs/vercel/doctor-fixes.json when you want the fix prompts. Capture the markdown report + health score (--score alone if you only need the number).
Triage each finding by area (table above). Verify it in the code before acting — a scan is a
signal, not a verdict. Trust the categories unequally:
| Category |
Trust |
Why |
| Caching / route policy |
high |
reading a route's headers is a fact |
Deployment + config (file count, --archive=tgz, Fluid Compute) |
high |
mechanical, verifiable |
| Static-asset size |
medium |
the threshold is low (~25 KB); judge each asset |
| Link prefetch |
read the version first — see below |
|
Dead code (Unused file / Unused export / Unused type) |
lowest — never act on it alone |
see below |
⚠️ The dead-code pass is the one that will hurt you. It runs knip, which resolves imports on its
own and does not know about framework-discovered entry points. On a real run against a Next 16 +
eve project it flagged 67 of 115 files (58%) as unused — and every one we spot-checked was wrong:
agent/agent.ts (the eve agent entry point, discovered by path, imported by nobody — deleting it
destroys the agent), db/index.ts (imported by agent/tools/* via a relative path), hooks/use-mobile.ts
(imported through the @/ alias), and buttonVariants (used by components/ui/calendar.tsx).
Open every file before deleting it, and treat anything a framework discovers by convention —
agent/**, route files, middleware/proxy, instrumentation.ts, config files — as a guaranteed
false positive. When in doubt, --no-dead-code and audit that separately.
⚠️ The Link-prefetch advice predates Next 16.3. It says "add prefetch={false}… add prefetch={true}
only to critical links", which was right when every link cost a prefetch request. Under Partial
Prefetching (16.3) Next prefetches one reusable App Shell per route, so rendering a <Link> is
effectively free and prefetch={true} means something different (deeper per-link prefetch + runtime
prefetching). On ≤16.2 the advice stands; on 16.3+ with partialPrefetching on, ignore it and follow
data-fetching §Instant Navigation instead.
Apply the safe, mechanical fixes here (dead-code deletion with tsc --noEmit green, config corrections, next/image sizes/format tweaks).
Route the judgment calls to the owning skill rather than hand-fixing: caching + invocation findings → data-fetching (it owns the Next 16 read/caching ladder); image-pattern findings → design-md-to-app. Don't re-implement their rules here.
Persist: write the report to docs/vercel/doctor-report.md, update meta.json#vercel_doctor, append history. No phase bump.
meta.json#vercel_doctor block
"vercel_doctor": {
"last_run_at": "<ISO>",
"health_score": 0, // as reported
"findings": { "high": 0, "medium": 0, "low": 0 },
"fixed": ["unused-code","image-sizes"],
"routed": { "data-fetching": ["caching","invocations"], "design-md-to-app": ["images"] }
}
dev-flow hook
Horizontal capability — run any time. dev-flow proposes it as a pre-deploy gate at feature_complete (right beside compliance-audit — legal gate + cost gate before shipping), and re-runs it in the deployed maintenance loop (catch cost regressions after changes). It records meta.json#vercel_doctor + history and never bumps phase. It never blocks the deploy on its own — it surfaces the score + findings so the user decides.
Relationship to other skills
compliance-audit — the sibling gate: legal/privacy risk (GDPR/AI-Act) vs vercel-doctor's cost/perf risk. Both are feature_complete pre-deploy gates, both no-phase-bump, both "auto-fix safe + route/flag the rest."
data-fetching — owns the real fix for the two biggest cost areas (caching, invocations). vercel-doctor detects, data-fetching corrects (Next 16 "use cache" / Server-Component reads). Don't duplicate its ladder.
shadscan — the third gate: UI quality + accessibility. Same shape (pre-deploy, no phase bump, auto-fix safe + route the rest), different surface. Worth noting the contrast in precision: shadscan reports what is present in the file it names, while the dead-code pass here resolves imports speculatively and gets it wrong most of the time.
vercel-deploy — the actual Vercel deploy. vercel-doctor runs before it, not instead of it.
Definition of Done
docs/vercel/doctor-report.md written; meta.json#vercel_doctor populated with the score + findings.
- Safe fixes applied as reviewable diffs (dead code removed with
tsc green; config/image tweaks); judgment calls routed to data-fetching / design-md-to-app with a one-line pointer each.
- Every reported finding was verified in code before acting (no raw scan noise).
What this skill does NOT do
- Not a legal/privacy audit — that's
compliance-audit.
- Doesn't deploy — that's
vercel-deploy.
- Doesn't re-implement the caching/read rules — it routes to
data-fetching.
- Doesn't bump
phase, and never blocks deploy by itself.
Reference files
references/contracts.md — the .workflow/ dev-flow contract (vendored).
1---2name: vercel-doctor3description: Run vercel-doctor on a Next.js project to find costly Vercel patterns BEFORE they inflate the bill, then route the fixes to the owning dev-flow skill (`data-fetching` for caching, `design-md-to-app` for images). The cost counterpart to `compliance-audit` (legal gate); dev-flow proposes it before a Vercel deploy. Use when the user says "my Vercel bill is high", "optimize for Vercel", "reduce Vercel cost", "vercel-doctor", "check caching / function duration / image cost". Refuses for non-Vercel / non-Next targets. Not for: legal/privacy audit (use compliance-audit), building features, or actually deploying (use vercel-deploy).4---56# vercel-doctor — cost & performance pre-deploy gate for Vercel/Next.js78Runs on a project that already exists and **targets Vercel**. It wraps the third-party [vercel-doctor](https://www.vercel-doctor.com/) CLI — which scans a Next.js codebase for **patterns that cost money on Vercel** — and turns its report into applied fixes, routing each finding to the skill that owns it.910> **Third-party tool.** vercel-doctor is **not** an official Vercel product — it's an independent11> open-source project by [Aniket-508](https://github.com/Aniket-508/vercel-doctor), published to npm as12> **`vercel-doctor`** (bin `vercel-doctor`, **MIT**). Verified against the npm registry + repo README13> (latest at time of writing: **1.2.0** — pin or re-check, the flag set can change). It reads your14> codebase and phones home unless `--offline`; treat its AI-fix prompts as suggestions to verify, not gospel.1516## Verified invocation + flags1718```bash19npx -y vercel-doctor@latest . # scan the project at <path>20npx -y vercel-doctor@latest . --output markdown --report docs/vercel/doctor-report.md21npx -y vercel-doctor@latest . --ai-prompts docs/vercel/doctor-fixes.json22```2324| Flag | Effect |25|---|---|26| `--output <human\|json\|markdown>` | report format (default `human`) |27| `--report <file>` | write the report to a file |28| `--ai-prompts <file>` | export fix prompts as JSON (only issues with a known fix strategy) |29| `--score` | print only the health score |30| `--verbose` | list the matching files per rule |31| `--diff [base]` | scan only files changed vs the base branch |32| `--project <name>` / `-y, --yes` | pick workspace project(s) / scan all without prompting |33| `--no-lint` / `--no-dead-code` | skip the lint / dead-code analyses |34| `--offline` | skip telemetry |35| `-v, --version` / `-h, --help` | version / help |3637If a flag above is rejected, the CLI has moved — run `npx -y vercel-doctor@latest --help` and use what38it reports rather than guessing.3940### ⚠️ Two behaviours the `--help` does not tell you (found by running it)41421. **With uncommitted changes it silently scans only those.** You don't have to pass `--diff` — if the43 working tree is dirty the run prints *"Scanning uncommitted changes"* and looks at just those files.44 A dirty tree can therefore produce a confident **"No issues found!"** that means nothing. **Run it on a45 clean tree** (commit or stash first) when you want a real audit, and check the line that says either46 *"Found N source files"* (full scan) or *"Scanning N changed source files"* (diff mode) before you47 trust the result.482. **`--offline` also disables the score.** Telemetry is what computes it (`--help`: *"anonymous, not49 stored, only used to calculate score"*). Use `--offline` for a private codebase and accept there is no50 number; drop it only when the user is happy for the scan to phone home.5152## The six cost areas it scans5354| Area | Typical finding | Who fixes it in dev-flow |55|---|---|---|56| **Caching** | `fetch`/routes that opt out of the CDN; missing `"use cache"` / `revalidate` | **`data-fetching`** (Next 16 Cache Components — `"use cache"`, `cacheLife`, `revalidateTag`) |57| **Unused code** | dead files, exports, types | safe cleanup (delete + `tsc` verify) — mechanical, this skill |58| **Function duration** | long-running serverless work, blocking awaits, oversized bundles | this skill flags; heavy refactors → the owning feature skill |59| **Image optimization** | unoptimized images, missing `sizes`, raster where SVG fits | **`design-md-to-app`** image patterns / `next/image` config |60| **Function invocations** | patterns triggering excessive serverless calls (per-request fan-out) | **`data-fetching`** (lift reads to Server Components, batch) |61| **Platform / config** | `next.config` / `vercel.json` / region / runtime misconfig | this skill (config is mechanical) |6263## Run, then route64651. **Preconditions.** `meta.json#stack.framework ∈ {"next","monorepo"}` and the deploy target is Vercel (`stack.deploy = "vercel"` or the user says so). Refuse otherwise — the checks are Vercel-specific.662. **Run** `npx -y vercel-doctor@latest . --output markdown --report docs/vercel/doctor-report.md` at the project root (or `apps/web/`, or `--project <name>` in a monorepo). Add `--ai-prompts docs/vercel/doctor-fixes.json` when you want the fix prompts. Capture the **markdown report** + **health score** (`--score` alone if you only need the number).673. **Triage** each finding by area (table above). **Verify it in the code** before acting — a scan is a68 signal, not a verdict. Trust the categories unequally:6970 | Category | Trust | Why |71 |---|---|---|72 | Caching / route policy | **high** | reading a route's headers is a fact |73 | Deployment + config (file count, `--archive=tgz`, Fluid Compute) | **high** | mechanical, verifiable |74 | Static-asset size | medium | the threshold is low (~25 KB); judge each asset |75 | Link prefetch | **read the version first** — see below | |76 | **Dead code (`Unused file` / `Unused export` / `Unused type`)** | **lowest — never act on it alone** | see below |7778 ⚠️ **The dead-code pass is the one that will hurt you.** It runs `knip`, which resolves imports on its79 own and does **not** know about framework-discovered entry points. On a real run against a Next 16 +80 eve project it flagged **67 of 115 files (58%) as unused** — and every one we spot-checked was wrong:81 `agent/agent.ts` (the **eve agent entry point**, discovered by path, imported by nobody — deleting it82 destroys the agent), `db/index.ts` (imported by `agent/tools/*` via a relative path), `hooks/use-mobile.ts`83 (imported through the `@/` alias), and `buttonVariants` (used by `components/ui/calendar.tsx`).84 **Open every file before deleting it**, and treat anything a framework discovers by convention —85 `agent/**`, route files, `middleware`/`proxy`, `instrumentation.ts`, config files — as a guaranteed86 false positive. When in doubt, `--no-dead-code` and audit that separately.8788 ⚠️ **The Link-prefetch advice predates Next 16.3.** It says "add `prefetch={false}`… add `prefetch={true}`89 only to critical links", which was right when every link cost a prefetch request. Under **Partial90 Prefetching** (16.3) Next prefetches one reusable **App Shell per route**, so rendering a `<Link>` is91 effectively free and `prefetch={true}` means something different (deeper per-link prefetch + runtime92 prefetching). On ≤16.2 the advice stands; on 16.3+ with `partialPrefetching` on, **ignore it** and follow93 `data-fetching` §Instant Navigation instead.944. **Apply the safe, mechanical fixes** here (dead-code deletion with `tsc --noEmit` green, config corrections, `next/image` `sizes`/format tweaks).955. **Route the judgment calls** to the owning skill rather than hand-fixing: caching + invocation findings → `data-fetching` (it owns the Next 16 read/caching ladder); image-pattern findings → `design-md-to-app`. Don't re-implement their rules here.966. **Persist**: write the report to `docs/vercel/doctor-report.md`, update `meta.json#vercel_doctor`, append `history`. **No phase bump.**9798## `meta.json#vercel_doctor` block99100```jsonc101"vercel_doctor": {102 "last_run_at": "<ISO>",103 "health_score": 0, // as reported104 "findings": { "high": 0, "medium": 0, "low": 0 },105 "fixed": ["unused-code","image-sizes"],106 "routed": { "data-fetching": ["caching","invocations"], "design-md-to-app": ["images"] }107}108```109110## dev-flow hook111112Horizontal capability — run any time. dev-flow **proposes it as a pre-deploy gate** at `feature_complete` (right beside `compliance-audit` — legal gate + cost gate before shipping), and re-runs it in the `deployed` maintenance loop (catch cost regressions after changes). It records `meta.json#vercel_doctor` + `history` and **never bumps `phase`**. It never *blocks* the deploy on its own — it surfaces the score + findings so the user decides.113114## Relationship to other skills115116- **`compliance-audit`** — the sibling gate: legal/privacy risk (GDPR/AI-Act) vs vercel-doctor's cost/perf risk. Both are `feature_complete` pre-deploy gates, both no-phase-bump, both "auto-fix safe + route/flag the rest."117- **`data-fetching`** — owns the real fix for the two biggest cost areas (caching, invocations). vercel-doctor **detects**, `data-fetching` **corrects** (Next 16 `"use cache"` / Server-Component reads). Don't duplicate its ladder.118- **`shadscan`** — the third gate: UI quality + accessibility. Same shape (pre-deploy, no phase bump, auto-fix safe + route the rest), different surface. Worth noting the contrast in *precision*: shadscan reports what is present in the file it names, while the dead-code pass here resolves imports speculatively and gets it wrong most of the time.119- **`vercel-deploy`** — the actual Vercel deploy. vercel-doctor runs *before* it, not instead of it.120121## Definition of Done122123- `docs/vercel/doctor-report.md` written; `meta.json#vercel_doctor` populated with the score + findings.124- Safe fixes applied as reviewable diffs (dead code removed with `tsc` green; config/image tweaks); judgment calls routed to `data-fetching` / `design-md-to-app` with a one-line pointer each.125- Every reported finding was **verified in code** before acting (no raw scan noise).126127## What this skill does NOT do128129- **Not a legal/privacy audit** — that's `compliance-audit`.130- **Doesn't deploy** — that's `vercel-deploy`.131- **Doesn't re-implement** the caching/read rules — it routes to `data-fetching`.132- **Doesn't bump `phase`**, and never blocks deploy by itself.133134## Reference files135136- `references/contracts.md` — the `.workflow/` dev-flow contract (vendored).