Skip to content

fix(router-plugin): isolate route metadata per plugin instance#7313

Merged
schiller-manuel merged 3 commits intomainfrom
fix-7174
May 1, 2026
Merged

fix(router-plugin): isolate route metadata per plugin instance#7313
schiller-manuel merged 3 commits intomainfrom
fix-7174

Conversation

@schiller-manuel
Copy link
Copy Markdown
Contributor

@schiller-manuel schiller-manuel commented May 1, 2026

fixes #7174

Summary by CodeRabbit

  • New Features

    • Added an explicit Router Plugin Context API (runtime + type) and new public export to access it.
    • Router plugins and platform wrappers can now accept an optional context to scope state per-instance.
  • Bug Fixes

    • Eliminates cross-instance/global-state interference between router plugin instances.
  • Tests

    • Added tests validating isolation and correct HMR behavior when using separate plugin contexts.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

📝 Walkthrough

Walkthrough

Router plugin state is moved from a shared global (globalThis.TSR_ROUTES_BY_ID_MAP) to explicit per-instance contexts. A new RouterPluginContext with routesByFile is introduced and passed into generator, code-splitter, HMR, composed factories and build integrations; default factory wrappers still create a context when none is provided.

Changes

Cohort / File(s) Summary
Core Context Infrastructure
packages/router-plugin/src/core/router-plugin-context.ts, packages/router-plugin/src/context.ts, packages/router-plugin/src/index.ts
Add RouterPluginContext type and createRouterPluginContext() factory; barrel and public re-exports to expose context API.
Router Sub-Plugins Refactoring
packages/router-plugin/src/core/router-generator-plugin.ts, packages/router-plugin/src/core/router-code-splitter-plugin.ts, packages/router-plugin/src/core/router-hmr-plugin.ts, packages/router-plugin/src/core/router-composed-plugin.ts
Introduce createRouter*Plugin(options, routerPluginContext) functions; update factories to delegate to these with a created default context; move route metadata reads/writes from globalThis into routerPluginContext.routesByFile; adjust signatures to accept/ignore meta where applicable.
Build Target Wrappers
packages/router-plugin/src/vite.ts, packages/router-plugin/src/webpack.ts, packages/router-plugin/src/esbuild.ts, packages/router-plugin/src/rspack.ts
Convert prebuilt plugin exports into wrapper functions accepting (options?, routerPluginContext?) that default to createRouterPluginContext() and delegate to createRouter*Plugin; add RouterPluginContext to exported types.
Package Manifest & Globals
packages/router-plugin/package.json, packages/router-plugin/src/global.d.ts, packages/router-plugin/vite.config.ts
Add ./context package export path; remove the TSR_ROUTES_BY_ID_MAP global declaration; include src/context.ts in Vite build entries.
Start Plugin Integrations
packages/start-plugin-core/src/vite/start-router-plugin/plugin.ts, packages/start-plugin-core/src/rsbuild/start-router-plugin.ts
Create a single routerPluginContext per start-plugin invocation and pass it to generator and code-splitter plugins; replace dynamic try/import logic in rsbuild path with static imports and direct construction using explicit contexts.
Tests & Changeset
.changeset/clean-router-plugin-context.md, packages/router-plugin/tests/router-plugin-context.test.ts
Add a changeset documenting the patch and tests verifying isolation between plugin instances and HMR behavior when using explicit contexts.

Sequence Diagram(s)

sequenceDiagram
    participant User as Vite Config / Consumer
    participant Composed as Composed Factory
    participant Context as RouterPluginContext
    participant Generator as Router Generator Plugin
    participant Splitter as Code-Splitter Plugin
    participant HMR as Router HMR Plugin
    participant RouteMap as context.routesByFile

    User->>Composed: call tanstackRouter(options)
    Composed->>Context: createRouterPluginContext()
    Composed->>Generator: createRouterGeneratorPlugin(options, Context)
    Generator->>RouteMap: write routes (generator.getRoutesByFileMap())
    Composed->>Splitter: createRouterCodeSplitterPlugin(options, Context)
    Composed->>HMR: createRouterHmrPlugin(options, Context)
    Splitter->>RouteMap: read routes for transform
    HMR->>RouteMap: read routes for HMR handling
Loading
sequenceDiagram
    participant InstanceA as Router Instance A
    participant CtxA as Context A
    participant InstanceB as Router Instance B
    participant CtxB as Context B
    participant File as Route File

    InstanceA->>CtxA: createRouterPluginContext()
    InstanceB->>CtxB: createRouterPluginContext()

    InstanceA->>CtxA: generator writes routes for File
    InstanceB->>CtxB: generator writes routes for other File

    Note over CtxA,CtxB: Isolated maps prevent cross-transforms
    File->>CtxA: Splitter A looks up File (only in CtxA)
    File->>CtxB: Splitter B looks up File (only in CtxB)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped in code, made contexts neat,
I tucked each router in its own retreat,
No globals clash, no duplicate hot,
Plugins mind their maps — they play the right spot,
HMR sings on, the dev server's sweet.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: isolating route metadata per plugin instance to fix the cross-instance shared state problem, which directly addresses the root cause of issue #7174.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding requirements from issue #7174: removes global shared state (TSR_ROUTES_BY_ID_MAP), creates per-instance RouterPluginContext with isolated routesByFile maps, and updates all plugin factories to accept and use explicit contexts instead of reading globalThis.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing issue #7174: implementing per-plugin-instance route metadata isolation. No unrelated refactoring, dependency updates, or out-of-scope modifications are present.

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

✨ 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 fix-7174

Review rate limit: 4/5 reviews remaining, refill in 12 minutes.

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

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented May 1, 2026

View your CI Pipeline Execution ↗ for commit 6dfa56a

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ❌ Failed 9m 5s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 48s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-01 19:01:11 UTC

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

🚀 Changeset Version Preview

2 package(s) bumped directly, 5 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/router-plugin 1.167.31 → 1.167.32 Changeset
@tanstack/start-plugin-core 1.169.13 → 1.169.14 Changeset
@tanstack/react-start 1.167.58 → 1.167.59 Dependent
@tanstack/react-start-rsc 0.0.37 → 0.0.38 Dependent
@tanstack/router-vite-plugin 1.166.46 → 1.166.47 Dependent
@tanstack/solid-start 1.167.55 → 1.167.56 Dependent
@tanstack/vue-start 1.167.51 → 1.167.52 Dependent

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Bundle Size Benchmarks

  • Commit: 8b9ea22b5b1a
  • Measured at: 2026-05-01T18:52:59.223Z
  • Baseline source: history:ae453b78624c
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.15 KiB 0 B (0.00%) 273.94 KiB 75.70 KiB █████▁▁▁▁▁▁
react-router.full 90.68 KiB 0 B (0.00%) 285.45 KiB 78.71 KiB █████▁▁▁▁▁▁
solid-router.minimal 35.38 KiB 0 B (0.00%) 106.25 KiB 31.81 KiB █████▁▁▁▁▁▁
solid-router.full 40.10 KiB 0 B (0.00%) 120.46 KiB 36.04 KiB █████▁▁▁▁▁▁
vue-router.minimal 53.15 KiB 0 B (0.00%) 151.39 KiB 47.73 KiB █████▁▁▁▁▁▁
vue-router.full 58.28 KiB 0 B (0.00%) 167.56 KiB 52.18 KiB █████▁▁▁▁▁▁
react-start.minimal 101.76 KiB 0 B (0.00%) 322.10 KiB 87.97 KiB █████▁▁▁▁▁▁
react-start.full 105.19 KiB 0 B (0.00%) 332.43 KiB 90.96 KiB █████▁▁▁▁▁▁
react-start.rsbuild.minimal 99.33 KiB 0 B (0.00%) 316.47 KiB 85.46 KiB ███▂▂▁▁▁▁▁▁
react-start.rsbuild.full 102.63 KiB 0 B (0.00%) 326.90 KiB 88.28 KiB ███▂▂▁▁▁▁▁▁
solid-start.minimal 49.40 KiB 0 B (0.00%) 152.08 KiB 43.60 KiB █████▁▁▁▁▁▁
solid-start.full 55.20 KiB 0 B (0.00%) 168.98 KiB 48.51 KiB █████▁▁▁▁▁▁

Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 1, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7313

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7313

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7313

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7313

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7313

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7313

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7313

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7313

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7313

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7313

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7313

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7313

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7313

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7313

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7313

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7313

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7313

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7313

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7313

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7313

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7313

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7313

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7313

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7313

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7313

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7313

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7313

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7313

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7313

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7313

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7313

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7313

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7313

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7313

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7313

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7313

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7313

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7313

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7313

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7313

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7313

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7313

commit: faa4577

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/router-plugin/tests/router-plugin-context.test.ts (1)

78-146: ⚡ Quick win

Cover the public tanstackRouter() wiring too.

This regression came from the composed/plugin-entry path, but the test only exercises createRouterCodeSplitterPlugin directly. A small integration test that instantiates tanstackRouter() twice would guard the actual Vite API surface changed in this PR and catch future context-wiring regressions higher up the stack.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-plugin/tests/router-plugin-context.test.ts` around lines 78 -
146, Add an integration test that exercises the public tanstackRouter() wiring
by instantiating tanstackRouter() twice (instead of calling
createRouterCodeSplitterPlugin directly) and verifying their code-splitter
contexts remain isolated; specifically, create two router instances via
tanstackRouter(), obtain their splitter plugins (or plugin entries) which
internally call createRouterCodeSplitterPlugin/createRouterPluginContext, run
configurePlugin on both, then run transformReferenceRoute for the first instance
and assert it produces one hot-declaration and that running
transformReferenceRoute on the second instance with the same input returns null
(use existing helpers like configurePlugin, transformReferenceRoute,
getReferencePlugin, countProgramHotDeclarations to mirror the current test
flow).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/router-plugin/src/webpack.ts`:
- Around line 42-43: The plugin is using the module-level singleton
defaultRouterPluginContext as a parameter default (routerPluginContext:
RouterPluginContext = defaultRouterPluginContext), which causes routesByFile and
other metadata to be shared across plugin instances; change the API so the
default is undefined or create a per-instance clone inside the factory (e.g., in
the function that builds the plugin) and initialize routerPluginContext =
cloneDefaultRouterPluginContext() or create a fresh object with an isolated
routesByFile map when routerPluginContext is not provided; update all places
that used the parameter default (including the other occurrence at the parameter
around line 60) to avoid referencing the module-wide defaultRouterPluginContext
directly so each plugin instance gets its own context instance.

---

Nitpick comments:
In `@packages/router-plugin/tests/router-plugin-context.test.ts`:
- Around line 78-146: Add an integration test that exercises the public
tanstackRouter() wiring by instantiating tanstackRouter() twice (instead of
calling createRouterCodeSplitterPlugin directly) and verifying their
code-splitter contexts remain isolated; specifically, create two router
instances via tanstackRouter(), obtain their splitter plugins (or plugin
entries) which internally call
createRouterCodeSplitterPlugin/createRouterPluginContext, run configurePlugin on
both, then run transformReferenceRoute for the first instance and assert it
produces one hot-declaration and that running transformReferenceRoute on the
second instance with the same input returns null (use existing helpers like
configurePlugin, transformReferenceRoute, getReferencePlugin,
countProgramHotDeclarations to mirror the current test flow).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4b4d8c30-9d80-407e-b67d-ebac4f48ca07

📥 Commits

Reviewing files that changed from the base of the PR and between 8b9ea22 and 44d2802.

📒 Files selected for processing (18)
  • .changeset/clean-router-plugin-context.md
  • packages/router-plugin/package.json
  • packages/router-plugin/src/context.ts
  • packages/router-plugin/src/core/router-code-splitter-plugin.ts
  • packages/router-plugin/src/core/router-composed-plugin.ts
  • packages/router-plugin/src/core/router-generator-plugin.ts
  • packages/router-plugin/src/core/router-hmr-plugin.ts
  • packages/router-plugin/src/core/router-plugin-context.ts
  • packages/router-plugin/src/esbuild.ts
  • packages/router-plugin/src/global.d.ts
  • packages/router-plugin/src/index.ts
  • packages/router-plugin/src/rspack.ts
  • packages/router-plugin/src/vite.ts
  • packages/router-plugin/src/webpack.ts
  • packages/router-plugin/tests/router-plugin-context.test.ts
  • packages/router-plugin/vite.config.ts
  • packages/start-plugin-core/src/rsbuild/start-router-plugin.ts
  • packages/start-plugin-core/src/vite/start-router-plugin/plugin.ts
💤 Files with no reviewable changes (1)
  • packages/router-plugin/src/global.d.ts

Comment thread packages/router-plugin/src/core/router-hmr-plugin.ts Outdated
Comment thread packages/router-plugin/src/webpack.ts Outdated
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 1, 2026

Merging this PR will not alter performance

✅ 5 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing fix-7174 (faa4577) with main (ae453b7)2

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

  2. No successful run was found on main (8b9ea22) during the generation of this report, so ae453b7 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/router-plugin/tests/router-plugin-context.test.ts (1)

79-180: ⚡ Quick win

Add one wrapper-level regression case.

This covers the core plugins well, but it never exercises the new public wrapper API. A small test that wires a shared RouterPluginContext through TanStackRouterGenerator* and TanStackRouterCodeSplitter* would catch entrypoint-level regressions in the exported surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-plugin/tests/router-plugin-context.test.ts` around lines 79 -
180, Add a wrapper-level regression test that wires a single shared
RouterPluginContext through the public wrapper exports (the
TanStackRouterGenerator* and TanStackRouterCodeSplitter* entrypoints) instead of
calling the internal plugin factories directly; create a RouterPluginContext via
createRouterPluginContext(), populate routesByFile for a sample route file,
instantiate the public wrapper generator and code-splitter (the exported
TanStackRouterGenerator* and TanStackRouterCodeSplitter* functions/classes), run
their configure/transform flows (similar to existing tests using
createRouterCodeSplitterPlugin/createRouterHmrPlugin) and assert the generated
output contains the expected HMR/fast-refresh anchor or code-splitting markers;
this ensures you reference the public wrapper symbols rather than internal
plugin constructors while keeping the test structure similar to the existing
'router plugin context' cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/router-plugin/src/esbuild.ts`:
- Around line 23-30: The problem is that TanStackRouterGeneratorEsbuild and the
corresponding code-splitter create separate router plugin contexts (and thus
separate routesByFile maps) when callers omit routerPluginContext; fix by
creating a shared module-level default context and using it when
routerPluginContext is not provided. Concretely, add a top-level const (e.g.,
defaultRouterPluginContext = createRouterPluginContext()) and change the
fallback in TanStackRouterGeneratorEsbuild (and
TanStackRouterCodeSplitterEsbuild) from routerPluginContext ??
createRouterPluginContext() to routerPluginContext ?? defaultRouterPluginContext
so both plugins share the same routesByFile map populated by
createRouterPluginContext.

In `@packages/router-plugin/src/rspack.ts`:
- Around line 55-66: The two exports TanStackRouterGeneratorRspack and
TanStackRouterCodeSplitterRspack each create a fresh RouterPluginContext which
breaks metadata sharing; fix by introducing a single module-level default
context (e.g., a const defaultRouterPluginContext = createRouterPluginContext())
and change both functions to use routerPluginContext ??
defaultRouterPluginContext so callers can still pass a context but the default
is shared across both generator and code-splitter; update both function bodies
(references to pluginContext in TanStackRouterGeneratorRspack and
TanStackRouterCodeSplitterRspack) to use this shared default.

In `@packages/router-plugin/src/webpack.ts`:
- Around line 40-48: Both TanStackRouterGeneratorWebpack and
TanStackRouterCodeSplitterWebpack create fresh RouterPluginContext instances
when none is passed, causing route metadata to be isolated between the two
exporters; fix by creating a single module-scoped default context (e.g.,
defaultRouterPluginContext = createRouterPluginContext()) and have both
TanStackRouterGeneratorWebpack and TanStackRouterCodeSplitterWebpack use that
default when routerPluginContext is undefined (instead of calling
createRouterPluginContext() inside each function), ensuring
createRouterGeneratorPlugin and the code-splitter share the same context unless
a caller explicitly provides one.

---

Nitpick comments:
In `@packages/router-plugin/tests/router-plugin-context.test.ts`:
- Around line 79-180: Add a wrapper-level regression test that wires a single
shared RouterPluginContext through the public wrapper exports (the
TanStackRouterGenerator* and TanStackRouterCodeSplitter* entrypoints) instead of
calling the internal plugin factories directly; create a RouterPluginContext via
createRouterPluginContext(), populate routesByFile for a sample route file,
instantiate the public wrapper generator and code-splitter (the exported
TanStackRouterGenerator* and TanStackRouterCodeSplitter* functions/classes), run
their configure/transform flows (similar to existing tests using
createRouterCodeSplitterPlugin/createRouterHmrPlugin) and assert the generated
output contains the expected HMR/fast-refresh anchor or code-splitting markers;
this ensures you reference the public wrapper symbols rather than internal
plugin constructors while keeping the test structure similar to the existing
'router plugin context' cases.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7fbe4b23-1a2f-4b17-8aec-132a647c4de8

📥 Commits

Reviewing files that changed from the base of the PR and between 44d2802 and 6dfa56a.

📒 Files selected for processing (9)
  • packages/router-plugin/src/core/router-code-splitter-plugin.ts
  • packages/router-plugin/src/core/router-generator-plugin.ts
  • packages/router-plugin/src/core/router-hmr-plugin.ts
  • packages/router-plugin/src/core/router-plugin-context.ts
  • packages/router-plugin/src/esbuild.ts
  • packages/router-plugin/src/rspack.ts
  • packages/router-plugin/src/vite.ts
  • packages/router-plugin/src/webpack.ts
  • packages/router-plugin/tests/router-plugin-context.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/router-plugin/src/core/router-plugin-context.ts
  • packages/router-plugin/src/core/router-hmr-plugin.ts
  • packages/router-plugin/src/core/router-generator-plugin.ts
  • packages/router-plugin/src/core/router-code-splitter-plugin.ts
  • packages/router-plugin/src/vite.ts

Comment thread packages/router-plugin/src/esbuild.ts
Comment thread packages/router-plugin/src/rspack.ts
Comment thread packages/router-plugin/src/webpack.ts
nx-cloud[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/router-plugin/src/vite.ts (1)

11-11: ⚡ Quick win

The new options alias drops strict config typing.

Partial<Config | (() => Config)> is not the same as “Partial<Config> or a config factory”. It weakens the public signature and makes it much easier for invalid option shapes to slip through unchecked. Keep this as an explicit union instead of wrapping the whole union in Partial.

Suggested type fix
-type RouterPluginOptions = Partial<Config | (() => Config)> | undefined
+type RouterPluginOptions = Partial<Config> | (() => Partial<Config>) | undefined

As per coding guidelines, **/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-plugin/src/vite.ts` at line 11, The RouterPluginOptions alias
currently wraps the union in Partial which weakens typing; change the type so it
is an explicit union of a partial config OR a config factory (e.g., replace the
current RouterPluginOptions definition with an explicit union like
Partial<Config> | (() => Config) | undefined) so that Config itself remains
strictly typed; update the type declaration for RouterPluginOptions in
packages/router-plugin/src/vite.ts accordingly and ensure references to
RouterPluginOptions and Config still compile under strict TypeScript.
packages/router-plugin/src/esbuild.ts (1)

12-12: ⚡ Quick win

RouterPluginOptions is typed too loosely for a public API.

Partial<Config | (() => Config)> does not model “partial config or config factory” correctly; it drops the intended shape and weakens consumer-side checking. This should stay a real union such as Partial<Config> | (() => Partial<Config>) | undefined or whatever exact input shape the core helpers accept.

Suggested type fix
-type RouterPluginOptions = Partial<Config | (() => Config)> | undefined
+type RouterPluginOptions = Partial<Config> | (() => Partial<Config>) | undefined

As per coding guidelines, **/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-plugin/src/esbuild.ts` at line 12, RouterPluginOptions is
currently too loose as Partial<Config | (() => Config)>; change it to an
explicit union that preserves either a partial config or a config factory, e.g.
declare RouterPluginOptions as Partial<Config> | (() => Partial<Config>) |
undefined (or the exact factory return type your core helpers expect) so
consumers get proper type-checking; update the type alias RouterPluginOptions in
esbuild.ts and ensure it matches the shape accepted by the core helpers that
consume Config.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/router-plugin/src/esbuild.ts`:
- Around line 14-15: The module-level defaultRouterPluginContext (created by
createRouterPluginContext) causes all
TanStackRouterGeneratorEsbuild/TanStackRouterCodeSplitterEsbuild instances to
share the same routesByFile namespace; change to create a fresh context per
instance (or scope routesByFile by resolved routesDirectory) by moving the
createRouterPluginContext call into the constructors/factory functions of
TanStackRouterGeneratorEsbuild and TanStackRouterCodeSplitterEsbuild (or by
keying the shared map by routesDirectory before any transform decision) so each
pair gets an isolated context and cannot see or collide with another pair’s
metadata.

In `@packages/router-plugin/src/rspack.ts`:
- Around line 13-14: The module-level defaultRouterPluginContext creates a
process-wide routesByFile map that causes standalone
TanStackRouterGeneratorRspack/TanStackRouterCodeSplitterRspack calls to share
metadata; instead, remove the shared module-level default and instantiate
createRouterPluginContext() per plugin instance when a context isn't provided.
Update the factory functions TanStackRouterGeneratorRspack and
TanStackRouterCodeSplitterRspack to call createRouterPluginContext() inside the
function (or accept and clone a provided context) so each invocation gets its
own context/ routesByFile map; replace usages of defaultRouterPluginContext
across the file (including the other occurrences you noted) to use the
instance-local context creation. Ensure semantics remain the same when a
user-supplied context is passed.

In `@packages/router-plugin/src/vite.ts`:
- Line 13: The module-level defaultRouterPluginContext created by
createRouterPluginContext() causes shared metadata across independent Vite
pairs; change to create per-pair context by removing the single module-level
instance and instead lazily create or cache a RouterPluginContext keyed by the
resolved routes directory (or create a fresh context each time) when
tanstackRouterGenerator() or tanStackRouterCodeSplitter() are invoked without an
explicit context; update those functions to call
createRouterPluginContext(resolvedRoutesDir) or use an internal Map keyed by
resolved routes dir to isolate metadata namespaces so each Vite pair gets its
own RouterPluginContext.

---

Nitpick comments:
In `@packages/router-plugin/src/esbuild.ts`:
- Line 12: RouterPluginOptions is currently too loose as Partial<Config | (() =>
Config)>; change it to an explicit union that preserves either a partial config
or a config factory, e.g. declare RouterPluginOptions as Partial<Config> | (()
=> Partial<Config>) | undefined (or the exact factory return type your core
helpers expect) so consumers get proper type-checking; update the type alias
RouterPluginOptions in esbuild.ts and ensure it matches the shape accepted by
the core helpers that consume Config.

In `@packages/router-plugin/src/vite.ts`:
- Line 11: The RouterPluginOptions alias currently wraps the union in Partial
which weakens typing; change the type so it is an explicit union of a partial
config OR a config factory (e.g., replace the current RouterPluginOptions
definition with an explicit union like Partial<Config> | (() => Config) |
undefined) so that Config itself remains strictly typed; update the type
declaration for RouterPluginOptions in packages/router-plugin/src/vite.ts
accordingly and ensure references to RouterPluginOptions and Config still
compile under strict TypeScript.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f7ef2660-513e-4ed3-8d2f-1ad04380008b

📥 Commits

Reviewing files that changed from the base of the PR and between 6dfa56a and faa4577.

📒 Files selected for processing (4)
  • packages/router-plugin/src/esbuild.ts
  • packages/router-plugin/src/rspack.ts
  • packages/router-plugin/src/vite.ts
  • packages/router-plugin/src/webpack.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/router-plugin/src/webpack.ts

Comment on lines +14 to 15
const defaultRouterPluginContext = createRouterPluginContext()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Module-level fallback context still couples standalone instances.

Line 14 makes every default TanStackRouterGeneratorEsbuild() / TanStackRouterCodeSplitterEsbuild() call share the same routesByFile namespace. If a config registers two standalone generator/code-splitter pairs, both splitters can still see the other pair’s metadata and hit the same double-transform path this PR is fixing. Use a fresh default context per pair/instance, or namespace the shared map by resolved routesDirectory before transform decisions are made.

Also applies to: 25-32, 44-52

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-plugin/src/esbuild.ts` around lines 14 - 15, The module-level
defaultRouterPluginContext (created by createRouterPluginContext) causes all
TanStackRouterGeneratorEsbuild/TanStackRouterCodeSplitterEsbuild instances to
share the same routesByFile namespace; change to create a fresh context per
instance (or scope routesByFile by resolved routesDirectory) by moving the
createRouterPluginContext call into the constructors/factory functions of
TanStackRouterGeneratorEsbuild and TanStackRouterCodeSplitterEsbuild (or by
keying the shared map by routesDirectory before any transform decision) so each
pair gets an isolated context and cannot see or collide with another pair’s
metadata.

Comment on lines +13 to +14
const defaultRouterPluginContext = createRouterPluginContext()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Rspack standalone wrappers still share one metadata namespace by default.

The module-level defaultRouterPluginContext keeps every default TanStackRouterGeneratorRspack() / TanStackRouterCodeSplitterRspack() call on the same routesByFile map. That preserves pairing for one generator/splitter set, but two standalone pairs in the same config can still bleed into each other and trigger cross-instance transforms. This should be isolated per pair/instance instead of falling back to a single process-wide context.

Also applies to: 57-68, 83-96

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-plugin/src/rspack.ts` around lines 13 - 14, The module-level
defaultRouterPluginContext creates a process-wide routesByFile map that causes
standalone TanStackRouterGeneratorRspack/TanStackRouterCodeSplitterRspack calls
to share metadata; instead, remove the shared module-level default and
instantiate createRouterPluginContext() per plugin instance when a context isn't
provided. Update the factory functions TanStackRouterGeneratorRspack and
TanStackRouterCodeSplitterRspack to call createRouterPluginContext() inside the
function (or accept and clone a provided context) so each invocation gets its
own context/ routesByFile map; replace usages of defaultRouterPluginContext
across the file (including the other occurrences you noted) to use the
instance-local context creation. Ensure semantics remain the same when a
user-supplied context is passed.


type RouterPluginOptions = Partial<Config | (() => Config)> | undefined

const defaultRouterPluginContext = createRouterPluginContext()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Shared default context defeats isolation for standalone Vite pairs.

Because Line 13 creates one module-level RouterPluginContext, every default tanstackRouterGenerator() / tanStackRouterCodeSplitter() instance ends up in the same metadata namespace. Two standalone pairs in one Vite config can still observe each other’s route records and reintroduce cross-instance transforms. This needs a per-pair default or an internal namespace keyed by the resolved routes directory.

Also applies to: 24-32, 43-51

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-plugin/src/vite.ts` at line 13, The module-level
defaultRouterPluginContext created by createRouterPluginContext() causes shared
metadata across independent Vite pairs; change to create per-pair context by
removing the single module-level instance and instead lazily create or cache a
RouterPluginContext keyed by the resolved routes directory (or create a fresh
context each time) when tanstackRouterGenerator() or
tanStackRouterCodeSplitter() are invoked without an explicit context; update
those functions to call createRouterPluginContext(resolvedRoutesDir) or use an
internal Map keyed by resolved routes dir to isolate metadata namespaces so each
Vite pair gets its own RouterPluginContext.

nx-cloud[bot]

This comment was marked as outdated.

@schiller-manuel schiller-manuel merged commit 96818b8 into main May 1, 2026
15 of 16 checks passed
@schiller-manuel schiller-manuel deleted the fix-7174 branch May 1, 2026 19:03
Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud Bot left a comment

Choose a reason for hiding this comment

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

Nx Cloud has identified a flaky task in your failed CI:

🔂 Since the failure was identified as flaky, we triggered a CI rerun by adding an empty commit to this branch.

Nx Cloud View detailed reasoning in Nx Cloud ↗


🎓 Learn more about Self-Healing CI on nx.dev

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: "Duplicate declaration hot" when using two tanstackRouter() plugin instances in dev

1 participant