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:
npm install | vnode_modules/ regenerated from scratch | vYour 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:
- Storing your modifications as diff files (patches)
- Automatically applying those patches after every install
- Keeping patches in version control for team sharing
npm install | vnode_modules/ regenerated | vpostinstall script runs patch-package | vPatches applied from patches/ folder | vYour modifications restored!3. Step-by-Step Setup
3.1 Install patch-package
First, install patch-package as a development dependency:
npm install patch-package --save-devyarn add patch-package --devpnpm add patch-package --save-dev3.2 Add the postinstall Script
This is the critical step that many people forget. Add a postinstall script to your 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:
ls node_modules/react-router/dist/index.jsOpen 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:
npx patch-package react-routerFor pnpm, you might need:
npx patch-package react-router --use-yarnThis creates a patch file in your project:
patches/ react-router+6.26.0.patch3.5 Commit the Patch
Add the patches directory to version control:
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:
diff --git a/node_modules/react-router/dist/index.js b/node_modules/react-router/dist/index.jsindex 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
{ "devDependencies": { "patch-package": "^8.0.0" }}{ "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:
patch-package: Applying patches... - patches/react-router+6.26.0.patchIf 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": { "patchedDependencies": { } }}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:
1. Update React Router version | v2. Run npm install | v3. Check patch-package output for errors | v4a. If clean: Test your application | v Done! |4b. If errors: Re-apply changes to new version | v Regenerate patch | v Commit updated patchAlways 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:
name: CIon: [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 testNo 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:
- Install patch-package
- Add postinstall script
- Modify the package in node_modules
- 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:
- 👨💻 patch-package on npm
- 👨💻 React Router PR #14866
- 👨💻 React Router Documentation
- 👨💻 pnpm patched-dependencies
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments