Find and Remove Dead Code in Next.js App Router Projects
Next.js App Router changed how we structure React apps. The app/ directory, nested layouts, server components, and file-based routing are powerful โ but they also create new ways for code to die silently. Routes get refactored, layouts change, components get replaced, and CSS classes become orphaned. Nobody notices until bundle size creeps up and node_modules feels heavier than it should.
DeadCode is a CLI tool that scans TypeScript/React/Next.js projects for four categories of dead code and can remove it safely โ with a dry-run preview before anything gets deleted.
Try it now
pip install git+https://github.com/Coding-Dev-Tools/deadcode.git · Scan your Next.js project in 30 seconds
View on GitHub →In This Article
Why Dead Code Accumulates in App Router Projects
Next.js App Router introduces file-based routing where the directory structure is the routing config. This is elegant, but it creates dead code patterns that don't exist in Pages Router or SPA projects:
- Route refactoring โ You merge
app/dashboard/old-feature/intoapp/dashboard/new-feature/. The oldpage.tsxgets deleted, but the components it imported are still exported fromcomponents/. - Layout churn โ Nested layouts change frequently. A
layout.tsxthat usedSidebarNavgets replaced withCompactNav. The old component sits there forever. - Server/client component migration โ Converting components from server to client (or vice versa) often leaves behind unused utility functions, CSS modules, and type definitions.
- CSS module drift โ When a component's JSX changes, the corresponding
.module.cssclasses may no longer be referenced. Unlike Tailwind's purge, CSS modules don't clean themselves.
The result: your project accumulates hundreds of lines of unreachable code. It slows down builds, confuses new developers, and makes code review harder because reviewers can't distinguish active code from dead code.
The Four Categories of Dead Code
DeadCode detects four specific categories of dead code, each targeting a different failure mode:
| Category | What It Finds | Example |
|---|---|---|
unused_export |
Exported names that are never imported elsewhere | export function formatDate() with zero consumers |
dead_route |
Next.js routes with no internal links pointing to them | app/legacy/page.tsx โ no <Link> or router.push() references it |
orphaned_css |
CSS module classes defined but never referenced in JSX | .legacyCard in styles.module.css with zero className usages |
unreferenced_component |
React components defined but never imported | <OldWidget /> component with no import sites |
Each category uses AST-aware scanning to avoid false positives. A function that's exported and used in another file won't be flagged. A CSS class that appears in a template literal className={`card-${variant}`} won't be marked as orphaned.
Install and Scan Your Project
Install DeadCode and run your first scan in under a minute:
# Install DeadCode
pip install git+https://github.com/Coding-Dev-Tools/deadcode.git
# Navigate to your Next.js project
cd /path/to/your-nextjs-app
# Run a full scan
deadcode scan
DeadCode will scan the entire project and output a categorized report:
Scanning /path/to/your-nextjs-app ...
โโโโ DeadCode Scan Results โโโโโโโโโโโโโโโโโโโโโ
โ Category Count โ
โโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ unused_export 23 โ
โ dead_route 4 โ
โ orphaned_css 15 โ
โ unreferenced_component 7 โ
โโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Total 49 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Unused Exports:
- components/format.ts: formatDate, parseCurrency
- lib/validators.ts: validateEmail, sanitizeInput
- app/api/deprecated/route.ts: handler
Dead Routes:
- app/legacy/page.tsx (no internal links)
- app/deprecated-api/page.tsx (no internal links)
Orphaned CSS:
- components/Card/styles.module.css: .legacyCard, .oldShadow
- app/dashboard/styles.module.css: .sidebar, .collapsedMenu
Unreferenced Components:
- components/OldSidebar.tsx: OldSidebar
- components/LegacyChart.tsx: LegacyChart
To scan a specific project path:
deadcode scan -p /path/to/project
To filter by a specific category:
# Only find dead routes
deadcode scan -c dead_route
# Only find orphaned CSS
deadcode scan -c orphaned_css
Review and Understand Findings
Before removing anything, get a quick overview with deadcode stats:
deadcode stats
Project: my-nextjs-app
Files scanned: 347
Unused exports: 23
Dead routes: 4
Orphaned CSS: 15
Unreferenced components: 7
Total dead code items: 49
Estimated removable lines: ~890
For JSON output (useful for scripting or CI integration):
deadcode scan --json-output > deadcode-report.json
The JSON output includes file paths, line numbers, category, and the specific dead code identifier โ everything you need to build custom tooling on top of the scan results.
Safe Removal with Dry-Run
DeadCode's removal workflow is designed to be safe. Always preview with --dry-run first:
# Preview what would be removed (no files are changed)
deadcode remove --dry-run
# Remove only a specific category
deadcode remove --dry-run -c orphaned_css
# When you're satisfied with the preview, apply the removals
deadcode remove
# Remove only orphaned CSS
deadcode remove -c orphaned_css
deadcode remove --dry-run and review the output before every removal. DeadCode's scanner is thorough but no static analysis tool is perfect โ you may have exports that are consumed by external packages, other repos, or dynamic imports that the scanner can't detect. Use -i to ignore paths you want to skip.
CI Integration: Fail on Dead Code
Dead code should stay dead โ meaning, once you clean it up, it shouldn't come back. Add DeadCode to your CI pipeline to catch new dead code before it merges:
# Fail CI if any dead routes are found
deadcode scan -c dead_route --fail 1
# Fail CI if total findings exceed a threshold
deadcode scan --fail 10
# Generate a JSON report for CI artifacts
deadcode scan --json-output > deadcode-report.json
The --fail N flag sets the threshold. If the scan finds N or more items, the command exits with code 1 (failing CI). Set it to 1 for zero-tolerance on specific categories, or use a higher number to gradually reduce dead code without blocking every PR.
GitHub Actions example:
name: Dead Code Check
on: [pull_request]
jobs:
deadcode:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install git+https://github.com/Coding-Dev-Tools/deadcode.git
- run: deadcode scan --fail 5
For more CI patterns, see our guide to failing CI on dead code.
Project Configuration with .deadcode.yml
Create a .deadcode.yml file in your project root to configure DeadCode permanently:
# .deadcode.yml
ignore:
- "generated/"
- "**/*.generated.ts"
- "app/api/generated/" # Auto-generated API routes
categories:
- unused_export
- dead_route
- orphaned_css
- unreferenced_component
# Exit with code 1 if findings >= this number (for CI gating)
fail_threshold: 10
CLI flags override config file settings, so you can bump the threshold for a specific CI run without changing the config.
Ignore patterns are useful for generated code, Storybook stories, and test utilities that are exported but consumed outside the project's import graph.
App Router-Specific Tips
Handling Dynamic Routes
App Router uses dynamic segments like app/blog/[slug]/page.tsx. DeadCode's dead_route scanner recognizes these patterns and checks for <Link href="/blog/..."> references. If you have a catch-all route like app/api/[...slug]/route.ts, add it to your ignore list to avoid false positives:
# .deadcode.yml
ignore:
- "app/api/[...slug]/"
Server Components vs. Client Components
DeadCode scans both server and client components. If you're migrating components between the two, you'll often find that server component utilities become unused when the component moves to client. DeadCode catches these orphaned utilities as unused_export findings.
Layout and Template Files
Next.js layout.tsx and template.tsx files are included in the scan. If you've replaced a nested layout with a simpler one, the old layout's components will show up as unreferenced_component findings.
CSS Modules in App Router
The App Router encourages CSS Modules (*.module.css). These are a common source of dead code because class names are referenced as styles.className โ and when JSX changes, the CSS class is easily orphaned. DeadCode's orphaned_css scanner catches these reliably.
Include Specific Directories
To focus your scan on the App Router app/ directory:
# Only scan the app/ directory
deadcode scan --include "app/"
# Scan app/ and components/ only
deadcode scan --include "app/" --include "components/"
The --include flag accepts gitignore-style patterns and is repeatable.
Get Started
Dead code doesn't have to be a permanent tax on your project. Install DeadCode, scan your Next.js app, and see how much dead weight you're carrying:
pip install git+https://github.com/Coding-Dev-Tools/deadcode.git
cd your-nextjs-app
deadcode scan
deadcode remove --dry-run # Preview before removing
DeadCode is part of the DevForge CLI tool suite โ 11 developer tools for API contracts, schema conversion, infrastructure diffs, config drift, and more. All built by autonomous AI agents.
Get started
Install DeadCode and scan your Next.js project in 30 seconds. No config required โ just run deadcode scan.