Skip to content

Recover stale React Router route graphs without process restart - #2564

Closed
rjcortese wants to merge 8 commits into
mainfrom
ai_main_09c5971ed3f94ab59c06
Closed

Recover stale React Router route graphs without process restart#2564
rjcortese wants to merge 8 commits into
mainfrom
ai_main_09c5971ed3f94ab59c06

Conversation

@rjcortese

@rjcortese rjcortese commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds automatic recovery for stale React Router route graphs during agent-native dev, plus unit and integration tests covering the classifier, recovery coordinator, and end-to-end Vite dev server behavior.

Problem

During agent-native dev, deleting or renaming a route file could leave React Router's virtual:react-router/server-build referencing a now-missing file, causing repeated ERR_LOAD_URL SSR failures until the dev server was manually restarted, interrupting the dev workflow.

Solution

Introduce a recovery coordinator that classifies stale route errors from the SSR error chain and serializes Vite restarts triggered either proactively by route file watcher topology changes (add/unlink) or reactively as an SSR fallback when a stale-load error is detected. Ordinary content edits, syntax errors, and application-level failures are left untouched to continue using normal HMR.

Key Changes

  • Added react-router-dev-recovery.ts/.spec.ts with classifyStaleReactRouterRouteError, extractViteLoadUrlPath, and createReactRouterRecoveryCoordinator:
    • Classifier walks nested error causes to strictly match ERR_LOAD_URL + virtual:react-router/server-build importer + a path within the configured route scope that is actually absent on disk.
    • Coordinator serializes restart calls with a single in-flight promise plus one pending rerun, supports a cooldown and per-module bounded consecutive fallback attempts, and can persist attempt state across coordinator replacement via a persistentStateKey.
    • Fallback attempt bounds are tracked per stale module and only cleared when that specific module's request path is later marked healthy via markSsrSuccess.
  • Added a Vite watcher integration (in react-router-dev-recovery.ts) that watches route discovery roots and config files, triggers debounced/serialized rebuilds on add/unlink/addDir/unlinkDir topology events, and leaves change events to normal HMR.
  • Added react-router-route-recovery.integration.spec.ts, a real Vite + React Router dev-server integration test verifying:
    • Non-route file changes and ignored route files do not trigger a restart.
    • Adding, editing, renaming, and deleting routes correctly trigger (or avoid) restarts as appropriate.
    • Syntax errors and loader failures do not trigger recovery.
    • The process PID remains unchanged throughout all recovery scenarios.
  • Added a changeset documenting the patch for @agent-native/core.

Edit in Builder  Preview


To clone this PR locally use the Github CLI with command gh pr checkout 2564

You can tag me at @BuilderIO for anything you want me to fix or change

@builder-io-integration builder-io-integration Bot changed the title Update from the Builder.io agent Recover stale React Router route graphs without process restart Jul 31, 2026
@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@builder-io-integration builder-io-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.

Builder reviewed your changes and found 1 potential issue 🟡

Review Details

Code Review Summary

PR #2564 adds development-time recovery for stale React Router route graphs. It introduces strict stale-load classification, a serialized Vite restart coordinator with bounded fallback attempts, route/config watcher integration, SSR 503 handling with no-store headers, and focused unit/integration coverage. The overall architecture is sound: recovery is limited to development, ordinary HMR changes remain untouched, restart requests are coalesced, and the SSR fallback avoids turning a transient route-graph rebuild into a cacheable response.

Risk: Standard. The main concern is a configuration-parsing edge case that can silently disable recovery for newly added routes. The coordinator, cleanup behavior, error-chain scoping, and SSR cache semantics otherwise look well covered by the added tests. I also excluded a reported HEAD response-body concern because the same body-discarding behavior existed before this PR and is not a regression.

Key Findings

🟡 MEDIUM — Static flatRoutes configurations containing comments are rejected by the parser, so add-route recovery can be disabled for valid configs.

✅ Good patterns: fail-closed stale-error classification, per-module attempt bounds, serialized restarts, explicit no-store fallback headers, and comprehensive integration coverage.

🧪 Browser testing: Will run after this review (PR touches UI-adjacent route/runtime behavior)

Comment on lines +109 to +112
let remaining = options.slice(1, -1);
if (rootMatch) remaining = remaining.replace(rootMatch[0], "");
if (ignoredMatch) remaining = remaining.replace(ignoredMatch[0], "");
if (remaining.replace(/[\s,]/g, "")) return undefined;

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.

🟡 Comments in a valid flatRoutes config disable add-route recovery

parseFlatRoutesDiscovery treats any text left after removing the recognized properties as an unsupported option, including comments that are valid in a static flatRoutes({...}) call. This returns undefined, leaves discoveryRoots empty, and means a newly added route under the custom root is not recognized by the watcher or recovered until a manual restart; strip comments or use an AST-based static parse while still failing closed for dynamic values.

Fix in Builder

@rjcortese

Copy link
Copy Markdown
Contributor Author

closing this one. i came up with something much better to try

@rjcortese rjcortese closed this Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants