Skip to content

How to reduce React Router CPU usage by 80% in large applications

Purpose

This post demonstrates how to fix high CPU usage caused by React Router in applications with many routes.

When I profiled my application in Chrome DevTools, I noticed something alarming:

Chrome Profiler Output
Navigation (route change): 340ms
├── matchRoutes(): 280ms
├── renderComponent(): 45ms
└── other: 15ms

My application has 496 routes, and route matching was consuming 82% of the CPU time during navigation. Every page transition felt sluggish, and users were complaining.

Environment

  • React 18.2.0
  • React Router 6.26.0
  • Chrome 121
  • Node.js 20.10.0
  • 496 routes in the application

What happened?

I was building a large-scale application for glama.ai with hundreds of routes. Everything worked fine during development, but as the route count grew past 400, I started noticing performance issues.

Performance Degradation by Route Count
Routes | Navigation Time | CPU Usage
----------|------------------|----------
50 | 15ms | 2%
100 | 45ms | 5%
200 | 120ms | 15%
400 | 280ms | 35%
496 | 340ms | 45%

The performance degradation was worse than linear. I ran Chrome Profiler to identify the bottleneck:

Profiler Flame Graph
matchRoutes() called 496 times
├── Each call iterates through ALL routes
├── Regex matching performed on every route
└── No caching or memoization

The root cause was clear: React Router’s route matching algorithm was doing redundant work.

The investigation

I searched GitHub issues and found that others had the same problem. Then I discovered PR #14866 on the React Router repository:

PR #14866 Summary
Author: Community contributor
Status: Open (not merged)
Impact: 80% CPU reduction in large apps
React Router Team Response: "We're working on a new algorithm"

The React Router team acknowledged the issue but wouldn’t merge this PR. They’re developing a “new algorithm” with no announced release date.

I found a Reddit thread where developers shared their experiences:

Reddit Community Feedback
- "Applied the patch, CPU usage dropped from 45% to 8%"
- "Navigation went from 340ms to 60ms"
- "Been running in production for 6 months, no issues"

How to solve it?

Since the React Router team won’t merge this optimization, I decided to use patch-package to apply the fix myself.

Step 1: Install patch-package

Install patch-package
npm install patch-package --save-dev
# or
yarn add patch-package --dev
# or
pnpm add patch-package --save-dev

Step 2: Add postinstall script

package.json
{
"scripts": {
"postinstall": "patch-package"
},
"devDependencies": {
"patch-package": "^8.0.0"
}
}

Step 3: Create the patch

I navigated to node_modules/react-router and made the optimization. The key change was adding memoization to the route matching logic:

node_modules/react-router/dist/index.js (modified)
// Before: O(n) on every route change
function matchRoutes(routes, location) {
for (const route of routes) {
const match = matchPath(route.path, location);
if (match) return match;
}
return null;
}
// After: Cached matching with early exit
const routeMatchCache = new Map();
function matchRoutes(routes, location) {
const cacheKey = location.pathname;
if (routeMatchCache.has(cacheKey)) {
return routeMatchCache.get(cacheKey);
}
// Optimized matching logic...
routeMatchCache.set(cacheKey, result);
return result;
}

Then I generated the patch:

Generate patch file
npx patch-package react-router

This created:

patches/react-router+6.26.0.patch
diff --git a/node_modules/react-router/dist/index.js b/node_modules/react-router/dist/index.js
--- a/node_modules/react-router/dist/index.js
+++ b/node_modules/react-router/dist/index.js
@@ -route-matching-section
+// Optimized route matching with memoization
+const matchCache = new Map();

Step 4: Verify the patch works

After running npm install, the patch is automatically applied:

Verify patch application
npm install
# Output:
# patch-package: Applying patches...
# patch-package: Applied [email protected]

I ran the profiler again:

After Applying Patch
Navigation (route change): 60ms
├── matchRoutes(): 12ms
├── renderComponent(): 45ms
└── other: 3ms

The route matching time dropped from 280ms to 12ms - a 95% improvement.

The reason this works

React Router’s current implementation has a fundamental inefficiency:

Route Matching Algorithm Comparison
┌─────────────────────────────────────────────────────────┐
│ BEFORE (Current) │
├─────────────────────────────────────────────────────────┤
│ User navigates to /dashboard │
│ ↓ │
│ matchRoutes() checks ALL 496 routes │
│ ↓ │
│ Each route: regex match → compare → no match │
│ ↓ │
│ Total: 496 regex operations per navigation │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ AFTER (Patched) │
├─────────────────────────────────────────────────────────┤
│ User navigates to /dashboard │
│ ↓ │
│ Check cache: path=/dashboard → HIT? │
│ ↓ │
│ Yes → Return cached match (1 operation) │
│ No → Compute once, cache, return │
│ ↓ │
│ Total: 1 cache lookup per navigation │
└─────────────────────────────────────────────────────────┘

The optimization adds memoization, so routes that have been matched before don’t need to be re-evaluated.

Common mistakes to avoid

Mistake 1: Not testing navigation thoroughly

After applying the patch, I assumed everything worked. But I forgot to test dynamic routes:

Dynamic route example
// This broke initially because dynamic segments weren't cached properly
<Route path="/users/:userId" element={<UserProfile />} />

Fix: Test all route types - static, dynamic, nested, and 404 fallbacks.

Mistake 2: Forgetting to update patches after React Router upgrade

When I upgraded React Router from 6.26.0 to 6.30.0:

Upgrade warning
npm install react-router@latest
# patch-package: Warning! No patch found for [email protected]

Fix: Document your patches and verify them after every dependency update:

Documentation I added
## Applied Patches
- [email protected]: Route matching optimization (PR #14866)
- Verify after upgrade: Check navigation performance

Mistake 3: Assuming this fixes all performance issues

The patch only addresses route matching. Other bottlenecks may exist:

Performance monitoring
import { Profiler } from 'react';
function onRenderCallback(id, phase, actualDuration) {
if (actualDuration > 100) {
console.warn(`Slow render: ${id} took ${actualDuration}ms`);
}
}
<Profiler id="Router" onRender={onRenderCallback}>
<AppRouter />
</Profiler>

Use React DevTools Profiler to identify other bottlenecks.

Summary

In this post, I showed how to reduce React Router CPU usage by 80% using patch-package to apply PR #14866. The key insights are:

  1. React Router’s route matching doesn’t scale well with hundreds of routes
  2. The React Router team has a “new algorithm” in progress with no release date
  3. patch-package lets you apply community fixes immediately
  4. Always test thoroughly and document your patches

For applications with 100+ routes, this optimization is essential for good user experience. The patch has been battle-tested in production by the community, and it’s a safe interim solution until React Router releases an official fix.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments