# Find Dead Code in a Next.js Project

Yes. An agent finds unused files, exports and dependencies in your Next.js codebase and lists only what still type-checks and builds once removed.

[Run this on Rush](https://cloud.getrush.ai/run/find-dead-code/nextjs/).

## What the agent does

The agent clones the repository into its own sandbox and maps how Next.js and the monorepo reach code before it trusts any tool's output. In a Next.js codebase like dubinc/dub, the root `package.json` sets `packageManager` to `pnpm@9.15.9` and runs Turborepo 1 (`turbo.json` still uses the `pipeline` key), and `pnpm-workspace.yaml` covers `apps/*`, `packages/*`, `packages/embeds/*` and `apps/web/.react-email`. The app is `apps/web` on Next.js `15.5.8`, with Prisma `6.19.1` reading a schema split across files in `apps/web/prisma/schema`, so `build` there is `pnpm prisma:generate && next build`.

In a Next.js app most entry points are files no one imports: every `page.tsx`, `layout.tsx`, `route.ts`, `not-found.tsx` and `opengraph-image.tsx` under `apps/web/app` is loaded by the framework, as are `middleware.ts`, `instrumentation.ts` and `next.config.js`. The agent runs knip with its Next.js plugin enabled for `apps/web` and adds the entries this repository needs on top. `scripts/run.ts` starts `tsx ./scripts/${command}.ts` from a name on the command line, so every file under `apps/web/scripts` looks unused to static analysis and is marked as an entry. `vitest.config.ts` loads `tests/setupTests.ts`, the `@/lib/*`, `@/ui/*` and `@/scripts/*` aliases come from `apps/web/tsconfig.json`, and `packages/email` exports `./templates/*` as a wildcard, so each of its templates is reachable by name.

Published packages need their own rule. The root scripts publish `@dub/ui`, `@dub/utils`, `@dub/embed-core`, `@dub/embed-react`, `@dub/tailwind-config` and the CLI to npm, so an export of one of them that nothing in the monorepo imports may still be used by someone outside it. The agent lists those as public API to decide on, never as dead. It also flags configuration that points at nothing: `apps/web/tsconfig.json` includes `../../packages/blocks/src/event-list.tsx`, and there is no `packages/blocks` in the repository.

Each remaining candidate is then checked by removing it on a scratch branch and running what would catch the breakage: `tsc --noEmit` for `apps/web` and the `check-types` script in `packages/ui` and `packages/utils`, `pnpm build` in `apps/web` (Prisma generate and `next build`), `pnpm build:packages` for the packages, and `next lint`. The Vitest suite in `apps/web/tests` calls a running deployment through `E2E_BASE_URL` (`tests/utils/env.ts`), so the agent says plainly that it could not run it in the sandbox rather than reporting it as passed. A dependency is checked by removing it from its workspace's `package.json`, running `pnpm install` and the same commands. Anything that fails goes back, with its error, into a list of things that looked dead and were not.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Clone https://github.com/dubinc/dub. Find unused files, exports and package dependencies with knip, configured for this pnpm and Turborepo monorepo: enable the Next.js plugin for apps/web, treat apps/web/scripts/**, tests/setupTests.ts and packages/email/src/templates/* as entries, and resolve the aliases in apps/web/tsconfig.json. List exports of packages published to npm (@dub/ui, @dub/utils, the embeds, the CLI) separately as public API. Check each other candidate by removing it on a scratch branch and running tsc --noEmit in apps/web, pnpm check-types in packages/ui and packages/utils, pnpm build in apps/web, pnpm build:packages and next lint. Say which checks you could not run and why. Write dead-code.md listing only what passed, grouped into files, exports and dependencies, each with the command output, plus the candidates that failed with their errors and any config that points at missing paths. Push nothing and open no pull request."
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is `dead-code.md`, a session artifact with a download link that lasts an hour. It lists what can be removed in three groups, files, exports and dependencies, and for each line names the workspace and the checks that still passed with it gone. A separate section lists exports of the published packages that only outside users could reach, and another lists configuration that refers to paths that no longer exist, such as a `tsconfig.json` include.

The report is as honest about what it could not prove as about what it did. The checks section says which commands ran on every candidate and which could not run in the sandbox, such as an integration suite that needs a live deployment, so a reader knows exactly what each removal rests on. The session's transcript keeps every knip run and every remove-and-rebuild cycle with its output.

Before deleting, a reviewer looks for code reached in ways neither knip nor the build can see: a helper only a cron route listed in `apps/web/vercel.json` reaches through a computed import, a template picked by name at runtime, or a script someone runs by hand. Route handlers themselves are entries and never reported, so a cron that is no longer needed is a question for the team, not this report. Then a follow-up message to the same session can ask for a pull request that removes the confirmed items, one group per commit.

## Run it on every release branch cut

Dead code is cheapest to remove right after a release, when nothing is in flight. When your release process cuts a branch, have it send this request from your server with an `Idempotency-Key` built from the release tag, so each release starts one run.

A webhook URL on the API key gets `run.completed` with the `session_url` when the run finishes. Your server reads the session, takes the `dead-code.md` link from its artifacts and opens a cleanup issue with it attached.

## Why run it on Rush?

Proving removals in a monorepo this size means many cycles of edit, install, Prisma generate, type check and `next build`, each one long enough to block a laptop. In a Rush sandbox all of those run on the agent's own machine and disk, with its own `node_modules` and Prisma client, and your working tree and branches are never touched.

A report that says something is unused has to be believed by people who did not run it. This one carries the transcript of every command and its output, and says outright which checks could not run, so each line can be traced to a passing build. Several repositories, or several workspaces of one, can be checked at once, one run each, up to the key's concurrent run limit.

## Frequently asked questions

### Does it understand the App Router's file conventions?

Yes. Pages, layouts, route handlers, `not-found`, Open Graph image files and middleware are loaded by Next.js rather than imported, so the agent treats them as entry points through knip's Next.js plugin. A file under `app/` is only reported if it is not one of those conventions and nothing imports it.

### What about tests that need secrets or a live deployment?

The agent runs every check that works in a clean sandbox and lists the ones it could not, with the reason. For an integration suite like the one that reads `E2E_BASE_URL`, you can save the values as Project secrets and start the session in that Project, or run that suite yourself before deleting.

### Will it delete code from my repository?

No. Removals happen on a scratch branch in the sandbox and exist only to run the checks. The prompt says to push nothing, and the output is a report. Ask for a pull request in a follow-up message once you have read it.

### Does it work with pnpm workspaces and Turborepo?

Yes. The agent reads `pnpm-workspace.yaml` and `turbo.json`, configures knip per workspace, and uses the repository's own pnpm scripts for the build and type checks, so dependencies are judged in the workspace that declares them.
