Dead Code Detection
Knip configuration reference -- per-workspace entry points, exclusions, and why dead code detection matters for AI-generated codebases.
Knip is a dead code detection tool for JavaScript/TypeScript monorepos. It analyzes entry points, import graphs, and configuration files to find unused files, unused exports, unused dependencies, and unused types across the entire workspace graph. Unlike ESLint's no-unused-vars (which checks within a single file), Knip traces cross-package usage to determine whether an exported function is actually imported anywhere.
Why Dead Code Detection Matters Here
Dead code detection is especially important with AI coding agents. Agents tend to create new code instead of discovering and reusing existing patterns. When two Claude Code sessions independently solve similar problems, the result is two implementations where only one is actually used. Over time, this accumulates dead exports, orphaned files, and phantom dependencies.
Running Locally
pnpm lint:dead-code
This expands to knip, which reads configuration from knip.json at the monorepo root.
Configuration
Per-Workspace Entry Points
Knip needs to know the entry points for each package so it can trace the import graph. The configuration defines these per workspace.
apps/web -- Next.js entry points:
| Entry Pattern | Why |
|---|---|
src/app/**/page.tsx | Next.js page routes |
src/app/**/layout.tsx | Next.js layout components |
src/app/**/route.ts | Next.js API route handlers |
source.config.ts | Fumadocs MDX source configuration |
vitest.config.ts | Test configuration |
src/**/__tests__/**/*.{ts,tsx} | Test files |
Ignored dependencies in apps/web: tailwindcss (consumed by PostCSS, not imported), postcss (same), @vitejs/plugin-react (Vitest plugin reference), fumadocs-mdx (build plugin).
packages/db -- Drizzle ORM package:
| Entry | Why |
|---|---|
src/index.ts | Main barrel export |
drizzle.config.ts | Drizzle Kit configuration |
vitest.config.ts | Test configuration |
Ignored dependency: drizzle-seed (used by seed scripts that Knip cannot trace).
packages/mcp -- MCP tool server (multiple entry points):
| Entry | Why |
|---|---|
src/index.ts | Barrel export |
src/server.ts | MCP server entry point |
src/auth.ts | Auth helper (imported by server) |
src/resolve-org.ts | Organization resolver |
packages/api -- tRPC routers. Ignored dependency: pino (used by logger context, not directly imported).
packages/auth -- Better Auth. Ignored dependency: drizzle-orm (used by Better Auth adapter config, not directly imported).
packages/logger -- Pino logger. Ignored dependency: pino-pretty (used via Pino transport config, not a direct import).
packages/* -- catch-all for remaining packages. Standard src/index.ts entry point.
tools/test-audit -- CLI tool with src/cli.ts entry point. Ignored dependencies: Stryker mutator packages (loaded dynamically by Stryker framework).
Global Exclusions
Certain packages and directories are excluded entirely from Knip analysis because they produce false positives:
| Excluded | Why |
|---|---|
packages/config-eslint/** | Tooling config, not an importable package |
packages/config-typescript/** | Tooling config, not an importable package |
packages/design-tokens/** | Consumed via Tailwind CSS config, not TypeScript imports |
apps/web/src/components/ui/** | shadcn/ui generated components (some may not be used yet) |
scripts/** | Standalone scripts, not part of the application graph |
Global Ignored Dependencies
| Dependency | Why |
|---|---|
@repo/* | Internal workspace packages (Knip handles these via workspace graph) |
@vitest/coverage-v8 | Vitest coverage plugin (loaded by Vitest, not imported) |
@modelcontextprotocol/sdk | MCP SDK (used by transport, Knip cannot trace) |
@vvago/vale | Vale prose linter (CLI tool, not imported) |
markdown-link-check | CLI tool, not imported |
vitest | Test runner (loaded by config, not imported in source) |
Configuration Flags
{
"vitest": false,
"ignoreExportsUsedInFile": true,
"exclude": ["unlisted"]
}
vitest: false-- disables Knip's built-in Vitest plugin (entry points are defined manually for accuracy)ignoreExportsUsedInFile-- does not flag exports that are also used within the same fileexclude: ["unlisted"]-- suppresses "unlisted dependency" findings (handled by other tools)
Barrel File Convention
One src/index.ts barrel file per package is the standard public API surface. Barrel files inside feature directories within apps/web/ are prohibited because:
- They cause circular import issues with Next.js App Router
- They make Knip's analysis less accurate -- everything re-exported from a barrel appears "used" even if nothing downstream imports it
CI Integration
Knip runs as pnpm lint:dead-code in the pnpm ci:check pipeline, after dependency-cruiser and before jscpd. A non-zero exit code blocks the PR from merging.
Common Findings
| Finding | Typical Cause | Fix |
|---|---|---|
| Unused export | AI agent created a helper that was later replaced | Remove the export, or remove the file if all exports are unused |
| Unused dependency | Package.json lists a dep that is no longer imported | Remove from package.json, run pnpm install |
| Unused file | AI agent created a utility file but never imported it | Delete the file |
Related Pages
- Duplication Detection -- jscpd catches the complementary problem (code that exists twice, both used)
- Dependency Graph Validation -- dependency-cruiser's
no-orphansrule catches a similar class of issues