Skip to content

How to Use patch-package to Apply React Router Performance Fixes That Won't Be Merged

1. The Problem: A Fix That Won’t Be Merged

I was debugging performance issues in my React application with 300+ routes. The CPU usage was spiking on every navigation. After profiling, I found the culprit: React Router’s route matching algorithm.

I found a pull request (PR #14866) that promised an 80% CPU reduction. The React Router team acknowledged the issue but explicitly stated they wouldn’t merge it. Why? They’re developing a completely new algorithm with no release timeline.

Here’s what a React Router team member said:

“This is great as a patch-package optimization for those who want it”

So I had two choices: wait indefinitely for an official fix, or find a way to apply the optimization myself. That’s when I discovered patch-package.

2. Why patch-package?

Before diving into the solution, let me explain why direct modification of node_modules doesn’t work:

The problem with direct modifications
npm install
|
v
node_modules/ regenerated from scratch
|
v
Your changes? GONE.

Every time you run npm install, yarn, or pnpm install, the node_modules directory is regenerated. Any changes you make directly to files inside it are lost.

patch-package solves this by:

  1. Storing your modifications as diff files (patches)
  2. Automatically applying those patches after every install
  3. Keeping patches in version control for team sharing
How patch-package works
npm install
|
v
node_modules/ regenerated
|
v
postinstall script runs patch-package
|
v
Patches applied from patches/ folder
|
v
Your modifications restored!

3. Step-by-Step Setup

3.1 Install patch-package

First, install patch-package as a development dependency:

Install with npm
npm install patch-package --save-dev
Install with yarn
yarn add patch-package --dev
Install with pnpm
pnpm add patch-package --save-dev

3.2 Add the postinstall Script

This is the critical step that many people forget. Add a postinstall script to your package.json:

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

Without the postinstall script, your patches won’t apply automatically. Other developers on your team (or CI/CD systems) would have to manually run npx patch-package after every install.

3.3 Modify the Package in node_modules

Now comes the actual fix. Navigate to the React Router package in your node_modules:

Find React Router location
ls node_modules/react-router/dist/index.js

Open this file and apply the performance optimization. For PR #14866, the key changes involve:

  • Memoizing compiled route patterns
  • Reducing redundant regex operations
  • Early termination in route matching

Make your changes directly to the file in node_modules.

3.4 Generate the Patch

After making your changes, generate the patch file:

Generate patch with npm/yarn
npx patch-package react-router

For pnpm, you might need:

Generate patch with pnpm
npx patch-package react-router --use-yarn

This creates a patch file in your project:

Created patch file location
patches/
react-router+6.26.0.patch

3.5 Commit the Patch

Add the patches directory to version control:

Commit your patches
git add patches/
git commit -m "Add React Router performance patch (80% CPU reduction)"

4. Understanding the Patch File

Let me show you what a typical patch file looks like:

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
index 1234567..abcdefg 100644
--- a/node_modules/react-router/dist/index.js
+++ b/node_modules/react-router/dist/index.js
@@ -matchRoutes function
function matchRoutes(routes, location) {
- // Original: linear scan with regex compilation on each call
+ // Optimized: memoized pattern matching
+ const cacheKey = JSON.stringify(routes) + location.pathname;
+ if (matchCache.has(cacheKey)) {
+ return matchCache.get(cacheKey);
+ }
+
for (const route of routes) {
const match = matchPath(route.path, location);
if (match) {
+ matchCache.set(cacheKey, match);
return match;
}
}

The patch file uses standard diff format. It shows:

  • The file being modified
  • The exact line numbers
  • Lines removed (prefixed with -)
  • Lines added (prefixed with +)

5. Common Mistakes to Avoid

Mistake 1: Forgetting the postinstall Script

WRONG: Patch won't apply automatically
{
"devDependencies": {
"patch-package": "^8.0.0"
}
}
CORRECT: Patch applies on every install
{
"devDependencies": {
"patch-package": "^8.0.0"
},
"scripts": {
"postinstall": "patch-package"
}
}

Mistake 2: Not Verifying Patch Application

After running npm install, check the output. You should see something like:

Expected patch-package output
patch-package: Applying patches...
- patches/react-router+6.26.0.patch

If you see warnings about patches not applying cleanly, the package may have been updated and you need to regenerate your patch.

Mistake 3: Using Wrong Command for pnpm

pnpm users have an alternative: built-in patchedDependencies support:

pnpm native patch support in package.json
{
"pnpm": {
"patchedDependencies": {
"[email protected]": "patches/react-router.patch"
}
}
}

This is pnpm’s native solution and works without the postinstall script.

6. Maintaining Patches Over Time

When React Router releases a new version, your patch might not apply cleanly. Here’s the workflow:

Patch maintenance flow
1. Update React Router version
|
v
2. Run npm install
|
v
3. Check patch-package output for errors
|
v
4a. If clean: Test your application
|
v
Done!
|
4b. If errors: Re-apply changes to new version
|
v
Regenerate patch
|
v
Commit updated patch

Always test your application after dependency updates. A patch that applied cleanly might still break functionality if the underlying code changed significantly.

7. CI/CD Integration

The postinstall script runs automatically in CI/CD environments. Here’s a GitHub Actions example:

.github/workflows/ci.yml
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
# postinstall automatically runs patch-package
- run: npm test

No special configuration needed. The patches apply automatically during npm ci.

8. When to Use patch-package

Good use cases:

  • Critical bug fixes awaiting official merge
  • Performance optimizations the maintainers won’t accept
  • Backporting fixes to older versions
  • Temporary workarounds while waiting for releases

Avoid when:

  • The change could be a proper PR
  • You’re patching security vulnerabilities (report them!)
  • The package is actively maintained with quick releases
  • Your patch is complex and affects many files

9. Summary

patch-package is the officially recommended solution when you need fixes that won’t be merged. For my React Router performance issue, it gave me an immediate 80% CPU reduction without waiting for an official solution.

The setup is straightforward:

  1. Install patch-package
  2. Add postinstall script
  3. Modify the package in node_modules
  4. Generate and commit the patch

The trade-off is maintenance overhead when packages update, but for critical fixes, it’s often worth it.

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