
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
Find and remove unused code in Next.js and NestJS projects.
Pruny scans your codebase using regex-based static analysis to detect unused API routes, page links, exports, files, public assets, and NestJS service methods. Works with monorepos out of the box.
npm install -g pruny
# or
npx pruny
| Scanner | What it finds |
|---|---|
| API Routes | Unused Next.js route.ts handlers and NestJS controller methods |
| Broken Links | <Link>, router.push(), redirect(), href: "/path" in arrays/objects pointing to pages that don't exist |
| Unused Exports | Named exports and class methods not imported anywhere |
| Unused Files | Source files not reachable from any entry point |
| Unused Services | NestJS service methods never called by controllers or other services |
| Public Assets | Images/files in public/ not referenced in code |
| Source Assets | Media files in src/ not referenced in code |
| Missing Assets | References to files in public/ that don't exist |
| Command | Description |
|---|---|
pruny | Interactive scan (auto-detects monorepo apps) |
pruny --all | CI mode: scan all apps, exit 1 if issues found |
pruny --fix | Interactively delete unused items |
pruny --dry-run | Simulate fix mode and output a JSON report |
pruny --app <name> | Scan a specific app in a monorepo |
pruny --folder <path> | Scan a specific folder for routes/controllers |
pruny --cleanup <items> | Quick cleanup: routes, exports, public, files |
pruny --filter <pattern> | Filter results by path or app name |
pruny --ignore-apps <list> | Skip specific apps (comma-separated) |
pruny --no-public | Skip public asset scanning |
pruny --json | Output results as JSON |
pruny -v, --verbose | Verbose debug logging |
pruny --dir <path> | Set target directory (default: ./) |
pruny -c, --config <path> | Path to config file |
pruny init | Generate a pruny.config.json with defaults |
Create pruny.config.json in your project root (or run pruny init):
{
"ignore": {
"routes": ["/api/webhooks/**", "/api/cron/**"],
"folders": ["node_modules", ".next", "dist"],
"files": ["*.test.ts", "*.spec.ts"],
"links": ["/custom-path", "/legacy/*"]
},
"extensions": [".ts", ".tsx", ".js", ".jsx"]
}
| Key | What it does | Example |
|---|---|---|
ignore.routes | Skip API routes matching these patterns | ["/api/webhooks/**"] |
ignore.folders | Exclude directories from scanning | ["node_modules", "dist"] |
ignore.files | Exclude specific files from scanning | ["*.test.ts"] |
ignore.links | Suppress broken link warnings for these paths | ["/view_seat", "/admin/*"] |
All patterns support glob syntax (* matches any characters, ** matches nested paths).
Pruny also reads .gitignore and automatically excludes those folders.
| Key | What it does |
|---|---|
nestGlobalPrefix | NestJS global route prefix (e.g., "api/v1") |
extraRoutePatterns | Additional glob patterns to detect route files |
excludePublic | Set true to skip public asset scanning |
Pruny searches for config files recursively across your project:
pruny.config.json.prunyrc.json.prunyrcIn monorepos, configs from multiple apps are merged together. CLI --config takes precedence.
Pruny automatically handles multi-tenant architectures where routes live under dynamic segments like [domain].
For example, if your file structure is:
app/(code)/tenant_sites/[domain]/view_seat/page.tsx
And your components reference /view_seat (resolved at runtime via subdomain), Pruny recognizes this as a valid route and will not report it as a broken link. The matched tail must contain at least one literal segment (e.g., view_seat) — fully-dynamic tails like [token] alone won't match arbitrary paths.
If auto-detection doesn't cover your case, use ignore.links in config:
{
"ignore": {
"links": ["/view_seat", "/review", "/custom-path"]
}
}
Pruny auto-detects monorepos by looking for an apps/ directory. It scans each app independently but checks references across the entire monorepo root.
# Scan all apps (CI-friendly, exits 1 on issues)
pruny --all
# Scan a specific app
pruny --app web
# Skip certain apps
pruny --ignore-apps admin,docs
Add to your CI pipeline to catch unused code before merging:
npx pruny --all
This scans all monorepo apps and exits with code 1 if any issues are found. Combine with --json for machine-readable output.
app/api/**/route.ts (Next.js) and *.controller.ts (NestJS) files<Link>, router.push(), redirect(), href: "/path" in arrays/config objects, <a> tags, revalidatePath(), and pathname === comparisons — validates all against known page routes. The summary table always shows an "Internal Links" row when links are scanned, so you can see the feature is active[id], [...slug], [[...slug]] dynamic segmentsRoutes listed in vercel.json cron jobs are automatically marked as used:
{ "crons": [{ "path": "/api/cron/cleanup", "schedule": "0 0 * * *" }] }
DEBUG_PRUNY=1 pruny
Enables verbose logging across all scanners.
MIT
FAQs
Find and remove unused Next.js API routes & Nest.js Controllers
We found that pruny demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.