Skip to content

perf(webapp): memoize react-router per-request route matching via pnpm patch#3877

Open
ericallam wants to merge 1 commit into
mainfrom
perf/react-router-route-matching
Open

perf(webapp): memoize react-router per-request route matching via pnpm patch#3877
ericallam wants to merge 1 commit into
mainfrom
perf/react-router-route-matching

Conversation

@ericallam

@ericallam ericallam commented Jun 9, 2026

Copy link
Copy Markdown
Member

Summary

Under high request load the webapp spends most of its CPU inside react-router's matchRoutes, not in application code. @remix-run/router@1.23.2 (the React Router v6 / Remix 2 core) re-flattens, re-ranks, and recompiles the entire route table on every request, and with the webapp's ~436 routes that cost dominates once request rates climb. There is no NODE_ENV gate, so production pays it too.

This adds a pnpm patch that memoizes the parts that depend only on the static route manifest: it caches the flattened/ranked branches per route tree, hoists the loop-invariant decodePath out of the match loop, and caches compiled path regexes.

Benchmark

CPU profile over the same load (100 concurrent tag feeds, ~425 req/s), NODE_ENV=production, before vs after the patch:

Metric Before After
Active CPU (self-time over the window) 28.3s 18.5s (-34%)
Route-matching self-time 19.2s 7.5s (-61%)
Event-loop lag p99 322ms 113ms (-65%)
Idle headroom 26% 52%

Application/realtime code was ~0% of CPU in both profiles; the bottleneck was entirely generic per-request route matching.

Why a patch instead of an upgrade

The inefficiency is acknowledged upstream (remix-run/react-router#8653). A contributor PR doing exactly this (remix-run/react-router#14866) was closed in favor of a narrower fix (remix-run/react-router#14967, branch caching only, shipped in React Router v7), with the maintainer suggesting patch-package as the interim until the Remix 3 route-pattern rewrite (see remix-run/remix#4786). We are on the v6-era core and cannot pick up even the partial fix without a framework migration, so this patch is the sanctioned stopgap, and it also includes the compiled-regex cache the merged PR left out.

patches/README.md documents the full rationale, the safety argument (deterministic, internal-only, bounded caches), and when to remove the patch.

…m patch

react-router's matchRoutes re-flattened, re-ranked, and recompiled the entire
route table on every request. With the webapp's ~436 routes that cost dominates
once request rates climb, and there is no NODE_ENV gate so production pays it too.

Patch @remix-run/router to memoize the work that depends only on the static route
manifest: cache flattened/ranked branches per route tree, hoist the loop-invariant
path decode out of the match loop, and cache compiled path regexes. On a local
benchmark this cut route-matching CPU by roughly 60% and halved event-loop lag
under load.

See patches/README.md for the upstream history (react-router#14866 / #14967) and
when to drop the patch.
@changeset-bot

changeset-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: de32edf

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ff220da3-7fad-4ab8-a335-c7b38b9ef6b3

📥 Commits

Reviewing files that changed from the base of the PR and between df964ea and de32edf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • .server-changes/react-router-route-matching-perf.md
  • package.json
  • patches/@remix-run__router@1.23.2.patch
  • patches/README.md
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,jsx,css,json,md}

📄 CodeRabbit inference engine (AGENTS.md)

Use Prettier for code formatting and run pnpm run format before committing

Files:

  • patches/README.md
  • package.json
🧠 Learnings (1)
📚 Learning: 2026-05-14T14:54:39.095Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3545
File: .server-changes/agent-view-sessions.md:10-10
Timestamp: 2026-05-14T14:54:39.095Z
Learning: In the `trigger.dev` repository, do not flag inconsistent dot vs slash notation in route/path strings inside `.server-changes/*.md` files. These markdown files are consumed verbatim into the changelog, so the mixed notation (e.g., `resources.orgs.../runs.$runParam/...`) is intentional and should be preserved as-is.

Applied to files:

  • .server-changes/react-router-route-matching-perf.md
🪛 LanguageTool
patches/README.md

[grammar] ~72-~72: Ensure spelling is correct
Context: ...w algorithm instead of trying to > band-aide perf improvements to the existing algor...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~94-~94: Ensure spelling is correct
Context: ...build (dist/router.cjs.js), which the webapp server loads at runtime (`@remix-run/...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~99-~99: Ensure spelling is correct
Context: ... to remove Drop this patch if/when the webapp moves to React Router v7+ (which thread...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (4)
patches/@remix-run__router@1.23.2.patch (1)

1-69: LGTM!

package.json (1)

89-90: LGTM!

patches/README.md (1)

1-102: LGTM!

.server-changes/react-router-route-matching-perf.md (1)

1-6: LGTM!


Walkthrough

This PR adds a performance optimization patch for react-router's route matching logic. The patch introduces three caching strategies: a WeakMap-based cache for flattened and ranked route branches per routes tree reference, hoisting of path decoding to avoid repeated decoding per branch, and a Map-based cache for compiled path patterns keyed by pattern parameters with a size guard at 2000 entries. The patch is registered in package.json's pnpm.patchedDependencies, with supporting documentation in patches/README.md explaining the optimization rationale and safety assumptions, plus a changelog entry recording the improvement.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description provides comprehensive technical context, benchmarks, rationale, and upstream links, but does not follow the repository's required template structure with explicit checklist, testing, and changelog sections. Reformat the description to match the repository template: add the explicit checklist items, dedicated Testing section, and Changelog section, while preserving the detailed technical content.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly summarizes the main change: memoizing react-router's per-request route matching via a pnpm patch to improve webapp performance.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/react-router-route-matching

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ericallam ericallam marked this pull request as ready for review June 9, 2026 16:58

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant