perf(webapp): memoize react-router per-request route matching via pnpm patch#3877
perf(webapp): memoize react-router per-request route matching via pnpm patch#3877ericallam wants to merge 1 commit into
Conversation
…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.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (4)
📜 Recent review details🧰 Additional context used📓 Path-based instructions (1)**/*.{js,ts,tsx,jsx,css,json,md}📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (1)📚 Learning: 2026-05-14T14:54:39.095ZApplied to files:
🪛 LanguageToolpatches/README.md[grammar] ~72-~72: Ensure spelling is correct (QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1) [grammar] ~94-~94: Ensure spelling is correct (QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1) [grammar] ~99-~99: Ensure spelling is correct (QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1) 🔇 Additional comments (4)
WalkthroughThis 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 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsStopped 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 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. Comment |
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 noNODE_ENVgate, 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
decodePathout 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: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.mddocuments the full rationale, the safety argument (deterministic, internal-only, bounded caches), and when to remove the patch.