Skip to content

[codex] Allow parallel dev compilers#54

Open
ScriptedAlchemy wants to merge 11 commits into
perf/bundling-performancefrom
codex/parallel-dev-compilers
Open

[codex] Allow parallel dev compilers#54
ScriptedAlchemy wants to merge 11 commits into
perf/bundling-performancefrom
codex/parallel-dev-compilers

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Goal

Let React Router development servers start faster by allowing Rsbuild's web and node compilers to run in parallel during development, while preserving coherent committed dev generations. Production builds keep the explicit node -> web compiler dependency.

What changed

  • Removed the hard dependencies: ['web'] edge from the dev node compiler config, keeping it for build.
  • Added compile-attempt identities so same-attempt web/node results can publish even when node starts before web completes.
  • Kept the existing completed-web lineage check for stale/mixed compiler cycles.
  • Added a node retry path when the runtime detects a mixed compiler pair, so real Rsbuild recovery can rebuild node against the latest completed web result instead of leaving initial readiness pending.
  • Enabled parallel route transforms by default without eagerly creating worker threads during plugin setup.
  • Updated the package interop harness to close every registered dev-server close hook, matching the lifecycle cleanup Rsbuild performs.
  • Added unit coverage for dev/build config behavior, parallel same-attempt startup/rebuild publication, and lazy worker creation.

Validation

  • corepack pnpm run test:core - 35 files, 357 tests passed
  • corepack pnpm exec tsc --noEmit
  • corepack pnpm run format:check
  • corepack pnpm run build
  • timeout 60s corepack pnpm run test:package-interop - exits 0
  • git diff --check

@ScriptedAlchemy ScriptedAlchemy changed the base branch from perf/bundling-performance to main June 26, 2026 23:50
@ScriptedAlchemy ScriptedAlchemy marked this pull request as ready for review June 26, 2026 23:51
@pkg-pr-new

pkg-pr-new Bot commented Jun 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

pnpm add https://pkg.pr.new/rsbuild-plugin-react-router@0c1f1e5

commit: 0c1f1e5

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

Compared PR head 90ea459 against base 44a6cad.

Total median wall time: 9.44s -> 9.94s (+5.2%, 0.95x speedup)
Compiler ready median: 9.44s -> 9.94s (+5.2%)
Route load median: 1.92s -> 2.01s (+4.8%)

Benchmark Base total Head total Delta Head ready Head routes Speedup Head RSS p95
large-355-ssr-esm 9.44s 9.94s +5.2% 9.94s 2.01s 0.95x -

Profile: large; mode: dev; iterations: 10; warmup: 0.
Workflow run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1095e45d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/dev-server.ts Outdated
@ScriptedAlchemy ScriptedAlchemy changed the base branch from main to perf/bundling-performance June 27, 2026 00:02
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b8c10ff6-a873-490f-a640-aeed1b40dcca

📥 Commits

Reviewing files that changed from the base of the PR and between 559bb2e and 0c1f1e5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (125)
  • .changeset/bright-routes-run.md
  • .changeset/coherent-dev-generations.md
  • .changeset/lazy-entry-hydration.md
  • .changeset/quiet-topology-order.md
  • .changeset/sharp-routes-heal.md
  • .changeset/warm-watchers-rest.md
  • README.md
  • benchmarks/README.md
  • config/package.json
  • config/rslib.config.ts
  • docs/coherent-dev-generations.md
  • examples/client-only/package.json
  • examples/cloudflare/package.json
  • examples/custom-node-server/app/load-context.d.ts
  • examples/custom-node-server/app/routes/projects/edit.tsx
  • examples/custom-node-server/app/routes/projects/index.tsx
  • examples/custom-node-server/app/routes/projects/layout.tsx
  • examples/custom-node-server/app/routes/projects/project.tsx
  • examples/custom-node-server/app/routes/projects/settings.tsx
  • examples/custom-node-server/package.json
  • examples/custom-node-server/playwright.config.ts
  • examples/custom-node-server/rsbuild.config.ts
  • examples/custom-node-server/scripts/smoke-production.mjs
  • examples/custom-node-server/server.js
  • examples/custom-node-server/server/index.ts
  • examples/custom-node-server/tsconfig.json
  • examples/default-template/app/dev-routes.ts
  • examples/default-template/app/routes.ts
  • examples/default-template/package.json
  • examples/default-template/playwright.config.ts
  • examples/default-template/react-router.config.ts
  • examples/default-template/rsbuild.config.ts
  • examples/default-template/tests/e2e/dev-route-watch.test.ts
  • examples/default-template/tests/e2e/lazy-compilation.test.ts
  • examples/epic-stack/package.json
  • examples/federation/epic-stack-remote/package.json
  • examples/federation/epic-stack/package.json
  • examples/prerender/package.json
  • examples/spa-mode/package.json
  • package.json
  • rslib.config.ts
  • scripts/bench-builds.mjs
  • scripts/benchmark/fixture.mjs
  • scripts/compare-benchmarks.mjs
  • scripts/test-package-interop.mjs
  • src/babel.ts
  • src/bounded-cache.ts
  • src/build-manifest.ts
  • src/build-output-transforms.ts
  • src/concurrency.ts
  • src/constants.ts
  • src/dev-generation.ts
  • src/dev-runtime-artifacts.ts
  • src/dev-runtime-compilation.ts
  • src/dev-runtime-controller.ts
  • src/dev-runtime-session.ts
  • src/dev-server.ts
  • src/export-utils.ts
  • src/index.ts
  • src/lazy-compilation.ts
  • src/manifest.ts
  • src/modify-browser-manifest.ts
  • src/parallel-route-transform-protocol.ts
  • src/parallel-route-transform-worker.ts
  • src/parallel-route-transforms.ts
  • src/performance.ts
  • src/plugin-utils.ts
  • src/prerender-build.ts
  • src/prerender.ts
  • src/react-router-config.ts
  • src/route-artifacts.ts
  • src/route-ast.ts
  • src/route-chunks.ts
  • src/route-component-transform.ts
  • src/route-export-pruning.ts
  • src/route-export-resolution.ts
  • src/route-transform-tasks.ts
  • src/route-watch.ts
  • src/server-build-plan.ts
  • src/server-utils.ts
  • src/templates/entry.server.tsx
  • src/typegen.ts
  • src/types.ts
  • src/virtual-modules.ts
  • src/warnings/warn-on-client-source-maps.ts
  • src/yuku.ts
  • tests/benchmark-fixture.test.ts
  • tests/bounded-cache.test.ts
  • tests/build-manifest.test.ts
  • tests/build-output-transforms.test.ts
  • tests/client-modules.test.ts
  • tests/dev-generation-multi-entry.test.ts
  • tests/dev-generation.test.ts
  • tests/dev-runtime-controller.test.ts
  • tests/dev-runtime.integration.test.ts
  • tests/dev-server.test.ts
  • tests/export-utils.test.ts
  • tests/features.test.ts
  • tests/fixtures/dev-runtime/app/entry.client.tsx
  • tests/fixtures/dev-runtime/app/entry.server.tsx
  • tests/fixtures/dev-runtime/app/root.tsx
  • tests/fixtures/dev-runtime/app/routes.ts
  • tests/fixtures/dev-runtime/app/routes/index.tsx
  • tests/fixtures/dev-runtime/app/routes/other.tsx
  • tests/fixtures/dev-runtime/react-router.config.ts
  • tests/fixtures/dev-runtime/server/index.ts
  • tests/index.test.ts
  • tests/lazy-compilation.test.ts
  • tests/manifest-split-route-modules.test.ts
  • tests/manifest.test.ts
  • tests/modify-browser-manifest.test.ts
  • tests/parallel-route-transforms.test.ts
  • tests/performance.test.ts
  • tests/plugin-utils.test.ts
  • tests/prerender.test.ts
  • tests/react-router-config.test.ts
  • tests/remove-exports.test.ts
  • tests/route-artifacts.test.ts
  • tests/route-chunks-cache.test.ts
  • tests/route-chunks.test.ts
  • tests/route-watch.test.ts
  • tests/server-build-plan.test.ts
  • tests/server-utils.test.ts
  • tests/setup.ts
  • tests/warn-on-client-source-maps.test.ts
💤 Files with no reviewable changes (10)
  • tests/plugin-utils.test.ts
  • tests/react-router-config.test.ts
  • src/babel.ts
  • tests/manifest-split-route-modules.test.ts
  • tests/prerender.test.ts
  • tests/performance.test.ts
  • examples/custom-node-server/server/index.ts
  • tests/modify-browser-manifest.test.ts
  • tests/manifest.test.ts
  • tests/parallel-route-transforms.test.ts
✅ Files skipped from review due to trivial changes (16)
  • tests/fixtures/dev-runtime/app/routes/index.tsx
  • examples/custom-node-server/app/routes/projects/index.tsx
  • examples/custom-node-server/app/routes/projects/layout.tsx
  • config/package.json
  • .changeset/warm-watchers-rest.md
  • .changeset/bright-routes-run.md
  • .changeset/lazy-entry-hydration.md
  • .changeset/coherent-dev-generations.md
  • .changeset/sharp-routes-heal.md
  • src/warnings/warn-on-client-source-maps.ts
  • examples/federation/epic-stack-remote/package.json
  • .changeset/quiet-topology-order.md
  • examples/custom-node-server/app/routes/projects/project.tsx
  • benchmarks/README.md
  • examples/client-only/package.json
  • docs/coherent-dev-generations.md
🚧 Files skipped from review as they are similar to previous changes (83)
  • tests/build-manifest.test.ts
  • tests/fixtures/dev-runtime/app/root.tsx
  • src/parallel-route-transform-protocol.ts
  • examples/custom-node-server/tsconfig.json
  • tests/fixtures/dev-runtime/react-router.config.ts
  • rslib.config.ts
  • tests/fixtures/dev-runtime/app/entry.server.tsx
  • examples/default-template/app/routes.ts
  • tests/fixtures/dev-runtime/app/routes/other.tsx
  • tests/fixtures/dev-runtime/app/routes.ts
  • examples/custom-node-server/app/load-context.d.ts
  • examples/spa-mode/package.json
  • tests/fixtures/dev-runtime/app/entry.client.tsx
  • src/virtual-modules.ts
  • tests/export-utils.test.ts
  • examples/prerender/package.json
  • examples/custom-node-server/rsbuild.config.ts
  • examples/federation/epic-stack/package.json
  • examples/custom-node-server/app/routes/projects/settings.tsx
  • examples/default-template/app/dev-routes.ts
  • examples/custom-node-server/app/routes/projects/edit.tsx
  • examples/default-template/tests/e2e/dev-route-watch.test.ts
  • src/bounded-cache.ts
  • examples/cloudflare/package.json
  • src/templates/entry.server.tsx
  • examples/default-template/rsbuild.config.ts
  • src/concurrency.ts
  • src/types.ts
  • tests/fixtures/dev-runtime/server/index.ts
  • examples/custom-node-server/playwright.config.ts
  • src/typegen.ts
  • examples/epic-stack/package.json
  • src/build-manifest.ts
  • examples/default-template/react-router.config.ts
  • scripts/compare-benchmarks.mjs
  • tests/dev-server.test.ts
  • src/lazy-compilation.ts
  • examples/default-template/package.json
  • src/dev-server.ts
  • tests/bounded-cache.test.ts
  • src/yuku.ts
  • src/constants.ts
  • examples/custom-node-server/package.json
  • src/parallel-route-transform-worker.ts
  • src/dev-runtime-compilation.ts
  • tests/dev-generation-multi-entry.test.ts
  • src/server-build-plan.ts
  • src/route-ast.ts
  • examples/custom-node-server/server.js
  • src/dev-runtime-session.ts
  • src/route-artifacts.ts
  • examples/custom-node-server/scripts/smoke-production.mjs
  • tests/build-output-transforms.test.ts
  • src/performance.ts
  • scripts/test-package-interop.mjs
  • src/dev-runtime-artifacts.ts
  • tests/client-modules.test.ts
  • examples/default-template/playwright.config.ts
  • src/dev-generation.ts
  • tests/lazy-compilation.test.ts
  • src/route-export-resolution.ts
  • src/route-component-transform.ts
  • package.json
  • src/react-router-config.ts
  • config/rslib.config.ts
  • src/prerender.ts
  • src/route-export-pruning.ts
  • src/export-utils.ts
  • tests/index.test.ts
  • src/build-output-transforms.ts
  • src/route-watch.ts
  • src/parallel-route-transforms.ts
  • src/route-transform-tasks.ts
  • tests/dev-generation.test.ts
  • src/plugin-utils.ts
  • examples/default-template/tests/e2e/lazy-compilation.test.ts
  • src/manifest.ts
  • tests/features.test.ts
  • src/modify-browser-manifest.ts
  • tests/dev-runtime-controller.test.ts
  • src/prerender-build.ts
  • src/route-chunks.ts
  • src/index.ts

📝 Walkthrough

Walkthrough

