Tutorial

May 15, 2026 · 8 min read

Clean Up Your React/Next.js Codebase with DeadCode

Find unused exports, dead routes, orphaned CSS, and unreferenced components — with concrete before-and-after examples for a real Next.js project.

Every codebase accrues dead code. A feature gets replaced, but the old component stays. A route gets merged, but the page file lingers. A utility export was used exactly once and now the import is gone but the export remains.

Over time, this becomes a tax on every developer who touches the project:

DeadCode is a CLI tool that scans your project and identifies unused exports, unreferenced components, dead route files, and orphaned CSS classes. It runs in seconds, integrates with any CI pipeline, and returns actionable results — not noise.


What We'll Build

We'll take a realistic Next.js App Router project and walk through:

  1. Scanning for unused exports and dead routes
  2. Interpreting the output (what's real vs. false positives)
  3. Removing the dead code safely
  4. Setting up DeadCode in CI to prevent backsliding

Step 1: Install and Scan

Install DeadCode:

pip install git+https://github.com/Coding-Dev-Tools/deadcode.git

Navigate to your project root and run a scan. For a Next.js App Router project, we care about app/, components/, lib/, and utils/:

deadcode scan -p . --exclude node_modules,.next --verbose

Here's what the output looks like on a real project:

DeadCode Scan Results
Scanning /Users/dev/my-next-app... [UNUSED_EXPORT] src/lib/formatting.ts:12 - formatRelativeTime Last import found 47 commits ago (Mar 2025) Used by: (none) [UNUSED_EXPORT] src/lib/formatting.ts:34 - formatCurrency Last import found: never Used by: (none) [DEAD_ROUTE] app/dashboard/legacy-reports/page.tsx No internal links point to /dashboard/legacy-reports git log --oneline --diff-filter=D -- app/dashboard/legacy-reports/ → route was replaced by /dashboard/analytics 6 months ago [DEAD_ROUTE] app/api/v1/deprecated-endpoint/route.ts No internal links point to /api/v1/deprecated-endpoint Search npm: "deprecated-endpoint" — no external callers found in repo [UNREFERENCED_COMPONENT] src/components/old-hero-banner.tsx Exported as HeroBanner — no imports found git log: last modified 14 months ago (feature/redesign) [UNREFERENCED_COMPONENT] src/components/deprecated/beta-badge.tsx Exported as BetaBadge — no imports found Note: directory name "deprecated" suggests this was intentionally archived [ORPHANED_CSS] src/styles/legacy-utilities.css Selectors .legacy-grid, .old-card-stack — no matches in any .tsx/.ts file Note: 0 matches across 342 source files Summary: 4 unused exports, 2 dead routes, 2 unreferenced components, 1 orphaned stylesheet Estimated savings: 44 KB bundle size, 327 lines of code
Reading the output: Each finding includes a category tag and — most importantly — supporting evidence. DeadCode doesn't just say "something is unused"; it tells you why it thinks so: git history, import searches, cross-references. This makes triaging findings much faster than with linters.
Share this article:

Step 2: Triage the Findings

Not every flagged item is safe to remove. Here's how to triage each category:

Unused Exports (formatRelativeTime, formatCurrency)

These are the safest to remove. DeadCode traces every import statement in the project. If formatRelativeTime is exported but never imported anywhere, it's dead. The only exception is re-exports from barrel files (index.ts) — DeadCode handles those by resolving the chain. If a public library export is consumed outside the repo (e.g., an npm package), use --allow-unresolved-imports or mark it with an annotation.

Decision for formatRelativeTime: Remove it. It was replaced by date-fns/formatDistanceToNow 47 commits ago. The comment in the code even says "TODO: migrate to date-fns."

Dead Routes (legacy-reports, deprecated-endpoint)

Next.js App Router pages and API routes that no internal link points to. DeadCode checks all <Link>, router.push(), <a> tags, and redirect configs. If no path points to /dashboard/legacy-reports and the git log confirms it was replaced, it's safe to delete the directory.

For API routes, DeadCode also checks for external callers by searching the codebase for URL references. The deprecated-endpoint route has no callers — the frontend migrated to the v2 endpoint 6 months ago. Remove it.

Unreferenced Components (old-hero-banner, beta-badge)

DeadCode checks for direct imports, dynamic imports (next/dynamic), and React.lazy references. old-hero-banner was part of a redesign and was replaced by modern-hero-banner. The directory name "deprecated" for beta-badge is a strong signal it was intentionally archived. Both safe to delete.

Orphaned CSS

CSS files that are imported but whose selectors don't match any rendered components. legacy-utilities.css is imported in layout.tsx (so it's bundled) but none of its classes appear in any TSX file. Delete the file and remove the import.


Step 3: Remove the Dead Code

With DeadCode, you have two removal strategies:

Manual removal (recommended for first pass)

Delete files one by one, running the test suite after each. This is safest because you verify no breakage before moving on.

# Delete dead routes
rm -rf app/dashboard/legacy-reports/
rm -rf app/api/v1/deprecated-endpoint/

# Delete unused components
rm -rf src/components/old-hero-banner.tsx
rm -rf src/components/deprecated/

# Remove unused exports (edit the file)
# Delete formatRelativeTime and formatCurrency functions + their exports

# Remove orphaned CSS file + import
rm src/styles/legacy-utilities.css
# Also remove: import './legacy-utilities.css' from layout.tsx

# Re-run scan to verify
deadcode scan -p . --exclude node_modules,.next
After Cleanup
Scan complete: 0 dead exports, 0 dead routes, 0 unreferenced components, 0 orphaned CSS files Estimated savings realized: 327 LOC, 44 KB bundle size

Batch removal with verification

Once you trust the output, use --json to pipe results into your own scripts:

deadcode scan -p . --exclude node_modules,.next --json > deadcode-results.json

# Then process with jq or a script to generate delete commands
cat deadcode-results.json | jq -r '.[] | select(.severity=="high") | .file' | xargs rm
Tip: The --allow-list flag lets you suppress findings you've consciously decided to keep (e.g., a publicly exported utility you plan to deprecate over two releases). Run with --generate-allow-list to create an initial baseline, then review and trim it.

Step 4: Set Up DeadCode in CI

The real power is preventing dead code from accumulating again. Add DeadCode to your CI pipeline as a check that blocks PRs introducing unused exports:

# .github/workflows/deadcode-check.yml
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.11'
      - run: pip install git+https://github.com/Coding-Dev-Tools/deadcode.git
      - run: deadcode scan -p . --exclude node_modules,.next
        # Fails (exit 1) if unused exports or dead routes are found

Now every PR is automatically checked. If a developer adds an export but forgets to use it, or merges a route change without cleaning up the old page, DeadCode catches it before it reaches main.


DeadCode vs. ESLint (no-unused-vars)

You might be thinking: "Doesn't ESLint already handle this?" Let's clarify the difference:

  ESLint no-unused-vars DeadCode
Scope Variables in a single file Exports, routes, CSS across the entire project
Cross-file No — per-file only Yes — resolves import chains across the whole project
Next.js routes No Yes — checks app/ and pages/ route linking
CSS selectors No Yes — checks if selectors match rendered components
Dynamic imports No Yes — checks next/dynamic and React.lazy

ESLint's no-unused-vars catches local variables you declared but never used inside a single file. But it won't catch an exported component that nothing imports, a page that no route links to, or a CSS class that no component references. Those are DeadCode's job.


Real-World Impact

In a recent cleanup of an internal Next.js monorepo, DeadCode found:

The cleanup saved 168 KB of parsed bundle size (pre-minification, post-tree-shaking) and removed 2,100+ lines of code that were doing nothing but increasing maintenance burden.


deadcode scan -p /path/to/your/project --exclude node_modules,.next

Find unused exports, dead routes, orphaned CSS, and unreferenced components — in seconds.

Try It Now

DeadCode is free and open source under Apache 2.0. Install it, run it on your project, and see how much dead code you've accumulated. The first scan might surprise you.

pip install deadcode
deadcode scan -p /path/to/react-project --exclude node_modules

Get Early Access

PyPI publishing is coming soon. Leave your email and we'll notify you the moment DeadCode and the rest of the Revenue Holdings tools ship.

✓ You're on the list. We'll email you when tools launch.

Star us on GitHub · View Pricing