The pull request updates the React Router plugin internals around route analysis, route chunking, manifest generation, browser manifest emission, prerendering, and dev-server runtime coordination. It also adds benchmark scripts and fixture coverage, revises documentation and release notes, and updates example applications and package manifests.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: allowing parallel dev compilers.
Description check ✅ Passed The description is relevant and matches the changeset, especially around parallel dev compilers and related validation.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/parallel-dev-compilers

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.23.0)
examples/custom-node-server/tsconfig.json

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.22][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

examples/default-template/app/routes.ts

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.17][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

examples/default-template/package.json

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.19][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

  • 75 others
🔧 markdownlint-cli2 (0.22.1)
.changeset/bright-routes-run.md

markdownlint-cli2 wrapper config was not available before execution

.changeset/coherent-dev-generations.md

markdownlint-cli2 wrapper config was not available before execution

.changeset/lazy-entry-hydration.md

markdownlint-cli2 wrapper config was not available before execution

  • 6 others

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (10)
examples/custom-node-server/scripts/smoke-production.mjs-64-70 (1)

64-70: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Avoid the fixed 5s teardown delay after early exits.

If the child has already exited before this block runs, once(child, 'exit') subscribes too late and the script always waits the full timeout. Capture the exit promise before signaling, and skip the wait when exitCode is already set.

Proposed fix
 } finally {
-  child.kill('SIGTERM');
-  await Promise.race([
-    once(child, 'exit'),
-    new Promise(resolve => setTimeout(resolve, 5_000)),
-  ]);
-  if (child.exitCode === null) {
-    child.kill('SIGKILL');
-  }
+  if (child.exitCode === null) {
+    const exited = once(child, 'exit');
+    child.kill('SIGTERM');
+    await Promise.race([
+      exited,
+      new Promise(resolve => setTimeout(resolve, 5_000)),
+    ]);
+    if (child.exitCode === null) {
+      child.kill('SIGKILL');
+    }
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/custom-node-server/scripts/smoke-production.mjs` around lines 64 -
70, The teardown in smoke-production.mjs waits on once(child, 'exit') only after
sending SIGTERM, which can miss an exit that already happened and force the full
timeout. Update the child process handling around the existing child.kill and
Promise.race flow by creating the exit promise before signaling, and guard the
wait with child.exitCode so you skip the timeout path when the process has
already exited.
README.md-107-112 (1)

107-112: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document one parallelTransforms default.

Line 110 says @default true, but the defaults section on Lines 309-314 says the option defaults to undefined with worker execution enabled implicitly. Pick one representation so the public API contract is not self-contradictory.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 107 - 112, The README documentation for
parallelTransforms is contradictory about its default value. Update the public
API docs so the JSDoc on parallelTransforms and the defaults section describe
the same behavior, using one consistent default representation in both places.
Refer to parallelTransforms in the options docs and the defaults section, and
make sure the wording matches whether worker execution is enabled implicitly or
via an explicit true default.
README.md-527-527 (1)

527-527: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Exit on unrecoverable startup failure.

Line 527 only logs boot errors, but the section above explicitly requires a clean process restart after startup failure. Exit non-zero after logging so the example does not stay alive in a broken state.

Proposed fix
-startServer().catch(console.error);
+startServer().catch(error => {
+  console.error(error);
+  process.exit(1);
+});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 527, The startup example in startServer() currently only
logs failures with console.error and leaves the process running, which conflicts
with the documented requirement for unrecoverable startup failures. Update the
startup handling so the catch path for startServer() both logs the error and
terminates the process with a non-zero exit code, keeping the example from
continuing in a broken state.
scripts/benchmark/fixture.mjs-648-658 (1)

648-658: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fail fast on invalid largeConfig counts.

The new largeConfig override path is unvalidated, but several generators later use counts like workers, localeFiles, cssModules, svgAssets, utilitiesPerRoute, and payloadEntriesPerComponent in %/division and import-path generation. Passing 0 currently emits broken fixture code instead of rejecting the config up front.

🛠️ Proposed fix
+const assertPositiveInteger = (name, value) => {
+  if (!Number.isInteger(value) || value < 1) {
+    throw new Error(
+      `${name} must be a positive integer for the large benchmark fixture.`
+    );
+  }
+};
+
 const generateLargeFixture = async ({
   root,
   routeCount,
   variant,
@@
 }) => {
   const config = normalizeLargeConfig(routeCount, largeConfig);
+  for (const [name, value] of Object.entries({
+    routes: config.routes,
+    componentsPerRoute: config.componentsPerRoute,
+    utilitiesPerRoute: config.utilitiesPerRoute,
+    lazyModulesPerRoute: config.lazyModulesPerRoute,
+    workers: config.workers,
+    svgAssets: config.svgAssets,
+    cssModules: config.cssModules,
+    localeFiles: config.localeFiles,
+    payloadEntriesPerComponent: config.payloadEntriesPerComponent,
+  })) {
+    assertPositiveInteger(name, value);
+  }
   const isSpa = variant === 'spa';

Also applies to: 814-834

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/benchmark/fixture.mjs` around lines 648 - 658, The largeConfig
override path in generateLargeFixture/normalizeLargeConfig accepts invalid zero
values, which later break generators that use workers, localeFiles, cssModules,
svgAssets, utilitiesPerRoute, and payloadEntriesPerComponent in division,
modulo, or import-path logic. Add upfront validation for these count fields in
normalizeLargeConfig (or immediately after calling it in generateLargeFixture)
and reject non-positive values with a clear error before any fixture generation
starts.
rslib.config.ts-23-28 (1)

23-28: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Emit the route transform worker from the CJS build too.

createWorkerUrl() always resolves ./parallel-route-transform-worker.js, but the CJS source.entry block doesn’t include that file. CJS consumers won’t get the worker asset, so parallel transforms will fall back to the serial path.

Proposed fix
       source: {
         entry: {
           index: './src/index.ts',
+          'parallel-route-transform-worker':
+            './src/parallel-route-transform-worker.ts',
           'templates/entry.server': './src/templates/entry.server.tsx',
           'templates/entry.client': './src/templates/entry.client.tsx',
         },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rslib.config.ts` around lines 23 - 28, The CJS source.entry map is missing
the route transform worker entry, so CJS builds won’t emit the asset that
createWorkerUrl() resolves. Update the source.entry block in rslib.config.ts to
include the parallel route transform worker alongside the existing index,
templates/entry.server, and templates/entry.client entries, so CJS consumers
also receive parallel-route-transform-worker.js.
src/route-export-pruning.ts-544-549 (1)

544-549: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Don’t track sourced re-export names as local bindings.

For export { loader as clientLoader } from "./x", specifier.local.name is the imported name from the source module, not a local binding. Adding it to removedExportLocalNames can make the later assignment cleanup remove unrelated local loader.* = ... statements.

Proposed fix
             const exportedName = getExportedName(specifier);
             if (exportedName && exportsToRemoveSet.has(exportedName)) {
               exportsChanged = true;
-              if (specifier.local?.name) {
+              if (!statement.source && specifier.local?.name) {
                 removedExportLocalNames.add(specifier.local.name);
                 removedExportReferencedNames.add(specifier.local.name);
               }
               return false;
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/route-export-pruning.ts` around lines 544 - 549, The re-export cleanup in
the export pruning flow is incorrectly treating sourced re-export names as local
bindings. In the logic around the export specifier handling in
route-export-pruning and the sets removedExportLocalNames /
removedExportReferencedNames, only add names that are actually declared locally
in the file; for `export { loader as clientLoader } from "./x"`, avoid recording
`specifier.local.name` as a local binding so later assignment cleanup does not
delete unrelated `loader.*` statements. Use the existing export-specifier and
assignment-pruning checks to distinguish sourced re-exports from true local
exports before adding names to those tracking sets.
tests/index.test.ts-106-139 (1)

106-139: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Restore the previous existsSync mock implementation.

Line 111 overrides a shared mock, but Line 138 leaves it hardcoded to true. Capture and restore the previous implementation so this test cannot leak filesystem behavior into later cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/index.test.ts` around lines 106 - 139, Restore the shared fs.existsSync
mock in this test by capturing the previous mock implementation before
overriding it and reinstating that exact implementation in the finally block.
Update the test around createStubRsbuild and pluginReactRouter so the temporary
behavior used for the watchFiles assertion does not leak into later tests.
src/index.ts-231-239 (1)

231-239: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat serverOutput: undefined as omitted.

hasOwnProperty makes { serverOutput: undefined } explicit, so resolvedServerOutput can become undefined and flow into the node Rspack config.

Proposed fix
-    const hasExplicitServerOutput = Object.prototype.hasOwnProperty.call(
-      options,
-      'serverOutput'
-    );
-    let resolvedServerOutput = pluginOptions.serverOutput;
-    if (!hasExplicitServerOutput) {
-      resolvedServerOutput =
-        serverModuleFormat === 'cjs' ? 'commonjs' : 'module';
-    }
+    const hasExplicitServerOutput = options.serverOutput !== undefined;
+    const resolvedServerOutput = hasExplicitServerOutput
+      ? options.serverOutput
+      : serverModuleFormat === 'cjs'
+        ? 'commonjs'
+        : 'module';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.ts` around lines 231 - 239, Treat serverOutput: undefined the same
as an omitted option in the server output resolution logic. Update the
hasExplicitServerOutput check in the src/index.ts flow around
pluginOptions.serverOutput so that undefined does not count as explicitly set,
and keep the fallback based on serverModuleFormat ('cjs' => commonjs, otherwise
module) from flowing into the Node Rspack config. Reference the server output
resolution branch and the resolvedServerOutput assignment when making the fix.
src/dev-generation.ts-551-553 (1)

551-553: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep load() asynchronous in the ready path.

selectBuild() can throw here, which escapes synchronously instead of rejecting the promised API. That makes loadReactRouterServerBuild() behave differently between starting and ready states for the same missing-build failure.

Proposed fix
       if (state.kind === 'ready') {
-        return Promise.resolve(selectBuild(state.committed, entryName));
+        return Promise.resolve().then(() =>
+          selectBuild(state.committed, entryName)
+        );
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dev-generation.ts` around lines 551 - 553, The `load()` implementation in
`src/dev-generation.ts` should stay asynchronous in the `state.kind === 'ready'`
branch, because calling `selectBuild()` directly can throw synchronously instead
of returning a rejected promise. Update the ready-path logic around
`selectBuild()` and `loadReactRouterServerBuild()` so missing-build failures are
handled through promise rejection consistently with the `starting` state.
src/typegen.ts-12-19 (1)

12-19: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Use the local react-router CLI and keep watch failures visible.

  • npx --yes can fall back to a registry install when the binary is missing; run the app’s installed CLI directly so typegen stays on the pinned version.
  • The watch process currently swallows every rejection. Only ignore shutdown, or restart/log unexpected exits so typegen doesn’t die silently.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/typegen.ts` around lines 12 - 19, The typegen launcher in typegen.ts
should use the project’s locally installed react-router CLI instead of npx
--yes, so update the execa invocation to call the pinned binary directly from
the app dependencies. In the same typegen/watch flow, avoid swallowing every
rejected process exit: adjust the process handling around the execa call so only
intentional shutdown is ignored, and unexpected failures from the watch process
are surfaced or restarted/logged. Use the existing typegen process setup and its
process variable as the place to make these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/build-output-transforms.ts`:
- Around line 123-136: The fallback in build-output-transforms should stay
bundle-scoped even when latestServerManifestsByBundleId is not yet populated. In
the manifest selection logic around getLatestServerManifest,
getLatestServerManifestByBundleId, and getReactRouterManifestForDev, ensure the
cold-path dev manifest returned for
virtual/react-router/server-manifest-${bundleId} is filtered to the current
bundleId before it is used. Keep the per-bundle behavior consistent with the
virtual module wiring from src/index.ts so the fallback never embeds routes from
other bundles.

In `@src/dev-runtime-artifacts.ts`:
- Around line 145-174: The assertBuildMatchesManifest check only verifies routes
present in ReactRouterDevManifest, so stale ServerBuild entries for deleted
routes can slip through. Update assertBuildMatchesManifest to also compare
build.routes against manifest.routes and throw when the evaluated server build
contains any extra route IDs not in the manifest, alongside the existing
missing-route and loader/action export checks. Use the existing entryName,
build, manifest, and routeId logic to keep the validation in one place.

In `@src/dev-runtime-session.ts`:
- Around line 62-69: The completeClose flow in dev-runtime-session skips
closeBinding() when the session is already in the closing state, so successful
server.close() calls never fully clean up the runtime. Update completeClose to
invoke closeBinding(binding) for the successful closing path as well as active,
while still guarding with isCurrentBinding(binding) and preserving the
transition to idle; apply the same fix in the other matching close path around
the later close handler.

In `@src/prerender-build.ts`:
- Around line 175-177: The prerender write path logic in the output path
construction must prevent traversal outside clientBuildDir. Update the path
handling in prerender-build.ts around the outputPath/mkdir/writeFile flow to
validate normalizedPath segments before joining, reject any .. or
backslash-based traversal, and assert the final resolved path stays under
clientBuildDir. Apply the same safeguard to the other prerender output write
sites mentioned by the build flow helpers so all generated files remain confined
to the client build directory.
- Around line 496-498: The cleanup in enqueue path handling creates an
unconsumed promise from task.finally, which can surface prerender failures as
unhandled rejections; update the logic around enqueue, pending, and task so the
cleanup promise is handled explicitly. Use the task promise’s then with both
fulfillment and rejection handlers, or attach a catch to the promise returned by
finally, while still removing the task from pending.
- Around line 549-607: The prerender/build-request flow in prerender-build.ts
leaves process.env.IS_RR_BUILD_REQUEST mutated after the work completes. In the
block around resolveServerBuildModule, createRequestHandler, runPrerenderPaths,
and handleSpaMode, save the previous IS_RR_BUILD_REQUEST value before setting it
and restore it in a finally block after all prerender/Spa handling finishes.
This should be done around the existing build request logic so buildEnd and
subsequent runs/tests see the original process-global state.
- Around line 211-225: Escape the redirect target before interpolating it into
the fallback HTML in the redirect handling block so untrusted `Location` values
cannot inject markup or attributes. Update the logic around
`redirectStatusCodes`, `response.headers.get('Location')`, and the HTML template
in `src/prerender-build.ts` to use a safely escaped/sanitized version for the
`<title>`, `meta refresh`, and `<a href>`/link text. Keep `normalizedPath`
as-is, but ensure every use of the redirect destination is HTML- and
attribute-safe.

In `@src/route-chunks.ts`:
- Around line 211-216: The shared side-effect collection in route chunking is
incorrectly treating directive prologues like "use client" and "use strict" as
blocking statements. Update the filtering logic in the shared side-effect scan
and the related `topLevelNonModuleStatements` handling so `ExpressionStatement`s
that are directive prologues are excluded, while real side-effectful expressions
remain included. Use the existing `sharedTopLevelSideEffects` and export
analysis paths to ensure `splitRouteModules: "enforce"` only rejects genuinely
shared side effects.

In `@src/route-export-resolution.ts`:
- Around line 160-182: Update resolvePackageExports and related callers so
subpath imports are handled correctly: when packageJson.exports is absent, do
not always fall back to module/main for every packageSubpath, and make sure
pkg/subpath is not resolved as the package entry. In the exports branch, enhance
the lookup logic in resolvePackageExports to support non-exact export shapes
such as pattern mappings like "./*" instead of only direct packageSubpath keys.
Then adjust resolveExportAllModule so a null result from resolvePackageExports
is treated as “not matched yet” and it can still fall back to
createRequire(...).resolve() before rejecting the import.

In `@src/typegen.ts`:
- Around line 22-30: The `typegen --watch` promise handling in `typegenProcess`
is swallowing every rejection, which hides unexpected watcher exits. Update the
`.catch` path so it only ignores the intentional shutdown case from server
teardown, and for any other rejection in `src/typegen.ts` surface the failure by
logging or rethrowing it. Use the existing `typegenProcess` cleanup in
`.finally()` to keep the process reset behavior intact while making crashes or
invalid config failures visible.

In `@src/virtual-modules.ts`:
- Around line 11-19: The virtual module ID validation in the module path builder
does not block backslash separators, so a Windows-style traversal pattern can
slip through. Update the validation in the relativeId/segments check to reject
any segment containing backslashes before returning the node_modules path,
keeping the existing dot-segment and empty-segment checks in place.

---

Minor comments:
In `@examples/custom-node-server/scripts/smoke-production.mjs`:
- Around line 64-70: The teardown in smoke-production.mjs waits on once(child,
'exit') only after sending SIGTERM, which can miss an exit that already happened
and force the full timeout. Update the child process handling around the
existing child.kill and Promise.race flow by creating the exit promise before
signaling, and guard the wait with child.exitCode so you skip the timeout path
when the process has already exited.

In `@README.md`:
- Around line 107-112: The README documentation for parallelTransforms is
contradictory about its default value. Update the public API docs so the JSDoc
on parallelTransforms and the defaults section describe the same behavior, using
one consistent default representation in both places. Refer to
parallelTransforms in the options docs and the defaults section, and make sure
the wording matches whether worker execution is enabled implicitly or via an
explicit true default.
- Line 527: The startup example in startServer() currently only logs failures
with console.error and leaves the process running, which conflicts with the
documented requirement for unrecoverable startup failures. Update the startup
handling so the catch path for startServer() both logs the error and terminates
the process with a non-zero exit code, keeping the example from continuing in a
broken state.

In `@rslib.config.ts`:
- Around line 23-28: The CJS source.entry map is missing the route transform
worker entry, so CJS builds won’t emit the asset that createWorkerUrl()
resolves. Update the source.entry block in rslib.config.ts to include the
parallel route transform worker alongside the existing index,
templates/entry.server, and templates/entry.client entries, so CJS consumers
also receive parallel-route-transform-worker.js.

In `@scripts/benchmark/fixture.mjs`:
- Around line 648-658: The largeConfig override path in
generateLargeFixture/normalizeLargeConfig accepts invalid zero values, which
later break generators that use workers, localeFiles, cssModules, svgAssets,
utilitiesPerRoute, and payloadEntriesPerComponent in division, modulo, or
import-path logic. Add upfront validation for these count fields in
normalizeLargeConfig (or immediately after calling it in generateLargeFixture)
and reject non-positive values with a clear error before any fixture generation
starts.

In `@src/dev-generation.ts`:
- Around line 551-553: The `load()` implementation in `src/dev-generation.ts`
should stay asynchronous in the `state.kind === 'ready'` branch, because calling
`selectBuild()` directly can throw synchronously instead of returning a rejected
promise. Update the ready-path logic around `selectBuild()` and
`loadReactRouterServerBuild()` so missing-build failures are handled through
promise rejection consistently with the `starting` state.

In `@src/index.ts`:
- Around line 231-239: Treat serverOutput: undefined the same as an omitted
option in the server output resolution logic. Update the hasExplicitServerOutput
check in the src/index.ts flow around pluginOptions.serverOutput so that
undefined does not count as explicitly set, and keep the fallback based on
serverModuleFormat ('cjs' => commonjs, otherwise module) from flowing into the
Node Rspack config. Reference the server output resolution branch and the
resolvedServerOutput assignment when making the fix.

In `@src/route-export-pruning.ts`:
- Around line 544-549: The re-export cleanup in the export pruning flow is
incorrectly treating sourced re-export names as local bindings. In the logic
around the export specifier handling in route-export-pruning and the sets
removedExportLocalNames / removedExportReferencedNames, only add names that are
actually declared locally in the file; for `export { loader as clientLoader }
from "./x"`, avoid recording `specifier.local.name` as a local binding so later
assignment cleanup does not delete unrelated `loader.*` statements. Use the
existing export-specifier and assignment-pruning checks to distinguish sourced
re-exports from true local exports before adding names to those tracking sets.

In `@src/typegen.ts`:
- Around line 12-19: The typegen launcher in typegen.ts should use the project’s
locally installed react-router CLI instead of npx --yes, so update the execa
invocation to call the pinned binary directly from the app dependencies. In the
same typegen/watch flow, avoid swallowing every rejected process exit: adjust
the process handling around the execa call so only intentional shutdown is
ignored, and unexpected failures from the watch process are surfaced or
restarted/logged. Use the existing typegen process setup and its process
variable as the place to make these changes.

In `@tests/index.test.ts`:
- Around line 106-139: Restore the shared fs.existsSync mock in this test by
capturing the previous mock implementation before overriding it and reinstating
that exact implementation in the finally block. Update the test around
createStubRsbuild and pluginReactRouter so the temporary behavior used for the
watchFiles assertion does not leak into later tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5108dcda-f5f7-41c8-9159-04e0bd83d220

📥 Commits

Reviewing files that changed from the base of the PR and between 06ae3db and b1095e4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (124)
  • .changeset/bright-routes-run.md
  • .changeset/coherent-dev-generations.md
  • .changeset/lazy-entry-hydration.md
  • .changeset/quiet-topology-order.md
  • .changeset/sharp-routes-heal.md
  • .changeset/warm-watchers-rest.md
  • README.md
  • benchmarks/README.md
  • config/package.json
  • config/rslib.config.ts
  • docs/coherent-dev-generations.md
  • examples/client-only/package.json
  • examples/cloudflare/package.json
  • examples/custom-node-server/app/load-context.d.ts
  • examples/custom-node-server/app/routes/projects/edit.tsx
  • examples/custom-node-server/app/routes/projects/index.tsx
  • examples/custom-node-server/app/routes/projects/layout.tsx
  • examples/custom-node-server/app/routes/projects/project.tsx
  • examples/custom-node-server/app/routes/projects/settings.tsx
  • examples/custom-node-server/package.json
  • examples/custom-node-server/playwright.config.ts
  • examples/custom-node-server/rsbuild.config.ts
  • examples/custom-node-server/scripts/smoke-production.mjs
  • examples/custom-node-server/server.js
  • examples/custom-node-server/server/index.ts
  • examples/custom-node-server/tsconfig.json
  • examples/default-template/app/dev-routes.ts
  • examples/default-template/app/routes.ts
  • examples/default-template/package.json
  • examples/default-template/playwright.config.ts
  • examples/default-template/rsbuild.config.ts
  • examples/default-template/tests/e2e/dev-route-watch.test.ts
  • examples/default-template/tests/e2e/lazy-compilation.test.ts
  • examples/epic-stack/package.json
  • examples/federation/epic-stack-remote/package.json
  • examples/federation/epic-stack/package.json
  • examples/prerender/package.json
  • examples/spa-mode/package.json
  • package.json
  • rslib.config.ts
  • scripts/bench-builds.mjs
  • scripts/benchmark/fixture.mjs
  • scripts/compare-benchmarks.mjs
  • scripts/test-package-interop.mjs
  • src/babel.ts
  • src/bounded-cache.ts
  • src/build-manifest.ts
  • src/build-output-transforms.ts
  • src/concurrency.ts
  • src/constants.ts
  • src/dev-generation.ts
  • src/dev-runtime-artifacts.ts
  • src/dev-runtime-compilation.ts
  • src/dev-runtime-controller.ts
  • src/dev-runtime-session.ts
  • src/dev-server.ts
  • src/export-utils.ts
  • src/index.ts
  • src/lazy-compilation.ts
  • src/manifest.ts
  • src/modify-browser-manifest.ts
  • src/parallel-route-transform-protocol.ts
  • src/parallel-route-transform-worker.ts
  • src/parallel-route-transforms.ts
  • src/performance.ts
  • src/plugin-utils.ts
  • src/prerender-build.ts
  • src/prerender.ts
  • src/react-router-config.ts
  • src/route-artifacts.ts
  • src/route-ast.ts
  • src/route-chunks.ts
  • src/route-component-transform.ts
  • src/route-export-pruning.ts
  • src/route-export-resolution.ts
  • src/route-transform-tasks.ts
  • src/route-watch.ts
  • src/server-build-plan.ts
  • src/server-utils.ts
  • src/templates/entry.server.tsx
  • src/typegen.ts
  • src/types.ts
  • src/virtual-modules.ts
  • src/warnings/warn-on-client-source-maps.ts
  • src/yuku.ts
  • tests/benchmark-fixture.test.ts
  • tests/bounded-cache.test.ts
  • tests/build-manifest.test.ts
  • tests/build-output-transforms.test.ts
  • tests/client-modules.test.ts
  • tests/dev-generation-multi-entry.test.ts
  • tests/dev-generation.test.ts
  • tests/dev-runtime-controller.test.ts
  • tests/dev-runtime.integration.test.ts
  • tests/dev-server.test.ts
  • tests/export-utils.test.ts
  • tests/features.test.ts
  • tests/fixtures/dev-runtime/app/entry.client.tsx
  • tests/fixtures/dev-runtime/app/entry.server.tsx
  • tests/fixtures/dev-runtime/app/root.tsx
  • tests/fixtures/dev-runtime/app/routes.ts
  • tests/fixtures/dev-runtime/app/routes/index.tsx
  • tests/fixtures/dev-runtime/app/routes/other.tsx
  • tests/fixtures/dev-runtime/react-router.config.ts
  • tests/fixtures/dev-runtime/server/index.ts
  • tests/index.test.ts
  • tests/lazy-compilation.test.ts
  • tests/manifest-split-route-modules.test.ts
  • tests/manifest.test.ts
  • tests/modify-browser-manifest.test.ts
  • tests/parallel-route-transforms.test.ts
  • tests/performance.test.ts
  • tests/plugin-utils.test.ts
  • tests/prerender.test.ts
  • tests/react-router-config.test.ts
  • tests/remove-exports.test.ts
  • tests/route-artifacts.test.ts
  • tests/route-chunks-cache.test.ts
  • tests/route-chunks.test.ts
  • tests/route-watch.test.ts
  • tests/server-build-plan.test.ts
  • tests/server-utils.test.ts
  • tests/setup.ts
  • tests/warn-on-client-source-maps.test.ts
💤 Files with no reviewable changes (2)
  • examples/custom-node-server/server/index.ts
  • src/babel.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 11

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (10)
examples/custom-node-server/scripts/smoke-production.mjs-64-70 (1)

64-70: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Avoid the fixed 5s teardown delay after early exits.

If the child has already exited before this block runs, once(child, 'exit') subscribes too late and the script always waits the full timeout. Capture the exit promise before signaling, and skip the wait when exitCode is already set.

Proposed fix
 } finally {
-  child.kill('SIGTERM');
-  await Promise.race([
-    once(child, 'exit'),
-    new Promise(resolve => setTimeout(resolve, 5_000)),
-  ]);
-  if (child.exitCode === null) {
-    child.kill('SIGKILL');
-  }
+  if (child.exitCode === null) {
+    const exited = once(child, 'exit');
+    child.kill('SIGTERM');
+    await Promise.race([
+      exited,
+      new Promise(resolve => setTimeout(resolve, 5_000)),
+    ]);
+    if (child.exitCode === null) {
+      child.kill('SIGKILL');
+    }
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/custom-node-server/scripts/smoke-production.mjs` around lines 64 -
70, The teardown in smoke-production.mjs waits on once(child, 'exit') only after
sending SIGTERM, which can miss an exit that already happened and force the full
timeout. Update the child process handling around the existing child.kill and
Promise.race flow by creating the exit promise before signaling, and guard the
wait with child.exitCode so you skip the timeout path when the process has
already exited.
README.md-107-112 (1)

107-112: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document one parallelTransforms default.

Line 110 says @default true, but the defaults section on Lines 309-314 says the option defaults to undefined with worker execution enabled implicitly. Pick one representation so the public API contract is not self-contradictory.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 107 - 112, The README documentation for
parallelTransforms is contradictory about its default value. Update the public
API docs so the JSDoc on parallelTransforms and the defaults section describe
the same behavior, using one consistent default representation in both places.
Refer to parallelTransforms in the options docs and the defaults section, and
make sure the wording matches whether worker execution is enabled implicitly or
via an explicit true default.
README.md-527-527 (1)

527-527: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Exit on unrecoverable startup failure.

Line 527 only logs boot errors, but the section above explicitly requires a clean process restart after startup failure. Exit non-zero after logging so the example does not stay alive in a broken state.

Proposed fix
-startServer().catch(console.error);
+startServer().catch(error => {
+  console.error(error);
+  process.exit(1);
+});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 527, The startup example in startServer() currently only
logs failures with console.error and leaves the process running, which conflicts
with the documented requirement for unrecoverable startup failures. Update the
startup handling so the catch path for startServer() both logs the error and
terminates the process with a non-zero exit code, keeping the example from
continuing in a broken state.
scripts/benchmark/fixture.mjs-648-658 (1)

648-658: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fail fast on invalid largeConfig counts.

The new largeConfig override path is unvalidated, but several generators later use counts like workers, localeFiles, cssModules, svgAssets, utilitiesPerRoute, and payloadEntriesPerComponent in %/division and import-path generation. Passing 0 currently emits broken fixture code instead of rejecting the config up front.

🛠️ Proposed fix
+const assertPositiveInteger = (name, value) => {
+  if (!Number.isInteger(value) || value < 1) {
+    throw new Error(
+      `${name} must be a positive integer for the large benchmark fixture.`
+    );
+  }
+};
+
 const generateLargeFixture = async ({
   root,
   routeCount,
   variant,
@@
 }) => {
   const config = normalizeLargeConfig(routeCount, largeConfig);
+  for (const [name, value] of Object.entries({
+    routes: config.routes,
+    componentsPerRoute: config.componentsPerRoute,
+    utilitiesPerRoute: config.utilitiesPerRoute,
+    lazyModulesPerRoute: config.lazyModulesPerRoute,
+    workers: config.workers,
+    svgAssets: config.svgAssets,
+    cssModules: config.cssModules,
+    localeFiles: config.localeFiles,
+    payloadEntriesPerComponent: config.payloadEntriesPerComponent,
+  })) {
+    assertPositiveInteger(name, value);
+  }
   const isSpa = variant === 'spa';

Also applies to: 814-834

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/benchmark/fixture.mjs` around lines 648 - 658, The largeConfig
override path in generateLargeFixture/normalizeLargeConfig accepts invalid zero
values, which later break generators that use workers, localeFiles, cssModules,
svgAssets, utilitiesPerRoute, and payloadEntriesPerComponent in division,
modulo, or import-path logic. Add upfront validation for these count fields in
normalizeLargeConfig (or immediately after calling it in generateLargeFixture)
and reject non-positive values with a clear error before any fixture generation
starts.
rslib.config.ts-23-28 (1)

23-28: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Emit the route transform worker from the CJS build too.

createWorkerUrl() always resolves ./parallel-route-transform-worker.js, but the CJS source.entry block doesn’t include that file. CJS consumers won’t get the worker asset, so parallel transforms will fall back to the serial path.

Proposed fix
       source: {
         entry: {
           index: './src/index.ts',
+          'parallel-route-transform-worker':
+            './src/parallel-route-transform-worker.ts',
           'templates/entry.server': './src/templates/entry.server.tsx',
           'templates/entry.client': './src/templates/entry.client.tsx',
         },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rslib.config.ts` around lines 23 - 28, The CJS source.entry map is missing
the route transform worker entry, so CJS builds won’t emit the asset that
createWorkerUrl() resolves. Update the source.entry block in rslib.config.ts to
include the parallel route transform worker alongside the existing index,
templates/entry.server, and templates/entry.client entries, so CJS consumers
also receive parallel-route-transform-worker.js.
src/route-export-pruning.ts-544-549 (1)

544-549: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Don’t track sourced re-export names as local bindings.

For export { loader as clientLoader } from "./x", specifier.local.name is the imported name from the source module, not a local binding. Adding it to removedExportLocalNames can make the later assignment cleanup remove unrelated local loader.* = ... statements.

Proposed fix
             const exportedName = getExportedName(specifier);
             if (exportedName && exportsToRemoveSet.has(exportedName)) {
               exportsChanged = true;
-              if (specifier.local?.name) {
+              if (!statement.source && specifier.local?.name) {
                 removedExportLocalNames.add(specifier.local.name);
                 removedExportReferencedNames.add(specifier.local.name);
               }
               return false;
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/route-export-pruning.ts` around lines 544 - 549, The re-export cleanup in
the export pruning flow is incorrectly treating sourced re-export names as local
bindings. In the logic around the export specifier handling in
route-export-pruning and the sets removedExportLocalNames /
removedExportReferencedNames, only add names that are actually declared locally
in the file; for `export { loader as clientLoader } from "./x"`, avoid recording
`specifier.local.name` as a local binding so later assignment cleanup does not
delete unrelated `loader.*` statements. Use the existing export-specifier and
assignment-pruning checks to distinguish sourced re-exports from true local
exports before adding names to those tracking sets.
tests/index.test.ts-106-139 (1)

106-139: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Restore the previous existsSync mock implementation.

Line 111 overrides a shared mock, but Line 138 leaves it hardcoded to true. Capture and restore the previous implementation so this test cannot leak filesystem behavior into later cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/index.test.ts` around lines 106 - 139, Restore the shared fs.existsSync
mock in this test by capturing the previous mock implementation before
overriding it and reinstating that exact implementation in the finally block.
Update the test around createStubRsbuild and pluginReactRouter so the temporary
behavior used for the watchFiles assertion does not leak into later tests.
src/index.ts-231-239 (1)

231-239: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat serverOutput: undefined as omitted.

hasOwnProperty makes { serverOutput: undefined } explicit, so resolvedServerOutput can become undefined and flow into the node Rspack config.

Proposed fix
-    const hasExplicitServerOutput = Object.prototype.hasOwnProperty.call(
-      options,
-      'serverOutput'
-    );
-    let resolvedServerOutput = pluginOptions.serverOutput;
-    if (!hasExplicitServerOutput) {
-      resolvedServerOutput =
-        serverModuleFormat === 'cjs' ? 'commonjs' : 'module';
-    }
+    const hasExplicitServerOutput = options.serverOutput !== undefined;
+    const resolvedServerOutput = hasExplicitServerOutput
+      ? options.serverOutput
+      : serverModuleFormat === 'cjs'
+        ? 'commonjs'
+        : 'module';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.ts` around lines 231 - 239, Treat serverOutput: undefined the same
as an omitted option in the server output resolution logic. Update the
hasExplicitServerOutput check in the src/index.ts flow around
pluginOptions.serverOutput so that undefined does not count as explicitly set,
and keep the fallback based on serverModuleFormat ('cjs' => commonjs, otherwise
module) from flowing into the Node Rspack config. Reference the server output
resolution branch and the resolvedServerOutput assignment when making the fix.
src/dev-generation.ts-551-553 (1)

551-553: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep load() asynchronous in the ready path.

selectBuild() can throw here, which escapes synchronously instead of rejecting the promised API. That makes loadReactRouterServerBuild() behave differently between starting and ready states for the same missing-build failure.

Proposed fix
       if (state.kind === 'ready') {
-        return Promise.resolve(selectBuild(state.committed, entryName));
+        return Promise.resolve().then(() =>
+          selectBuild(state.committed, entryName)
+        );
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dev-generation.ts` around lines 551 - 553, The `load()` implementation in
`src/dev-generation.ts` should stay asynchronous in the `state.kind === 'ready'`
branch, because calling `selectBuild()` directly can throw synchronously instead
of returning a rejected promise. Update the ready-path logic around
`selectBuild()` and `loadReactRouterServerBuild()` so missing-build failures are
handled through promise rejection consistently with the `starting` state.
src/typegen.ts-12-19 (1)

12-19: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Use the local react-router CLI and keep watch failures visible.

  • npx --yes can fall back to a registry install when the binary is missing; run the app’s installed CLI directly so typegen stays on the pinned version.
  • The watch process currently swallows every rejection. Only ignore shutdown, or restart/log unexpected exits so typegen doesn’t die silently.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/typegen.ts` around lines 12 - 19, The typegen launcher in typegen.ts
should use the project’s locally installed react-router CLI instead of npx
--yes, so update the execa invocation to call the pinned binary directly from
the app dependencies. In the same typegen/watch flow, avoid swallowing every
rejected process exit: adjust the process handling around the execa call so only
intentional shutdown is ignored, and unexpected failures from the watch process
are surfaced or restarted/logged. Use the existing typegen process setup and its
process variable as the place to make these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/build-output-transforms.ts`:
- Around line 123-136: The fallback in build-output-transforms should stay
bundle-scoped even when latestServerManifestsByBundleId is not yet populated. In
the manifest selection logic around getLatestServerManifest,
getLatestServerManifestByBundleId, and getReactRouterManifestForDev, ensure the
cold-path dev manifest returned for
virtual/react-router/server-manifest-${bundleId} is filtered to the current
bundleId before it is used. Keep the per-bundle behavior consistent with the
virtual module wiring from src/index.ts so the fallback never embeds routes from
other bundles.

In `@src/dev-runtime-artifacts.ts`:
- Around line 145-174: The assertBuildMatchesManifest check only verifies routes
present in ReactRouterDevManifest, so stale ServerBuild entries for deleted
routes can slip through. Update assertBuildMatchesManifest to also compare
build.routes against manifest.routes and throw when the evaluated server build
contains any extra route IDs not in the manifest, alongside the existing
missing-route and loader/action export checks. Use the existing entryName,
build, manifest, and routeId logic to keep the validation in one place.

In `@src/dev-runtime-session.ts`:
- Around line 62-69: The completeClose flow in dev-runtime-session skips
closeBinding() when the session is already in the closing state, so successful
server.close() calls never fully clean up the runtime. Update completeClose to
invoke closeBinding(binding) for the successful closing path as well as active,
while still guarding with isCurrentBinding(binding) and preserving the
transition to idle; apply the same fix in the other matching close path around
the later close handler.

In `@src/prerender-build.ts`:
- Around line 175-177: The prerender write path logic in the output path
construction must prevent traversal outside clientBuildDir. Update the path
handling in prerender-build.ts around the outputPath/mkdir/writeFile flow to
validate normalizedPath segments before joining, reject any .. or
backslash-based traversal, and assert the final resolved path stays under
clientBuildDir. Apply the same safeguard to the other prerender output write
sites mentioned by the build flow helpers so all generated files remain confined
to the client build directory.
- Around line 496-498: The cleanup in enqueue path handling creates an
unconsumed promise from task.finally, which can surface prerender failures as
unhandled rejections; update the logic around enqueue, pending, and task so the
cleanup promise is handled explicitly. Use the task promise’s then with both
fulfillment and rejection handlers, or attach a catch to the promise returned by
finally, while still removing the task from pending.
- Around line 549-607: The prerender/build-request flow in prerender-build.ts
leaves process.env.IS_RR_BUILD_REQUEST mutated after the work completes. In the
block around resolveServerBuildModule, createRequestHandler, runPrerenderPaths,
and handleSpaMode, save the previous IS_RR_BUILD_REQUEST value before setting it
and restore it in a finally block after all prerender/Spa handling finishes.
This should be done around the existing build request logic so buildEnd and
subsequent runs/tests see the original process-global state.
- Around line 211-225: Escape the redirect target before interpolating it into
the fallback HTML in the redirect handling block so untrusted `Location` values
cannot inject markup or attributes. Update the logic around
`redirectStatusCodes`, `response.headers.get('Location')`, and the HTML template
in `src/prerender-build.ts` to use a safely escaped/sanitized version for the
`<title>`, `meta refresh`, and `<a href>`/link text. Keep `normalizedPath`
as-is, but ensure every use of the redirect destination is HTML- and
attribute-safe.

In `@src/route-chunks.ts`:
- Around line 211-216: The shared side-effect collection in route chunking is
incorrectly treating directive prologues like "use client" and "use strict" as
blocking statements. Update the filtering logic in the shared side-effect scan
and the related `topLevelNonModuleStatements` handling so `ExpressionStatement`s
that are directive prologues are excluded, while real side-effectful expressions
remain included. Use the existing `sharedTopLevelSideEffects` and export
analysis paths to ensure `splitRouteModules: "enforce"` only rejects genuinely
shared side effects.

In `@src/route-export-resolution.ts`:
- Around line 160-182: Update resolvePackageExports and related callers so
subpath imports are handled correctly: when packageJson.exports is absent, do
not always fall back to module/main for every packageSubpath, and make sure
pkg/subpath is not resolved as the package entry. In the exports branch, enhance
the lookup logic in resolvePackageExports to support non-exact export shapes
such as pattern mappings like "./*" instead of only direct packageSubpath keys.
Then adjust resolveExportAllModule so a null result from resolvePackageExports
is treated as “not matched yet” and it can still fall back to
createRequire(...).resolve() before rejecting the import.

In `@src/typegen.ts`:
- Around line 22-30: The `typegen --watch` promise handling in `typegenProcess`
is swallowing every rejection, which hides unexpected watcher exits. Update the
`.catch` path so it only ignores the intentional shutdown case from server
teardown, and for any other rejection in `src/typegen.ts` surface the failure by
logging or rethrowing it. Use the existing `typegenProcess` cleanup in
`.finally()` to keep the process reset behavior intact while making crashes or
invalid config failures visible.

In `@src/virtual-modules.ts`:
- Around line 11-19: The virtual module ID validation in the module path builder
does not block backslash separators, so a Windows-style traversal pattern can
slip through. Update the validation in the relativeId/segments check to reject
any segment containing backslashes before returning the node_modules path,
keeping the existing dot-segment and empty-segment checks in place.

---

Minor comments:
In `@examples/custom-node-server/scripts/smoke-production.mjs`:
- Around line 64-70: The teardown in smoke-production.mjs waits on once(child,
'exit') only after sending SIGTERM, which can miss an exit that already happened
and force the full timeout. Update the child process handling around the
existing child.kill and Promise.race flow by creating the exit promise before
signaling, and guard the wait with child.exitCode so you skip the timeout path
when the process has already exited.

In `@README.md`:
- Around line 107-112: The README documentation for parallelTransforms is
contradictory about its default value. Update the public API docs so the JSDoc
on parallelTransforms and the defaults section describe the same behavior, using
one consistent default representation in both places. Refer to
parallelTransforms in the options docs and the defaults section, and make sure
the wording matches whether worker execution is enabled implicitly or via an
explicit true default.
- Line 527: The startup example in startServer() currently only logs failures
with console.error and leaves the process running, which conflicts with the
documented requirement for unrecoverable startup failures. Update the startup
handling so the catch path for startServer() both logs the error and terminates
the process with a non-zero exit code, keeping the example from continuing in a
broken state.

In `@rslib.config.ts`:
- Around line 23-28: The CJS source.entry map is missing the route transform
worker entry, so CJS builds won’t emit the asset that createWorkerUrl()
resolves. Update the source.entry block in rslib.config.ts to include the
parallel route transform worker alongside the existing index,
templates/entry.server, and templates/entry.client entries, so CJS consumers
also receive parallel-route-transform-worker.js.

In `@scripts/benchmark/fixture.mjs`:
- Around line 648-658: The largeConfig override path in
generateLargeFixture/normalizeLargeConfig accepts invalid zero values, which
later break generators that use workers, localeFiles, cssModules, svgAssets,
utilitiesPerRoute, and payloadEntriesPerComponent in division, modulo, or
import-path logic. Add upfront validation for these count fields in
normalizeLargeConfig (or immediately after calling it in generateLargeFixture)
and reject non-positive values with a clear error before any fixture generation
starts.

In `@src/dev-generation.ts`:
- Around line 551-553: The `load()` implementation in `src/dev-generation.ts`
should stay asynchronous in the `state.kind === 'ready'` branch, because calling
`selectBuild()` directly can throw synchronously instead of returning a rejected
promise. Update the ready-path logic around `selectBuild()` and
`loadReactRouterServerBuild()` so missing-build failures are handled through
promise rejection consistently with the `starting` state.

In `@src/index.ts`:
- Around line 231-239: Treat serverOutput: undefined the same as an omitted
option in the server output resolution logic. Update the hasExplicitServerOutput
check in the src/index.ts flow around pluginOptions.serverOutput so that
undefined does not count as explicitly set, and keep the fallback based on
serverModuleFormat ('cjs' => commonjs, otherwise module) from flowing into the
Node Rspack config. Reference the server output resolution branch and the
resolvedServerOutput assignment when making the fix.

In `@src/route-export-pruning.ts`:
- Around line 544-549: The re-export cleanup in the export pruning flow is
incorrectly treating sourced re-export names as local bindings. In the logic
around the export specifier handling in route-export-pruning and the sets
removedExportLocalNames / removedExportReferencedNames, only add names that are
actually declared locally in the file; for `export { loader as clientLoader }
from "./x"`, avoid recording `specifier.local.name` as a local binding so later
assignment cleanup does not delete unrelated `loader.*` statements. Use the
existing export-specifier and assignment-pruning checks to distinguish sourced
re-exports from true local exports before adding names to those tracking sets.

In `@src/typegen.ts`:
- Around line 12-19: The typegen launcher in typegen.ts should use the project’s
locally installed react-router CLI instead of npx --yes, so update the execa
invocation to call the pinned binary directly from the app dependencies. In the
same typegen/watch flow, avoid swallowing every rejected process exit: adjust
the process handling around the execa call so only intentional shutdown is
ignored, and unexpected failures from the watch process are surfaced or
restarted/logged. Use the existing typegen process setup and its process
variable as the place to make these changes.

In `@tests/index.test.ts`:
- Around line 106-139: Restore the shared fs.existsSync mock in this test by
capturing the previous mock implementation before overriding it and reinstating
that exact implementation in the finally block. Update the test around
createStubRsbuild and pluginReactRouter so the temporary behavior used for the
watchFiles assertion does not leak into later tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5108dcda-f5f7-41c8-9159-04e0bd83d220

📥 Commits

Reviewing files that changed from the base of the PR and between 06ae3db and b1095e4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (124)
  • .changeset/bright-routes-run.md
  • .changeset/coherent-dev-generations.md
  • .changeset/lazy-entry-hydration.md
  • .changeset/quiet-topology-order.md
  • .changeset/sharp-routes-heal.md
  • .changeset/warm-watchers-rest.md
  • README.md
  • benchmarks/README.md
  • config/package.json
  • config/rslib.config.ts
  • docs/coherent-dev-generations.md
  • examples/client-only/package.json
  • examples/cloudflare/package.json
  • examples/custom-node-server/app/load-context.d.ts
  • examples/custom-node-server/app/routes/projects/edit.tsx
  • examples/custom-node-server/app/routes/projects/index.tsx
  • examples/custom-node-server/app/routes/projects/layout.tsx
  • examples/custom-node-server/app/routes/projects/project.tsx
  • examples/custom-node-server/app/routes/projects/settings.tsx
  • examples/custom-node-server/package.json
  • examples/custom-node-server/playwright.config.ts
  • examples/custom-node-server/rsbuild.config.ts
  • examples/custom-node-server/scripts/smoke-production.mjs
  • examples/custom-node-server/server.js
  • examples/custom-node-server/server/index.ts
  • examples/custom-node-server/tsconfig.json
  • examples/default-template/app/dev-routes.ts
  • examples/default-template/app/routes.ts
  • examples/default-template/package.json
  • examples/default-template/playwright.config.ts
  • examples/default-template/rsbuild.config.ts
  • examples/default-template/tests/e2e/dev-route-watch.test.ts
  • examples/default-template/tests/e2e/lazy-compilation.test.ts
  • examples/epic-stack/package.json
  • examples/federation/epic-stack-remote/package.json
  • examples/federation/epic-stack/package.json
  • examples/prerender/package.json
  • examples/spa-mode/package.json
  • package.json
  • rslib.config.ts
  • scripts/bench-builds.mjs
  • scripts/benchmark/fixture.mjs
  • scripts/compare-benchmarks.mjs
  • scripts/test-package-interop.mjs
  • src/babel.ts
  • src/bounded-cache.ts
  • src/build-manifest.ts
  • src/build-output-transforms.ts
  • src/concurrency.ts
  • src/constants.ts
  • src/dev-generation.ts
  • src/dev-runtime-artifacts.ts
  • src/dev-runtime-compilation.ts
  • src/dev-runtime-controller.ts
  • src/dev-runtime-session.ts
  • src/dev-server.ts
  • src/export-utils.ts
  • src/index.ts
  • src/lazy-compilation.ts
  • src/manifest.ts
  • src/modify-browser-manifest.ts
  • src/parallel-route-transform-protocol.ts
  • src/parallel-route-transform-worker.ts
  • src/parallel-route-transforms.ts
  • src/performance.ts
  • src/plugin-utils.ts
  • src/prerender-build.ts
  • src/prerender.ts
  • src/react-router-config.ts
  • src/route-artifacts.ts
  • src/route-ast.ts
  • src/route-chunks.ts
  • src/route-component-transform.ts
  • src/route-export-pruning.ts
  • src/route-export-resolution.ts
  • src/route-transform-tasks.ts
  • src/route-watch.ts
  • src/server-build-plan.ts
  • src/server-utils.ts
  • src/templates/entry.server.tsx
  • src/typegen.ts
  • src/types.ts
  • src/virtual-modules.ts
  • src/warnings/warn-on-client-source-maps.ts
  • src/yuku.ts
  • tests/benchmark-fixture.test.ts
  • tests/bounded-cache.test.ts
  • tests/build-manifest.test.ts
  • tests/build-output-transforms.test.ts
  • tests/client-modules.test.ts
  • tests/dev-generation-multi-entry.test.ts
  • tests/dev-generation.test.ts
  • tests/dev-runtime-controller.test.ts
  • tests/dev-runtime.integration.test.ts
  • tests/dev-server.test.ts
  • tests/export-utils.test.ts
  • tests/features.test.ts
  • tests/fixtures/dev-runtime/app/entry.client.tsx
  • tests/fixtures/dev-runtime/app/entry.server.tsx
  • tests/fixtures/dev-runtime/app/root.tsx
  • tests/fixtures/dev-runtime/app/routes.ts
  • tests/fixtures/dev-runtime/app/routes/index.tsx
  • tests/fixtures/dev-runtime/app/routes/other.tsx
  • tests/fixtures/dev-runtime/react-router.config.ts
  • tests/fixtures/dev-runtime/server/index.ts
  • tests/index.test.ts
  • tests/lazy-compilation.test.ts
  • tests/manifest-split-route-modules.test.ts
  • tests/manifest.test.ts
  • tests/modify-browser-manifest.test.ts
  • tests/parallel-route-transforms.test.ts
  • tests/performance.test.ts
  • tests/plugin-utils.test.ts
  • tests/prerender.test.ts
  • tests/react-router-config.test.ts
  • tests/remove-exports.test.ts
  • tests/route-artifacts.test.ts
  • tests/route-chunks-cache.test.ts
  • tests/route-chunks.test.ts
  • tests/route-watch.test.ts
  • tests/server-build-plan.test.ts
  • tests/server-utils.test.ts
  • tests/setup.ts
  • tests/warn-on-client-source-maps.test.ts
💤 Files with no reviewable changes (2)
  • examples/custom-node-server/server/index.ts
  • src/babel.ts
🛑 Comments failed to post (11)
src/build-output-transforms.ts (1)

123-136: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep bundle-specific server-manifest fallbacks bundle-scoped.

When this transform handles virtual/react-router/server-manifest-${bundleId} before stageLatestManifests() has populated latestServerManifestsByBundleId, Lines 123-136 fall back to getReactRouterManifestForDev(routes, ...), which regenerates the full manifest. In src/index.ts, those virtual modules are wired into per-bundle server builds and later filtered by bundle, so a node-first dev compile can embed routes outside the current bundle until a rebuild corrects it. Please filter the cold-path fallback by bundleId too, not just the cached path.

Possible direction
-          const manifest =
+          const baseManifest =
             (latestServerManifest
               ? ((bundleId && getLatestServerManifestByBundleId(bundleId)) ??
                 latestServerManifest)
               : null) ??
             (await getReactRouterManifestForDev(
               routes,
               pluginOptions,
               getClientStats(),
               appDirectory,
               getAssetPrefix(),
               routeChunkOptions
             ));
+          const manifest =
+            bundleId && routesByServerBundleId[bundleId]
+              ? {
+                  ...baseManifest,
+                  routes: Object.fromEntries(
+                    Object.entries(baseManifest.routes).filter(([routeId]) =>
+                      routeId in routesByServerBundleId[bundleId]
+                    )
+                  ),
+                }
+              : baseManifest;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/build-output-transforms.ts` around lines 123 - 136, The fallback in
build-output-transforms should stay bundle-scoped even when
latestServerManifestsByBundleId is not yet populated. In the manifest selection
logic around getLatestServerManifest, getLatestServerManifestByBundleId, and
getReactRouterManifestForDev, ensure the cold-path dev manifest returned for
virtual/react-router/server-manifest-${bundleId} is filtered to the current
bundleId before it is used. Keep the per-bundle behavior consistent with the
virtual module wiring from src/index.ts so the fallback never embeds routes from
other bundles.
src/dev-runtime-artifacts.ts (1)

145-174: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Also reject server builds with extra route IDs.

This check only walks manifest.routes. If the node build is stale and still contains a deleted route, validation passes and the mixed pair gets pinned anyway. Route removals are one of the mismatch cases this flow needs to catch.

Suggested fix
 const assertBuildMatchesManifest = (
   entryName: string,
   build: ServerBuild,
   manifest: ReactRouterDevManifest
 ): void => {
+  const manifestRouteIds = new Set(Object.keys(manifest.routes));
+  for (const routeId of Object.keys(build.routes)) {
+    if (!manifestRouteIds.has(routeId)) {
+      throw new Error(
+        `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} does not exist in the matching web manifest.`
+      );
+    }
+  }
+
   for (const [routeId, manifestRoute] of Object.entries(manifest.routes)) {
     const routeModule = build.routes[routeId]?.module;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

const assertBuildMatchesManifest = (
  entryName: string,
  build: ServerBuild,
  manifest: ReactRouterDevManifest
): void => {
  const manifestRouteIds = new Set(Object.keys(manifest.routes));
  for (const routeId of Object.keys(build.routes)) {
    if (!manifestRouteIds.has(routeId)) {
      throw new Error(
        `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} does not exist in the matching web manifest.`
      );
    }
  }

  for (const [routeId, manifestRoute] of Object.entries(manifest.routes)) {
    const routeModule = build.routes[routeId]?.module;
    if (!routeModule) {
      throw new Error(
        `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} is missing from the evaluated server build.`
      );
    }
    if (
      Boolean(manifestRoute.hasLoader) !==
      (typeof routeModule.loader === 'function')
    ) {
      throw new Error(
        `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} loader export does not match its web manifest.`
      );
    }
    if (
      Boolean(manifestRoute.hasAction) !==
      (typeof routeModule.action === 'function')
    ) {
      throw new Error(
        `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} action export does not match its web manifest.`
      );
    }
  }
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dev-runtime-artifacts.ts` around lines 145 - 174, The
assertBuildMatchesManifest check only verifies routes present in
ReactRouterDevManifest, so stale ServerBuild entries for deleted routes can slip
through. Update assertBuildMatchesManifest to also compare build.routes against
manifest.routes and throw when the evaluated server build contains any extra
route IDs not in the manifest, alongside the existing missing-route and
loader/action export checks. Use the existing entryName, build, manifest, and
routeId logic to keep the validation in one place.
src/dev-runtime-session.ts (1)

62-69: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Run closeBinding() after successful closes from the closing state.

Once markClosing() switches the session to closing, Line 66 stops invoking closeBinding(). A normal server.close() then moves to idle without runtime.close(), unregistering the dev runtime, or clearing compiler identity state.

Suggested fix
   const completeClose = (binding: RuntimeBinding): void => {
     if (!isCurrentBinding(binding)) {
       return;
     }
-    if (state.status === 'active') {
+    if (state.status === 'active' || state.status === 'closing') {
       closeBinding(binding);
     }
     state = { status: 'idle' };
   };

Also applies to: 170-173

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dev-runtime-session.ts` around lines 62 - 69, The completeClose flow in
dev-runtime-session skips closeBinding() when the session is already in the
closing state, so successful server.close() calls never fully clean up the
runtime. Update completeClose to invoke closeBinding(binding) for the successful
closing path as well as active, while still guarding with
isCurrentBinding(binding) and preserving the transition to idle; apply the same
fix in the other matching close path around the later close handler.
src/prerender-build.ts (4)

175-177: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep prerender output paths inside clientBuildDir.

normalizedPath.split('/') can include .. or backslash segments from configured/generated prerender paths, letting dynamic/splat route paths write outside the client build directory. Validate segments and assert the resolved path remains under clientBuildDir.

🛡️ Proposed fix
 const createDataRequestPath = (
   prerenderPath: string,
   trailingSlashAwareDataRequests: boolean
 ): string => {
@@
     : `${prerenderPath.replace(/\/$/, '')}.data`;
 };
+
+const getClientBuildOutputPath = (
+  clientBuildDir: string,
+  normalizedPath: string,
+  ...extraSegments: string[]
+): string => {
+  const segments = normalizedPath.split('/').filter(Boolean);
+  if (
+    segments.some(
+      segment =>
+        segment === '.' || segment === '..' || segment.includes('\\')
+    )
+  ) {
+    throw new Error(`Invalid prerender output path: ${normalizedPath}`);
+  }
+
+  const outputPath = resolve(clientBuildDir, ...segments, ...extraSegments);
+  const relativeOutputPath = relative(clientBuildDir, outputPath);
+  if (relativeOutputPath === '..' || relativeOutputPath.startsWith('../')) {
+    throw new Error(`Invalid prerender output path: ${normalizedPath}`);
+  }
+  return outputPath;
+};
@@
-    const outputPath = resolve(clientBuildDir, ...normalizedPath.split('/'));
+    const outputPath = getClientBuildOutputPath(clientBuildDir, normalizedPath);
@@
-      const outputPath = resolve(
-        clientBuildDir,
-        ...normalizedPath.split('/'),
-        'index.html'
-      );
+      const outputPath = getClientBuildOutputPath(
+        clientBuildDir,
+        normalizedPath,
+        'index.html'
+      );
@@
-      const outputPath = resolve(clientBuildDir, ...normalizedPath.split('/'));
+      const outputPath = getClientBuildOutputPath(clientBuildDir, normalizedPath);

Also applies to: 234-240, 284-286

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/prerender-build.ts` around lines 175 - 177, The prerender write path
logic in the output path construction must prevent traversal outside
clientBuildDir. Update the path handling in prerender-build.ts around the
outputPath/mkdir/writeFile flow to validate normalizedPath segments before
joining, reject any .. or backslash-based traversal, and assert the final
resolved path stays under clientBuildDir. Apply the same safeguard to the other
prerender output write sites mentioned by the build flow helpers so all
generated files remain confined to the client build directory.

211-225: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Escape redirect Location before writing fallback HTML.

The redirect page writes Location directly into text and attributes. A route redirect with <, ", or similar characters can generate static HTML with script/markup injection.

🛡️ Proposed fix
+const escapeHtml = (value: string): string =>
+  value.replace(/[&<>"']/g, char => {
+    switch (char) {
+      case '&':
+        return '&amp;';
+      case '<':
+        return '&lt;';
+      case '>':
+        return '&gt;';
+      case '"':
+        return '&quot;';
+      default:
+        return '&`#39`;';
+    }
+  });
+
@@
       if (redirectStatusCodes.has(response.status)) {
         const location = response.headers.get('Location');
+        if (!location) {
+          throw new Error(
+            `Prerender (html): Redirect response for \`${normalizedPath}\` is missing a Location header.`
+          );
+        }
+        const escapedLocation = escapeHtml(location);
         const delay = response.status === 302 ? 2 : 0;
         html = `<!doctype html>
 <head>
-<title>Redirecting to: ${location}</title>
-<meta http-equiv="refresh" content="${delay};url=${location}">
+<title>Redirecting to: ${escapedLocation}</title>
+<meta http-equiv="refresh" content="${delay};url=${escapedLocation}">
 <meta name="robots" content="noindex">
 </head>
 <body>
-\t<a href="${location}">
-    Redirecting from <code>${normalizedPath}</code> to <code>${location}</code>
+\t<a href="${escapedLocation}">
+    Redirecting from <code>${escapeHtml(normalizedPath)}</code> to <code>${escapedLocation}</code>
   </a>
 </body>
 </html>`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

const escapeHtml = (value: string): string =>
  value.replace(/[&<>"']/g, char => {
    switch (char) {
      case '&':
        return '&amp;';
      case '<':
        return '&lt;';
      case '>':
        return '&gt;';
      case '"':
        return '&quot;';
      default:
        return '&`#39`;';
    }
  });

      if (redirectStatusCodes.has(response.status)) {
        const location = response.headers.get('Location');
        if (!location) {
          throw new Error(
            `Prerender (html): Redirect response for \`${normalizedPath}\` is missing a Location header.`
          );
        }
        const escapedLocation = escapeHtml(location);
        const delay = response.status === 302 ? 2 : 0;
        html = `<!doctype html>
<head>
<title>Redirecting to: ${escapedLocation}</title>
<meta http-equiv="refresh" content="${delay};url=${escapedLocation}">
<meta name="robots" content="noindex">
</head>
<body>
\t<a href="${escapedLocation}">
    Redirecting from <code>${escapeHtml(normalizedPath)}</code> to <code>${escapedLocation}</code>
  </a>
</body>
</html>`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/prerender-build.ts` around lines 211 - 225, Escape the redirect target
before interpolating it into the fallback HTML in the redirect handling block so
untrusted `Location` values cannot inject markup or attributes. Update the logic
around `redirectStatusCodes`, `response.headers.get('Location')`, and the HTML
template in `src/prerender-build.ts` to use a safely escaped/sanitized version
for the `<title>`, `meta refresh`, and `<a href>`/link text. Keep
`normalizedPath` as-is, but ensure every use of the redirect destination is
HTML- and attribute-safe.

496-498: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Consume the promise returned by the cleanup handler.

task.finally(...) creates a second promise that rejects when task rejects; because it is ignored, a prerender failure can also surface as an unhandled rejection. Use then with both handlers or catch the returned promise.

🛠️ Proposed fix
     const task = enqueue(path);
     pending.add(task);
-    task.finally(() => pending.delete(task));
+    void task.then(
+      () => pending.delete(task),
+      () => pending.delete(task)
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    const task = enqueue(path);
    pending.add(task);
    void task.then(
      () => pending.delete(task),
      () => pending.delete(task)
    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/prerender-build.ts` around lines 496 - 498, The cleanup in enqueue path
handling creates an unconsumed promise from task.finally, which can surface
prerender failures as unhandled rejections; update the logic around enqueue,
pending, and task so the cleanup promise is handled explicitly. Use the task
promise’s then with both fulfillment and rejection handlers, or attach a catch
to the promise returned by finally, while still removing the task from pending.

549-607: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Restore IS_RR_BUILD_REQUEST after prerendering.

This mutates process-global state and leaves it set for buildEnd, later builds, or tests in the same process. Preserve and restore the previous value in a finally block around the build-request work.

🛠️ Proposed fix
   if (!ssr || isPrerenderEnabled) {
+    const previousBuildRequest = process.env.IS_RR_BUILD_REQUEST;
     process.env.IS_RR_BUILD_REQUEST = 'yes';
-    const buildModule = await import(pathToFileURL(serverBuildPath).toString());
-    const build = (await resolveServerBuildModule(
-      buildModule,
-      `Server build ${JSON.stringify(serverBuildPath)}`
-    )) as PrerenderServerBuild;
-    const requestHandler = createRequestHandler(build, 'production');
+    try {
+      const buildModule = await import(pathToFileURL(serverBuildPath).toString());
+      const build = (await resolveServerBuildModule(
+        buildModule,
+        `Server build ${JSON.stringify(serverBuildPath)}`
+      )) as PrerenderServerBuild;
+      const requestHandler = createRequestHandler(build, 'production');
 
-    if (isPrerenderEnabled) {
+      if (isPrerenderEnabled) {
         // existing prerender logic
-    }
+      }
 
-    if (!ssr) {
+      if (!ssr) {
         await handleSpaMode({
           handler: requestHandler,
           build,
           clientBuildDir,
           basename,
           api,
         });
+      }
+    } finally {
+      if (previousBuildRequest === undefined) {
+        delete process.env.IS_RR_BUILD_REQUEST;
+      } else {
+        process.env.IS_RR_BUILD_REQUEST = previousBuildRequest;
       }
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  if (!ssr || isPrerenderEnabled) {
    const previousBuildRequest = process.env.IS_RR_BUILD_REQUEST;
    process.env.IS_RR_BUILD_REQUEST = 'yes';
    try {
      const buildModule = await import(pathToFileURL(serverBuildPath).toString());
      const build = (await resolveServerBuildModule(
        buildModule,
        `Server build ${JSON.stringify(serverBuildPath)}`
      )) as PrerenderServerBuild;
      const requestHandler = createRequestHandler(build, 'production');

      if (isPrerenderEnabled) {
        if (!ssr) {
          const generated = latestBrowserManifest
            ? {
                manifest: latestBrowserManifest,
                moduleExportsByRouteId: latestBrowserManifestModuleExports,
              }
            : await generateReactRouterManifestForDev(
                routes,
                pluginOptions,
                clientStats,
                appDirectory,
                assetPrefix,
                routeChunkOptions
              );
          assertValidSsrFalsePrerenderExports({
            routes,
            manifestRoutes: generated.manifest.routes,
            routeExports: generated.moduleExportsByRouteId,
            prerenderPaths,
            api,
          });
        }

        validatePrerenderPathMatches(routes, prerenderPaths);

        if (prerenderPaths.length > 0) {
          api.logger.info(
            `Prerender (html): ${prerenderPaths.length} path(s)...`
          );
        }

        await runPrerenderPaths({
          build,
          requestHandler,
          clientBuildDir,
          options,
        });
      }

      if (!ssr) {
        await handleSpaMode({
          handler: requestHandler,
          build,
          clientBuildDir,
          basename,
          api,
        });
      }
    } finally {
      if (previousBuildRequest === undefined) {
        delete process.env.IS_RR_BUILD_REQUEST;
      } else {
        process.env.IS_RR_BUILD_REQUEST = previousBuildRequest;
      }
    }
  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/prerender-build.ts` around lines 549 - 607, The prerender/build-request
flow in prerender-build.ts leaves process.env.IS_RR_BUILD_REQUEST mutated after
the work completes. In the block around resolveServerBuildModule,
createRequestHandler, runPrerenderPaths, and handleSpaMode, save the previous
IS_RR_BUILD_REQUEST value before setting it and restore it in a finally block
after all prerender/Spa handling finishes. This should be done around the
existing build request logic so buildEnd and subsequent runs/tests see the
original process-global state.
src/route-chunks.ts (1)

211-216: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Exclude directive prologues from shared side-effect blocking.

"use client"/"use strict" directives are ExpressionStatements, so they are added to every export’s topLevelNonModuleStatements. That makes the intersection check reject otherwise split-safe exports, and can fail splitRouteModules: "enforce" for routes that only share a directive.

Proposed fix
-      const sharedTopLevelSideEffects = (module.ast as AnyNode).body.filter(
+      const sharedTopLevelSideEffects = (module.ast as AnyNode).body.filter(
         (statement: AnyNode) =>
           (statement.type === 'ImportDeclaration' &&
             statement.specifiers.length === 0) ||
           statement.type === 'ExpressionStatement'
       );
       for (const statement of sharedTopLevelSideEffects) {
         dependencies.topLevelStatements.add(statement);
-        dependencies.topLevelNonModuleStatements.add(statement);
+        if (statement.type !== 'ExpressionStatement' || !statement.directive) {
+          dependencies.topLevelNonModuleStatements.add(statement);
+        }
       }

Also applies to: 329-332

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/route-chunks.ts` around lines 211 - 216, The shared side-effect
collection in route chunking is incorrectly treating directive prologues like
"use client" and "use strict" as blocking statements. Update the filtering logic
in the shared side-effect scan and the related `topLevelNonModuleStatements`
handling so `ExpressionStatement`s that are directive prologues are excluded,
while real side-effectful expressions remain included. Use the existing
`sharedTopLevelSideEffects` and export analysis paths to ensure
`splitRouteModules: "enforce"` only rejects genuinely shared side effects.
src/route-export-resolution.ts (1)

160-182: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file and inspect the relevant regions with line numbers.
ast-grep outline src/route-export-resolution.ts --view expanded || true
wc -l src/route-export-resolution.ts
sed -n '1,280p' src/route-export-resolution.ts | cat -n

# Locate the specific functions and their call sites.
rg -n "resolvePackageExports|resolveExportAllModule|collectClientOnlyStubExportNames|PACKAGE_RESOLUTION_NOT_APPLICABLE|createRequire|packageSubpath" src/route-export-resolution.ts src -S

Repository: rstackjs/rsbuild-plugin-react-router

Length of output: 14053


Package subpath resolution misses common exports cases.

resolvePackageExports() ignores packageSubpath when "exports" is absent, so pkg/subpath resolves to the package entry instead of the submodule. The "exports" branch also only does exact-key lookups, so pattern exports like "./*" return null, and resolveExportAllModule() treats that as final instead of falling back to createRequire(...).resolve(). This can produce the wrong stub surface or reject valid imports.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/route-export-resolution.ts` around lines 160 - 182, Update
resolvePackageExports and related callers so subpath imports are handled
correctly: when packageJson.exports is absent, do not always fall back to
module/main for every packageSubpath, and make sure pkg/subpath is not resolved
as the package entry. In the exports branch, enhance the lookup logic in
resolvePackageExports to support non-exact export shapes such as pattern
mappings like "./*" instead of only direct packageSubpath keys. Then adjust
resolveExportAllModule so a null result from resolvePackageExports is treated as
“not matched yet” and it can still fall back to createRequire(...).resolve()
before rejecting the import.
src/typegen.ts (1)

22-30: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Don't swallow unexpected typegen --watch exits.

This catch path ignores every rejection. If the watcher exits because the CLI crashes or the route config is temporarily invalid, typegen is dead for the rest of the dev session and nothing reports that state. Only suppress the intentional shutdown path; unexpected exits should be logged or failed loudly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/typegen.ts` around lines 22 - 30, The `typegen --watch` promise handling
in `typegenProcess` is swallowing every rejection, which hides unexpected
watcher exits. Update the `.catch` path so it only ignores the intentional
shutdown case from server teardown, and for any other rejection in
`src/typegen.ts` surface the failure by logging or rethrowing it. Use the
existing `typegenProcess` cleanup in `.finally()` to keep the process reset
behavior intact while making crashes or invalid config failures visible.
src/virtual-modules.ts (1)

11-19: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject backslash path separators in virtual module ids.

The current traversal check only splits on /, so virtual/react-router/foo\..\bar passes validation but can be interpreted as traversal on Windows-style paths. Reject \ inside segments before constructing the node_modules/... path.

🛡️ Proposed fix
   if (
     !relativeId ||
-    segments.some(segment => !segment || segment === '.' || segment === '..')
+    segments.some(
+      segment =>
+        !segment ||
+        segment === '.' ||
+        segment === '..' ||
+        segment.includes('\\')
+    )
   ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  const segments = relativeId.split('/');
  if (
    !relativeId ||
    segments.some(
      segment =>
        !segment ||
        segment === '.' ||
        segment === '..' ||
        segment.includes('\\')
    )
  ) {
    throw new Error(`Invalid virtual module id: ${moduleId}`);
  }

  return `node_modules/${moduleId}.js`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/virtual-modules.ts` around lines 11 - 19, The virtual module ID
validation in the module path builder does not block backslash separators, so a
Windows-style traversal pattern can slip through. Update the validation in the
relativeId/segments check to reject any segment containing backslashes before
returning the node_modules path, keeping the existing dot-segment and
empty-segment checks in place.

@ScriptedAlchemy ScriptedAlchemy force-pushed the codex/parallel-dev-compilers branch from 6ad99ea to e16d85f Compare June 27, 2026 00:49
@ScriptedAlchemy ScriptedAlchemy changed the base branch from perf/bundling-performance to main June 27, 2026 01:09
Comment thread scripts/benchmark/fixture.mjs Fixed
Comment thread scripts/benchmark/fixture.mjs Fixed
@ScriptedAlchemy ScriptedAlchemy force-pushed the codex/parallel-dev-compilers branch from dff4b2a to 559bb2e Compare June 27, 2026 01:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/bench-builds.mjs`:
- Around line 256-283: The benchmark timing in the child process lifecycle is
measuring shutdown latency instead of pure readiness time. Update the
spawn/finish flow in the benchmark helper so the readiness timestamp is captured
when the server becomes ready (around the ready-marking logic) and have the
resolved result use that recorded start-to-ready duration, while keeping
finish() only for cleanup/settlement. Use the existing child spawn, ready, and
finish symbols to wire this in without including SIGTERM/SIGKILL teardown time
in wallMs.

In `@scripts/report-benchmark-ci.mjs`:
- Line 124: The benchmark report logic currently picks a single mode via the
report-generation path, which can hide a build-vs-dev mismatch and produce
misleading CI output. Update the comparison/reporting flow in
report-benchmark-ci.mjs so that the mode selection in the benchmark summary
checks both head and base modes explicitly, and reject the report when they
differ instead of defaulting through head.mode/base.mode. Use the existing
benchmark result handling around the mode field in the reporting script to
surface the mismatch before generating the final CI report.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 547b74ae-d610-4bf7-964c-5fc1e235c7a5

📥 Commits

Reviewing files that changed from the base of the PR and between dff4b2a and 559bb2e.

📒 Files selected for processing (18)
  • .github/workflows/benchmark.yml
  • README.md
  • benchmarks/README.md
  • scripts/bench-builds.mjs
  • scripts/benchmark/fixture.mjs
  • scripts/report-benchmark-ci.mjs
  • scripts/test-package-interop.mjs
  • src/dev-generation.ts
  • src/dev-runtime-artifacts.ts
  • src/dev-runtime-compilation.ts
  • src/dev-runtime-controller.ts
  • src/index.ts
  • src/parallel-route-transforms.ts
  • src/types.ts
  • tests/benchmark-fixture.test.ts
  • tests/dev-runtime-controller.test.ts
  • tests/index.test.ts
  • tests/parallel-route-transforms.test.ts
✅ Files skipped from review due to trivial changes (1)
  • benchmarks/README.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • src/dev-runtime-compilation.ts
  • src/types.ts
  • scripts/test-package-interop.mjs
  • tests/benchmark-fixture.test.ts
  • src/dev-generation.ts
  • src/dev-runtime-artifacts.ts
  • src/dev-runtime-controller.ts
  • tests/dev-runtime-controller.test.ts
  • tests/index.test.ts
  • src/parallel-route-transforms.ts
  • scripts/benchmark/fixture.mjs
  • README.md
  • src/index.ts

Comment thread scripts/bench-builds.mjs
Comment thread scripts/report-benchmark-ci.mjs Outdated
@ScriptedAlchemy ScriptedAlchemy force-pushed the codex/parallel-dev-compilers branch from 559bb2e to 0c1f1e5 Compare June 27, 2026 02:43
@ScriptedAlchemy ScriptedAlchemy changed the base branch from main to perf/bundling-performance June 27, 2026 02:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 447-457: The dev-server sample still closes over currentDevServer
and mounts its middlewares/handler on a shared app, so rerunning startServer()
would keep binding requests to the first server. Update the README example
around createDevServer, startServer, and onRouteTopologyChange to either
explicitly document this as a full-process-restart flow or refactor it to use
replaceable delegates that swap the active handler without stacking stale
middleware, and make sure the text matches the serialized close-then-recreate
behavior described in the surrounding prose.

In `@src/server-utils.ts`:
- Around line 117-124: The route discovery contract is inconsistent:
`resolveServerBuildModule` currently rejects `undefined` even though the
`routeDiscovery` field is treated as optional elsewhere. Update
`isRouteDiscovery` in `server-utils.ts` to accept `undefined` as valid, or make
the `routeDiscovery` requirement consistent across the
`resolveServerBuildModule` validation path and related type checks so local
builds with no route discovery do not throw.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b8c10ff6-a873-490f-a640-aeed1b40dcca

📥 Commits

Reviewing files that changed from the base of the PR and between 559bb2e and 0c1f1e5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (125)
  • .changeset/bright-routes-run.md
  • .changeset/coherent-dev-generations.md
  • .changeset/lazy-entry-hydration.md
  • .changeset/quiet-topology-order.md
  • .changeset/sharp-routes-heal.md
  • .changeset/warm-watchers-rest.md
  • README.md
  • benchmarks/README.md
  • config/package.json
  • config/rslib.config.ts
  • docs/coherent-dev-generations.md
  • examples/client-only/package.json
  • examples/cloudflare/package.json
  • examples/custom-node-server/app/load-context.d.ts
  • examples/custom-node-server/app/routes/projects/edit.tsx
  • examples/custom-node-server/app/routes/projects/index.tsx
  • examples/custom-node-server/app/routes/projects/layout.tsx
  • examples/custom-node-server/app/routes/projects/project.tsx
  • examples/custom-node-server/app/routes/projects/settings.tsx
  • examples/custom-node-server/package.json
  • examples/custom-node-server/playwright.config.ts
  • examples/custom-node-server/rsbuild.config.ts
  • examples/custom-node-server/scripts/smoke-production.mjs
  • examples/custom-node-server/server.js
  • examples/custom-node-server/server/index.ts
  • examples/custom-node-server/tsconfig.json
  • examples/default-template/app/dev-routes.ts
  • examples/default-template/app/routes.ts
  • examples/default-template/package.json
  • examples/default-template/playwright.config.ts
  • examples/default-template/react-router.config.ts
  • examples/default-template/rsbuild.config.ts
  • examples/default-template/tests/e2e/dev-route-watch.test.ts
  • examples/default-template/tests/e2e/lazy-compilation.test.ts
  • examples/epic-stack/package.json
  • examples/federation/epic-stack-remote/package.json
  • examples/federation/epic-stack/package.json
  • examples/prerender/package.json
  • examples/spa-mode/package.json
  • package.json
  • rslib.config.ts
  • scripts/bench-builds.mjs
  • scripts/benchmark/fixture.mjs
  • scripts/compare-benchmarks.mjs
  • scripts/test-package-interop.mjs
  • src/babel.ts
  • src/bounded-cache.ts
  • src/build-manifest.ts
  • src/build-output-transforms.ts
  • src/concurrency.ts
  • src/constants.ts
  • src/dev-generation.ts
  • src/dev-runtime-artifacts.ts
  • src/dev-runtime-compilation.ts
  • src/dev-runtime-controller.ts
  • src/dev-runtime-session.ts
  • src/dev-server.ts
  • src/export-utils.ts
  • src/index.ts
  • src/lazy-compilation.ts
  • src/manifest.ts
  • src/modify-browser-manifest.ts
  • src/parallel-route-transform-protocol.ts
  • src/parallel-route-transform-worker.ts
  • src/parallel-route-transforms.ts
  • src/performance.ts
  • src/plugin-utils.ts
  • src/prerender-build.ts
  • src/prerender.ts
  • src/react-router-config.ts
  • src/route-artifacts.ts
  • src/route-ast.ts
  • src/route-chunks.ts
  • src/route-component-transform.ts
  • src/route-export-pruning.ts
  • src/route-export-resolution.ts
  • src/route-transform-tasks.ts
  • src/route-watch.ts
  • src/server-build-plan.ts
  • src/server-utils.ts
  • src/templates/entry.server.tsx
  • src/typegen.ts
  • src/types.ts
  • src/virtual-modules.ts
  • src/warnings/warn-on-client-source-maps.ts
  • src/yuku.ts
  • tests/benchmark-fixture.test.ts
  • tests/bounded-cache.test.ts
  • tests/build-manifest.test.ts
  • tests/build-output-transforms.test.ts
  • tests/client-modules.test.ts
  • tests/dev-generation-multi-entry.test.ts
  • tests/dev-generation.test.ts
  • tests/dev-runtime-controller.test.ts
  • tests/dev-runtime.integration.test.ts
  • tests/dev-server.test.ts
  • tests/export-utils.test.ts
  • tests/features.test.ts
  • tests/fixtures/dev-runtime/app/entry.client.tsx
  • tests/fixtures/dev-runtime/app/entry.server.tsx
  • tests/fixtures/dev-runtime/app/root.tsx
  • tests/fixtures/dev-runtime/app/routes.ts
  • tests/fixtures/dev-runtime/app/routes/index.tsx
  • tests/fixtures/dev-runtime/app/routes/other.tsx
  • tests/fixtures/dev-runtime/react-router.config.ts
  • tests/fixtures/dev-runtime/server/index.ts
  • tests/index.test.ts
  • tests/lazy-compilation.test.ts
  • tests/manifest-split-route-modules.test.ts
  • tests/manifest.test.ts
  • tests/modify-browser-manifest.test.ts
  • tests/parallel-route-transforms.test.ts
  • tests/performance.test.ts
  • tests/plugin-utils.test.ts
  • tests/prerender.test.ts
  • tests/react-router-config.test.ts
  • tests/remove-exports.test.ts
  • tests/route-artifacts.test.ts
  • tests/route-chunks-cache.test.ts
  • tests/route-chunks.test.ts
  • tests/route-watch.test.ts
  • tests/server-build-plan.test.ts
  • tests/server-utils.test.ts
  • tests/setup.ts
  • tests/warn-on-client-source-maps.test.ts
💤 Files with no reviewable changes (10)
  • tests/plugin-utils.test.ts
  • tests/react-router-config.test.ts
  • src/babel.ts
  • tests/manifest-split-route-modules.test.ts
  • tests/prerender.test.ts
  • tests/performance.test.ts
  • examples/custom-node-server/server/index.ts
  • tests/modify-browser-manifest.test.ts
  • tests/manifest.test.ts
  • tests/parallel-route-transforms.test.ts
✅ Files skipped from review due to trivial changes (16)
  • tests/fixtures/dev-runtime/app/routes/index.tsx
  • examples/custom-node-server/app/routes/projects/index.tsx
  • examples/custom-node-server/app/routes/projects/layout.tsx
  • config/package.json
  • .changeset/warm-watchers-rest.md
  • .changeset/bright-routes-run.md
  • .changeset/lazy-entry-hydration.md
  • .changeset/coherent-dev-generations.md
  • .changeset/sharp-routes-heal.md
  • src/warnings/warn-on-client-source-maps.ts
  • examples/federation/epic-stack-remote/package.json
  • .changeset/quiet-topology-order.md
  • examples/custom-node-server/app/routes/projects/project.tsx
  • benchmarks/README.md
  • examples/client-only/package.json
  • docs/coherent-dev-generations.md
🚧 Files skipped from review as they are similar to previous changes (83)
  • tests/build-manifest.test.ts
  • tests/fixtures/dev-runtime/app/root.tsx
  • src/parallel-route-transform-protocol.ts
  • examples/custom-node-server/tsconfig.json
  • tests/fixtures/dev-runtime/react-router.config.ts
  • rslib.config.ts
  • tests/fixtures/dev-runtime/app/entry.server.tsx
  • examples/default-template/app/routes.ts
  • tests/fixtures/dev-runtime/app/routes/other.tsx
  • tests/fixtures/dev-runtime/app/routes.ts
  • examples/custom-node-server/app/load-context.d.ts
  • examples/spa-mode/package.json
  • tests/fixtures/dev-runtime/app/entry.client.tsx
  • src/virtual-modules.ts
  • tests/export-utils.test.ts
  • examples/prerender/package.json
  • examples/custom-node-server/rsbuild.config.ts
  • examples/federation/epic-stack/package.json
  • examples/custom-node-server/app/routes/projects/settings.tsx
  • examples/default-template/app/dev-routes.ts
  • examples/custom-node-server/app/routes/projects/edit.tsx
  • examples/default-template/tests/e2e/dev-route-watch.test.ts
  • src/bounded-cache.ts
  • examples/cloudflare/package.json
  • src/templates/entry.server.tsx
  • examples/default-template/rsbuild.config.ts
  • src/concurrency.ts
  • src/types.ts
  • tests/fixtures/dev-runtime/server/index.ts
  • examples/custom-node-server/playwright.config.ts
  • src/typegen.ts
  • examples/epic-stack/package.json
  • src/build-manifest.ts
  • examples/default-template/react-router.config.ts
  • scripts/compare-benchmarks.mjs
  • tests/dev-server.test.ts
  • src/lazy-compilation.ts
  • examples/default-template/package.json
  • src/dev-server.ts
  • tests/bounded-cache.test.ts
  • src/yuku.ts
  • src/constants.ts
  • examples/custom-node-server/package.json
  • src/parallel-route-transform-worker.ts
  • src/dev-runtime-compilation.ts
  • tests/dev-generation-multi-entry.test.ts
  • src/server-build-plan.ts
  • src/route-ast.ts
  • examples/custom-node-server/server.js
  • src/dev-runtime-session.ts
  • src/route-artifacts.ts
  • examples/custom-node-server/scripts/smoke-production.mjs
  • tests/build-output-transforms.test.ts
  • src/performance.ts
  • scripts/test-package-interop.mjs
  • src/dev-runtime-artifacts.ts
  • tests/client-modules.test.ts
  • examples/default-template/playwright.config.ts
  • src/dev-generation.ts
  • tests/lazy-compilation.test.ts
  • src/route-export-resolution.ts
  • src/route-component-transform.ts
  • package.json
  • src/react-router-config.ts
  • config/rslib.config.ts
  • src/prerender.ts
  • src/route-export-pruning.ts
  • src/export-utils.ts
  • tests/index.test.ts
  • src/build-output-transforms.ts
  • src/route-watch.ts
  • src/parallel-route-transforms.ts
  • src/route-transform-tasks.ts
  • tests/dev-generation.test.ts
  • src/plugin-utils.ts
  • examples/default-template/tests/e2e/lazy-compilation.test.ts
  • src/manifest.ts
  • tests/features.test.ts
  • src/modify-browser-manifest.ts
  • tests/dev-runtime-controller.test.ts
  • src/prerender-build.ts
  • src/route-chunks.ts
  • src/index.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 447-457: The dev-server sample still closes over currentDevServer
and mounts its middlewares/handler on a shared app, so rerunning startServer()
would keep binding requests to the first server. Update the README example
around createDevServer, startServer, and onRouteTopologyChange to either
explicitly document this as a full-process-restart flow or refactor it to use
replaceable delegates that swap the active handler without stacking stale
middleware, and make sure the text matches the serialized close-then-recreate
behavior described in the surrounding prose.

In `@src/server-utils.ts`:
- Around line 117-124: The route discovery contract is inconsistent:
`resolveServerBuildModule` currently rejects `undefined` even though the
`routeDiscovery` field is treated as optional elsewhere. Update
`isRouteDiscovery` in `server-utils.ts` to accept `undefined` as valid, or make
the `routeDiscovery` requirement consistent across the
`resolveServerBuildModule` validation path and related type checks so local
builds with no route discovery do not throw.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b8c10ff6-a873-490f-a640-aeed1b40dcca

📥 Commits

Reviewing files that changed from the base of the PR and between 559bb2e and 0c1f1e5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (125)
  • .changeset/bright-routes-run.md
  • .changeset/coherent-dev-generations.md
  • .changeset/lazy-entry-hydration.md
  • .changeset/quiet-topology-order.md
  • .changeset/sharp-routes-heal.md
  • .changeset/warm-watchers-rest.md
  • README.md
  • benchmarks/README.md
  • config/package.json
  • config/rslib.config.ts
  • docs/coherent-dev-generations.md
  • examples/client-only/package.json
  • examples/cloudflare/package.json
  • examples/custom-node-server/app/load-context.d.ts
  • examples/custom-node-server/app/routes/projects/edit.tsx
  • examples/custom-node-server/app/routes/projects/index.tsx
  • examples/custom-node-server/app/routes/projects/layout.tsx
  • examples/custom-node-server/app/routes/projects/project.tsx
  • examples/custom-node-server/app/routes/projects/settings.tsx
  • examples/custom-node-server/package.json
  • examples/custom-node-server/playwright.config.ts
  • examples/custom-node-server/rsbuild.config.ts
  • examples/custom-node-server/scripts/smoke-production.mjs
  • examples/custom-node-server/server.js
  • examples/custom-node-server/server/index.ts
  • examples/custom-node-server/tsconfig.json
  • examples/default-template/app/dev-routes.ts
  • examples/default-template/app/routes.ts
  • examples/default-template/package.json
  • examples/default-template/playwright.config.ts
  • examples/default-template/react-router.config.ts
  • examples/default-template/rsbuild.config.ts
  • examples/default-template/tests/e2e/dev-route-watch.test.ts
  • examples/default-template/tests/e2e/lazy-compilation.test.ts
  • examples/epic-stack/package.json
  • examples/federation/epic-stack-remote/package.json
  • examples/federation/epic-stack/package.json
  • examples/prerender/package.json
  • examples/spa-mode/package.json
  • package.json
  • rslib.config.ts
  • scripts/bench-builds.mjs
  • scripts/benchmark/fixture.mjs
  • scripts/compare-benchmarks.mjs
  • scripts/test-package-interop.mjs
  • src/babel.ts
  • src/bounded-cache.ts
  • src/build-manifest.ts
  • src/build-output-transforms.ts
  • src/concurrency.ts
  • src/constants.ts
  • src/dev-generation.ts
  • src/dev-runtime-artifacts.ts
  • src/dev-runtime-compilation.ts
  • src/dev-runtime-controller.ts
  • src/dev-runtime-session.ts
  • src/dev-server.ts
  • src/export-utils.ts
  • src/index.ts
  • src/lazy-compilation.ts
  • src/manifest.ts
  • src/modify-browser-manifest.ts
  • src/parallel-route-transform-protocol.ts
  • src/parallel-route-transform-worker.ts
  • src/parallel-route-transforms.ts
  • src/performance.ts
  • src/plugin-utils.ts
  • src/prerender-build.ts
  • src/prerender.ts
  • src/react-router-config.ts
  • src/route-artifacts.ts
  • src/route-ast.ts
  • src/route-chunks.ts
  • src/route-component-transform.ts
  • src/route-export-pruning.ts
  • src/route-export-resolution.ts
  • src/route-transform-tasks.ts
  • src/route-watch.ts
  • src/server-build-plan.ts
  • src/server-utils.ts
  • src/templates/entry.server.tsx
  • src/typegen.ts
  • src/types.ts
  • src/virtual-modules.ts
  • src/warnings/warn-on-client-source-maps.ts
  • src/yuku.ts
  • tests/benchmark-fixture.test.ts
  • tests/bounded-cache.test.ts
  • tests/build-manifest.test.ts
  • tests/build-output-transforms.test.ts
  • tests/client-modules.test.ts
  • tests/dev-generation-multi-entry.test.ts
  • tests/dev-generation.test.ts
  • tests/dev-runtime-controller.test.ts
  • tests/dev-runtime.integration.test.ts
  • tests/dev-server.test.ts
  • tests/export-utils.test.ts
  • tests/features.test.ts
  • tests/fixtures/dev-runtime/app/entry.client.tsx
  • tests/fixtures/dev-runtime/app/entry.server.tsx
  • tests/fixtures/dev-runtime/app/root.tsx
  • tests/fixtures/dev-runtime/app/routes.ts
  • tests/fixtures/dev-runtime/app/routes/index.tsx
  • tests/fixtures/dev-runtime/app/routes/other.tsx
  • tests/fixtures/dev-runtime/react-router.config.ts
  • tests/fixtures/dev-runtime/server/index.ts
  • tests/index.test.ts
  • tests/lazy-compilation.test.ts
  • tests/manifest-split-route-modules.test.ts
  • tests/manifest.test.ts
  • tests/modify-browser-manifest.test.ts
  • tests/parallel-route-transforms.test.ts
  • tests/performance.test.ts
  • tests/plugin-utils.test.ts
  • tests/prerender.test.ts
  • tests/react-router-config.test.ts
  • tests/remove-exports.test.ts
  • tests/route-artifacts.test.ts
  • tests/route-chunks-cache.test.ts
  • tests/route-chunks.test.ts
  • tests/route-watch.test.ts
  • tests/server-build-plan.test.ts
  • tests/server-utils.test.ts
  • tests/setup.ts
  • tests/warn-on-client-source-maps.test.ts
💤 Files with no reviewable changes (10)
  • tests/plugin-utils.test.ts
  • tests/react-router-config.test.ts
  • src/babel.ts
  • tests/manifest-split-route-modules.test.ts
  • tests/prerender.test.ts
  • tests/performance.test.ts
  • examples/custom-node-server/server/index.ts
  • tests/modify-browser-manifest.test.ts
  • tests/manifest.test.ts
  • tests/parallel-route-transforms.test.ts
✅ Files skipped from review due to trivial changes (16)
  • tests/fixtures/dev-runtime/app/routes/index.tsx
  • examples/custom-node-server/app/routes/projects/index.tsx
  • examples/custom-node-server/app/routes/projects/layout.tsx
  • config/package.json
  • .changeset/warm-watchers-rest.md
  • .changeset/bright-routes-run.md
  • .changeset/lazy-entry-hydration.md
  • .changeset/coherent-dev-generations.md
  • .changeset/sharp-routes-heal.md
  • src/warnings/warn-on-client-source-maps.ts
  • examples/federation/epic-stack-remote/package.json
  • .changeset/quiet-topology-order.md
  • examples/custom-node-server/app/routes/projects/project.tsx
  • benchmarks/README.md
  • examples/client-only/package.json
  • docs/coherent-dev-generations.md
🚧 Files skipped from review as they are similar to previous changes (83)
  • tests/build-manifest.test.ts
  • tests/fixtures/dev-runtime/app/root.tsx
  • src/parallel-route-transform-protocol.ts
  • examples/custom-node-server/tsconfig.json
  • tests/fixtures/dev-runtime/react-router.config.ts
  • rslib.config.ts
  • tests/fixtures/dev-runtime/app/entry.server.tsx
  • examples/default-template/app/routes.ts
  • tests/fixtures/dev-runtime/app/routes/other.tsx
  • tests/fixtures/dev-runtime/app/routes.ts
  • examples/custom-node-server/app/load-context.d.ts
  • examples/spa-mode/package.json
  • tests/fixtures/dev-runtime/app/entry.client.tsx
  • src/virtual-modules.ts
  • tests/export-utils.test.ts
  • examples/prerender/package.json
  • examples/custom-node-server/rsbuild.config.ts
  • examples/federation/epic-stack/package.json
  • examples/custom-node-server/app/routes/projects/settings.tsx
  • examples/default-template/app/dev-routes.ts
  • examples/custom-node-server/app/routes/projects/edit.tsx
  • examples/default-template/tests/e2e/dev-route-watch.test.ts
  • src/bounded-cache.ts
  • examples/cloudflare/package.json
  • src/templates/entry.server.tsx
  • examples/default-template/rsbuild.config.ts
  • src/concurrency.ts
  • src/types.ts
  • tests/fixtures/dev-runtime/server/index.ts
  • examples/custom-node-server/playwright.config.ts
  • src/typegen.ts
  • examples/epic-stack/package.json
  • src/build-manifest.ts
  • examples/default-template/react-router.config.ts
  • scripts/compare-benchmarks.mjs
  • tests/dev-server.test.ts
  • src/lazy-compilation.ts
  • examples/default-template/package.json
  • src/dev-server.ts
  • tests/bounded-cache.test.ts
  • src/yuku.ts
  • src/constants.ts
  • examples/custom-node-server/package.json
  • src/parallel-route-transform-worker.ts
  • src/dev-runtime-compilation.ts
  • tests/dev-generation-multi-entry.test.ts
  • src/server-build-plan.ts
  • src/route-ast.ts
  • examples/custom-node-server/server.js
  • src/dev-runtime-session.ts
  • src/route-artifacts.ts
  • examples/custom-node-server/scripts/smoke-production.mjs
  • tests/build-output-transforms.test.ts
  • src/performance.ts
  • scripts/test-package-interop.mjs
  • src/dev-runtime-artifacts.ts
  • tests/client-modules.test.ts
  • examples/default-template/playwright.config.ts
  • src/dev-generation.ts
  • tests/lazy-compilation.test.ts
  • src/route-export-resolution.ts
  • src/route-component-transform.ts
  • package.json
  • src/react-router-config.ts
  • config/rslib.config.ts
  • src/prerender.ts
  • src/route-export-pruning.ts
  • src/export-utils.ts
  • tests/index.test.ts
  • src/build-output-transforms.ts
  • src/route-watch.ts
  • src/parallel-route-transforms.ts
  • src/route-transform-tasks.ts
  • tests/dev-generation.test.ts
  • src/plugin-utils.ts
  • examples/default-template/tests/e2e/lazy-compilation.test.ts
  • src/manifest.ts
  • tests/features.test.ts
  • src/modify-browser-manifest.ts
  • tests/dev-runtime-controller.test.ts
  • src/prerender-build.ts
  • src/route-chunks.ts
  • src/index.ts
🛑 Comments failed to post (2)
README.md (1)

447-457: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

This sample still binds requests to the first dev server.

The prose says onRouteTopologyChange should recreate the dev server, but this example mounts currentDevServer.middlewares and the request handler onto a shared app and closes over currentDevServer. Re-running startServer() would stack stale middleware/handlers and keep requests on the old server. Please either document this as a full-process-restart flow or rewrite the sample around replaceable delegates.

Also applies to: 459-525

🧰 Tools
🪛 LanguageTool

[style] ~450-~450: Consider replacing this word to strengthen your wording.
Context: ...tic API. The callback is a notification and is not awaited, so it can safely start ...

(AND_THAT)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 447 - 457, The dev-server sample still closes over
currentDevServer and mounts its middlewares/handler on a shared app, so
rerunning startServer() would keep binding requests to the first server. Update
the README example around createDevServer, startServer, and
onRouteTopologyChange to either explicitly document this as a
full-process-restart flow or refactor it to use replaceable delegates that swap
the active handler without stacking stale middleware, and make sure the text
matches the serialized close-then-recreate behavior described in the surrounding
prose.
src/server-utils.ts (1)

117-124: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Accept undefined route discovery or make the contract required.

Lines 23-31 allow routeDiscovery to be undefined, but Line 162 rejects that via isRouteDiscovery, causing resolveServerBuildModule to throw for an otherwise valid local build shape.

Proposed fix
 function isRouteDiscovery(value: unknown): boolean {
   return (
-    isRecord(value) &&
-    (value.mode === 'initial' ||
+    value === undefined ||
+    (isRecord(value) &&
+      (value.mode === 'initial' ||
       (value.mode === 'lazy' &&
         (value.manifestPath === undefined ||
-          typeof value.manifestPath === 'string')))
+          typeof value.manifestPath === 'string'))))
   );
 }

Also applies to: 162-162

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server-utils.ts` around lines 117 - 124, The route discovery contract is
inconsistent: `resolveServerBuildModule` currently rejects `undefined` even
though the `routeDiscovery` field is treated as optional elsewhere. Update
`isRouteDiscovery` in `server-utils.ts` to accept `undefined` as valid, or make
the `routeDiscovery` requirement consistent across the
`resolveServerBuildModule` validation path and related type checks so local
builds with no route discovery do not throw.

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.

2 participants