Skip to content

feat: add router plugins#6593

Open
schiller-manuel wants to merge 3 commits intomainfrom
createStartContextBridge
Open

feat: add router plugins#6593
schiller-manuel wants to merge 3 commits intomainfrom
createStartContextBridge

Conversation

@schiller-manuel
Copy link
Contributor

@schiller-manuel schiller-manuel commented Feb 3, 2026

Summary by CodeRabbit

  • New Features

    • Adds a Context Bridge to surface selected server-side request data to client routes with example pages and devtools; includes global styles and a JSON-sorting utility.
  • Tests

    • Adds Playwright end-to-end suites validating context bridging, SSR behavior, query integration, and navigation.
  • Documentation

    • New "Context Bridge" guides for React and Solid and updated SSR-query integration docs with plugin-based examples.
  • Chores

    • Adds e2e project configs and updates package export/type surfaces to support plugins.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

Adds an end-to-end React Start context-bridge e2e app and docs, a createStartContextBridge utility with server dehydration/client hydration wiring, router and route changes to consume bridged context, SSR-query plugin migration to plugin API across integrations, Playwright tests, and router-core plugin typing and wiring changes.

Changes

Cohort / File(s) Summary
E2E: context-bridge scaffold
e2e/react-start/context-bridge/.gitignore, e2e/react-start/context-bridge/package.json, e2e/react-start/context-bridge/tsconfig.json, e2e/react-start/context-bridge/vite.config.ts, e2e/react-start/context-bridge/playwright.config.ts
Adds project scaffolding: ignore rules, package manifest, TS config, Vite config and Playwright config with integrated dev server and PORT/baseURL handling.
E2E: context-bridge app code
e2e/react-start/context-bridge/src/router.tsx, e2e/react-start/context-bridge/src/start.ts, e2e/react-start/context-bridge/src/routes/__root.tsx, e2e/react-start/context-bridge/src/routes/index.tsx, e2e/react-start/context-bridge/src/routes/next.tsx
New router wiring using Start context bridge, middleware (mwA/mwB/mwC) that augments request context, root shell and two file routes exposing server/global/bridged context.
E2E: context-bridge support files
e2e/react-start/context-bridge/src/utils/sortJson.ts, e2e/react-start/context-bridge/src/styles/app.css, e2e/react-start/context-bridge/src/routeTree.gen.ts
Adds JSON sorter, minimal CSS, and generated routeTree/type augmentations (including Register augmentation for SSR integration).
E2E: context-bridge tests
e2e/react-start/context-bridge/tests/context-bridge.spec.ts
Playwright tests validating server context, global Start context, bridged context contents and persistence after navigation to /next.
Context Bridge utility
packages/start-client-core/src/createStartContextBridge.ts, packages/start-client-core/src/index.tsx
Adds createStartContextBridge implementation (server dehydration + client hydration merging) and re-exports it from package index.
Router core: plugin system & types
packages/router-core/src/plugin.ts, packages/router-core/src/router.ts, packages/router-core/src/index.ts, packages/router-core/tests/plugin.test.ts
Introduces RouterPlugin types, PluginProvidedContext utilities, validates plugin-provided context, adds plugins?: Array<RouterPlugin> to Router options, wires plugin.setup(router) at runtime, and adds tests.
Router integrations: expose plugin types
packages/react-router/src/index.tsx, packages/solid-router/src/index.tsx, packages/vue-router/src/index.tsx
Re-exports RouterPlugin, RouterPluginTypes, PluginProvidedContext from router-core in framework packages.
SSR-query: plugin migration
packages/router-ssr-query-core/src/index.ts, packages/react-router-ssr-query/src/index.tsx, packages/solid-router-ssr-query/src/index.tsx, packages/vue-router-ssr-query/src/index.tsx
Adds createCoreSsrQueryPlugin / createSsrQueryPlugin factories and migrates existing setup* integration helpers to delegate to the new plugin-based APIs (wrapQueryClient option preserved).
Many router integration updates (e2e)
e2e/*/src/router.tsx (multiple projects)
Replace legacy setupRouterSsrQueryIntegration calls with plugin usage createSsrQueryPlugin({ queryClient }) and return router directly from createRouter; remove explicit context: { queryClient } wiring.
E2E multi-plugin scaffold & app
e2e/react-start/multi-plugin/* (package.json, tsconfig, vite.config.ts, playwright.config.ts, src/, tests/)
Adds a multi-plugin e2e app demonstrating SSR query + Start context bridge together, with routes, middleware, generated routeTree, tests, and supporting utilities/styles.
Route generator fixes
e2e/*/*/src/routeTree.gen.ts (various)
Small generated route metadata fixes (fullPath adjustments for some layout entries and new FileRoutesByPath entries for added routes).
Misc package.json export fix
packages/router-core/package.json
Updates export map type paths for isServer from client.* to development.*.

Sequence Diagram(s)

sequenceDiagram
  participant Test as Playwright
  participant DevServer as Vite Dev Server
  participant Server as Start Instance
  participant Browser as Client
  participant Router as TanStack Router Plugin System

  Test->>DevServer: start webServer (build + serve)
  DevServer->>Server: forward HTTP requests
  Server->>Server: mwA -> mwB -> mwC (augment request context)
  Server->>Server: router.dehydrate includes bridged data under bridgeKey
  Server->>Browser: serve HTML with dehydrated payload
  Browser->>Router: createRouter(...) with plugins (ssr-query, start-context-bridge)
  Router->>Router: plugin.hydrate merges bridged data into router.context
  Browser->>Router: render route -> component reads bridged context
  Test->>Browser: navigate to /next, Router handles client navigation and context persists
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Suggested reviewers

  • Sheraff
  • nlynzaad
  • chorobin

Poem

🐇 I nibble dehydrated crumbs with glee,

Middleware hums and bridges come to thee,
Server whispers travel in a key so small,
Client wakes them up and shares with all,
Hooray — routes sing, the rabbit hops the wall! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add router plugins' directly summarizes the main change: introducing a new router plugin system as the primary feature across multiple packages.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch createStartContextBridge

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

@nx-cloud
Copy link

nx-cloud bot commented Feb 3, 2026

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit 3d09332

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

☁️ Nx Cloud last updated this comment at 2026-02-06 18:53:26 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 3, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@6593

@tanstack/eslint-plugin-router

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

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@6593

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@6593

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@6593

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@6593

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@6593

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@6593

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@6593

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@6593

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6593

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@6593

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@6593

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@6593

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@6593

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@6593

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6593

@tanstack/vue-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-client@6593

@tanstack/vue-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-server@6593

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@6593

commit: 3d09332

@schiller-manuel schiller-manuel force-pushed the createStartContextBridge branch from 615893a to 3eec39b Compare February 3, 2026 23:38
Copy link
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

🤖 Fix all issues with AI agents
In `@e2e/react-start/context-bridge/package.json`:
- Around line 14-30: Update the package.json dependency specifiers for internal
TanStack packages to use the workspace protocol wildcard: replace "workspace:^"
with "workspace:*" for the packages named "@tanstack/react-router",
"@tanstack/react-router-devtools", "@tanstack/react-start", and
"@tanstack/router-e2e-utils" so they use workspace:* instead of workspace:^.

In `@e2e/react-start/context-bridge/src/routes/__root.tsx`:
- Around line 23-58: RootDocument renders the lazily-loaded RouterDevtools
(RouterDevtools) directly which will throw without a Suspense boundary; update
RootDocument to wrap the RouterDevtools invocation in a React.Suspense boundary
(with a lightweight fallback such as null or a small loader) so the lazy import
resolves correctly, and ensure React.Suspense is imported/available in the file;
keep the existing production-guarded RouterDevtools definition unchanged.

In `@e2e/react-start/context-bridge/vite.config.ts`:
- Around line 7-10: The Vite config currently hardcodes server.port to 3000
inside the defineConfig call; update the server.port assignment in
vite.config.ts to read process.env.VITE_SERVER_PORT and parse it as an integer
(fallback to 3000 when missing) so Vite respects the port Playwright provides;
modify the server.port expression used in defineConfig accordingly (use
parseInt(process.env.VITE_SERVER_PORT) with a default).
🧹 Nitpick comments (2)
e2e/react-start/context-bridge/src/utils/sortJson.ts (1)

1-17: Tighten typing to JSON values to avoid unsafe casts.

The current as T/as unknown as T casts sidestep strict typing. Consider a JSON-specific type to keep this utility safe and self-documenting.

♻️ Suggested refactor
+type JsonPrimitive = string | number | boolean | null
+type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }
+
-export function sortJson<T>(value: T): T {
+export function sortJson(value: JsonValue): JsonValue {
   if (Array.isArray(value)) {
-    return value.map(sortJson) as T
+    return value.map(sortJson)
   }

   if (!value || typeof value !== 'object') {
     return value
   }

-  const obj = value as Record<string, unknown>
-  const out: Record<string, unknown> = {}
+  const obj = value as Record<string, JsonValue>
+  const out: Record<string, JsonValue> = {}

   for (const key of Object.keys(obj).sort()) {
     out[key] = sortJson(obj[key])
   }

-  return out as T
+  return out
 }

As per coding guidelines: Use TypeScript strict mode with extensive type safety for all code.

packages/start-client-core/src/createStartContextBridge.ts (1)

70-80: Avoid any on the hydrate payload to preserve strict typing.

dehydrated: any and related casts bypass the strict-mode intent. Prefer unknown + narrowing (or a shared Dehydrated type) to keep safety intact.

♻️ Suggested refactor
-      router.options.hydrate = async (dehydrated: any) => {
-        await ogHydrate?.(dehydrated)
-        const selected = dehydrated?.[key] as
-          | Record<string, unknown>
-          | undefined
-        if (!selected) return
+      router.options.hydrate = async (dehydrated: unknown) => {
+        await ogHydrate?.(dehydrated as unknown)
+        if (!dehydrated || typeof dehydrated !== 'object') return
+        const selected = (dehydrated as Record<string, unknown>)[key]
+        if (!selected || typeof selected !== 'object') return

         router.update({
-          context: safeObjectMerge(router.options.context, selected),
+          context: safeObjectMerge(
+            router.options.context,
+            selected as Record<string, unknown>,
+          ),
         })
       }

As per coding guidelines: Use TypeScript strict mode with extensive type safety for all code.

Comment on lines +14 to +30
"dependencies": {
"@tanstack/react-router": "workspace:^",
"@tanstack/react-router-devtools": "workspace:^",
"@tanstack/react-start": "workspace:^",
"express": "^5.1.0",
"http-proxy-middleware": "^3.0.5",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/router-e2e-utils": "workspace:^",
"@types/node": "^22.10.2",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use workspace:* for internal TanStack dependencies.

🔧 Suggested update
   "dependencies": {
-    "@tanstack/react-router": "workspace:^",
-    "@tanstack/react-router-devtools": "workspace:^",
-    "@tanstack/react-start": "workspace:^",
+    "@tanstack/react-router": "workspace:*",
+    "@tanstack/react-router-devtools": "workspace:*",
+    "@tanstack/react-start": "workspace:*",
     "express": "^5.1.0",
     "http-proxy-middleware": "^3.0.5",
     "react": "^19.0.0",
     "react-dom": "^19.0.0"
   },
   "devDependencies": {
     "@playwright/test": "^1.50.1",
     "@tailwindcss/vite": "^4.1.18",
-    "@tanstack/router-e2e-utils": "workspace:^",
+    "@tanstack/router-e2e-utils": "workspace:*",

As per coding guidelines: Use workspace protocol workspace:* for internal dependencies in package.json files.

📝 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.

Suggested change
"dependencies": {
"@tanstack/react-router": "workspace:^",
"@tanstack/react-router-devtools": "workspace:^",
"@tanstack/react-start": "workspace:^",
"express": "^5.1.0",
"http-proxy-middleware": "^3.0.5",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/router-e2e-utils": "workspace:^",
"@types/node": "^22.10.2",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
"dependencies": {
"@tanstack/react-router": "workspace:*",
"@tanstack/react-router-devtools": "workspace:*",
"@tanstack/react-start": "workspace:*",
"express": "^5.1.0",
"http-proxy-middleware": "^3.0.5",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/router-e2e-utils": "workspace:*",
"@types/node": "^22.10.2",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
🤖 Prompt for AI Agents
In `@e2e/react-start/context-bridge/package.json` around lines 14 - 30, Update the
package.json dependency specifiers for internal TanStack packages to use the
workspace protocol wildcard: replace "workspace:^" with "workspace:*" for the
packages named "@tanstack/react-router", "@tanstack/react-router-devtools",
"@tanstack/react-start", and "@tanstack/router-e2e-utils" so they use
workspace:* instead of workspace:^.

Comment on lines +23 to +58
const RouterDevtools =
process.env.NODE_ENV === 'production'
? () => null
: React.lazy(() =>
import('@tanstack/react-router-devtools').then((res) => ({
default: res.TanStackRouterDevtools,
})),
)

function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
<div className="p-2 flex gap-3 text-lg">
<Link
to="/"
activeOptions={{ exact: true }}
activeProps={{ className: 'font-bold' }}
>
Home
</Link>
<Link
to="/next"
activeOptions={{ exact: true }}
activeProps={{ className: 'font-bold' }}
>
Next
</Link>
</div>
<hr />
{children}
<RouterDevtools position="bottom-right" />
<Scripts />
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

React.lazy Suspense boundary requirement React 18 19 runtime error

💡 Result:

In React 18 and React 19, a React.lazy() component must be rendered under an ancestor <Suspense> boundary because rendering a lazy component while its code is still loading “suspends” (it throws a Promise). React then walks up the rendered tree looking for the nearest <Suspense> to show its fallback; if it can’t find one, you’ll get a runtime failure/blank screen. [1]

Key “boundary requirement” gotchas that commonly cause runtime errors:

  • The <Suspense> must be above the lazy component in the rendered tree. A <Suspense> inside the component that suspends won’t help, because React can’t reach it when that component suspends during its own render. [2]
  • If you see A component suspended while responding to synchronous input… in React 18+, it usually means a user-driven synchronous update (navigation, typing, click) caused something to suspend (often a lazy route or Suspense-based data). Typical fixes are:
    • move/add a higher Suspense boundary (e.g., wrap your router / route outlet), and/or
    • wrap the update that triggers the suspend in startTransition. [3]

Minimal correct pattern

import { Suspense, lazy } from "react";

const Page = lazy(() => import("./Page"));

export function App() {
  return (
    <Suspense fallback={<div>Loading…</div>}>
      <Page />
    </Suspense>
  );
}

Sources: React lazy docs (v19.2) [1], explanation of boundary placement (React walks up to nearest <Suspense>) [2], React 18 runtime error (“suspended while responding to synchronous input”) discussion and fixes [3].

[1] (react.dev)
[2] (stackoverflow.com)
[3] (stackoverflow.com)

Citations:


Wrap the lazy devtools in a Suspense boundary.
Rendering a React.lazy component without Suspense will throw at runtime. The <Suspense> boundary must be placed above the lazy component in the rendered tree for React to properly handle code-splitting and suspense.

✅ Suggested fix
-        <RouterDevtools position="bottom-right" />
+        <React.Suspense fallback={null}>
+          <RouterDevtools position="bottom-right" />
+        </React.Suspense>
📝 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.

Suggested change
const RouterDevtools =
process.env.NODE_ENV === 'production'
? () => null
: React.lazy(() =>
import('@tanstack/react-router-devtools').then((res) => ({
default: res.TanStackRouterDevtools,
})),
)
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
<div className="p-2 flex gap-3 text-lg">
<Link
to="/"
activeOptions={{ exact: true }}
activeProps={{ className: 'font-bold' }}
>
Home
</Link>
<Link
to="/next"
activeOptions={{ exact: true }}
activeProps={{ className: 'font-bold' }}
>
Next
</Link>
</div>
<hr />
{children}
<RouterDevtools position="bottom-right" />
<Scripts />
const RouterDevtools =
process.env.NODE_ENV === 'production'
? () => null
: React.lazy(() =>
import('@tanstack/react-router-devtools').then((res) => ({
default: res.TanStackRouterDevtools,
})),
)
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
<div className="p-2 flex gap-3 text-lg">
<Link
to="/"
activeOptions={{ exact: true }}
activeProps={{ className: 'font-bold' }}
>
Home
</Link>
<Link
to="/next"
activeOptions={{ exact: true }}
activeProps={{ className: 'font-bold' }}
>
Next
</Link>
</div>
<hr />
{children}
<React.Suspense fallback={null}>
<RouterDevtools position="bottom-right" />
</React.Suspense>
<Scripts />
🤖 Prompt for AI Agents
In `@e2e/react-start/context-bridge/src/routes/__root.tsx` around lines 23 - 58,
RootDocument renders the lazily-loaded RouterDevtools (RouterDevtools) directly
which will throw without a Suspense boundary; update RootDocument to wrap the
RouterDevtools invocation in a React.Suspense boundary (with a lightweight
fallback such as null or a small loader) so the lazy import resolves correctly,
and ensure React.Suspense is imported/available in the file; keep the existing
production-guarded RouterDevtools definition unchanged.

Comment on lines +7 to +10
export default defineConfig({
server: {
port: 3000,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify port alignment between Vite and Playwright config
rg -n "getTestServerPort|baseURL|port" e2e/react-start/context-bridge/playwright.config.ts e2e/react-start/context-bridge/vite.config.ts

Repository: TanStack/router

Length of output: 1541


🏁 Script executed:

cat e2e/react-start/context-bridge/vite.config.ts

Repository: TanStack/router

Length of output: 504


🏁 Script executed:

cat e2e/react-start/context-bridge/playwright.config.ts

Repository: TanStack/router

Length of output: 788


Vite config must respect the VITE_SERVER_PORT environment variable that Playwright passes.

The vite.config.ts hardcodes port: 3000, but playwright.config.ts is configured to pass a dynamically-derived port via the VITE_SERVER_PORT environment variable (line 16 in playwright.config.ts: VITE_SERVER_PORT=${PORT}). The Vite config ignores this, causing test failures when the Playwright webServer tries to reach the dev server on the derived port while Vite listens on 3000.

Update vite.config.ts to read the environment variable:

server: {
  port: process.env.VITE_SERVER_PORT ? parseInt(process.env.VITE_SERVER_PORT) : 3000,
},
🤖 Prompt for AI Agents
In `@e2e/react-start/context-bridge/vite.config.ts` around lines 7 - 10, The Vite
config currently hardcodes server.port to 3000 inside the defineConfig call;
update the server.port assignment in vite.config.ts to read
process.env.VITE_SERVER_PORT and parse it as an integer (fallback to 3000 when
missing) so Vite respects the port Playwright provides; modify the server.port
expression used in defineConfig accordingly (use
parseInt(process.env.VITE_SERVER_PORT) with a default).

@schiller-manuel schiller-manuel force-pushed the createStartContextBridge branch from 3eec39b to 2cd5db8 Compare February 3, 2026 23:43
Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/solid-router/src/router.ts (1)

67-73: ⚠️ Potential issue | 🟡 Minor

JSDoc links reference React instead of Solid framework.

The defaultOnCatch property documentation links point to React framework docs instead of Solid:

  • Line 70: docs/framework/react/api/router/...
  • Line 71: docs/framework/react/guide/...

These should reference the Solid framework documentation for consistency with the rest of this file.

📝 Proposed fix
     /**
      * The default `onCatch` handler for errors caught by the Router ErrorBoundary
      *
-     * `@link` [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultoncatch-property)
-     * `@link` [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#handling-errors-with-routeoptionsoncatch)
+     * `@link` [API Docs](https://tanstack.com/router/latest/docs/framework/solid/api/router/RouterOptionsType#defaultoncatch-property)
+     * `@link` [Guide](https://tanstack.com/router/latest/docs/framework/solid/guide/data-loading#handling-errors-with-routeoptionsoncatch)
      */
     defaultOnCatch?: (error: Error) => void
docs/router/integrations/query.md (1)

150-155: ⚠️ Potential issue | 🟡 Minor

Bug: context is not defined in the loader scope.

The loader function destructures params but context is not destructured. The code should be:

 export const Route = createFileRoute('/user/$id')({
-  loader: ({ params }) => {
+  loader: ({ context, params }) => {
     // do not await this nor return the promise, just kick off the query to stream it to the client
     context.queryClient.fetchQuery(userQuery(params.id))
   },
 })
🤖 Fix all issues with AI agents
In `@e2e/react-start/multi-plugin/package.json`:
- Around line 17-20: Update the internal `@tanstack` package dependency versions
to use the workspace protocol with a wildcard: replace instances of
"workspace:^" with "workspace:*" for the packages "@tanstack/react-router",
"@tanstack/react-router-devtools", "@tanstack/react-router-ssr-query", and
"@tanstack/react-start"; ensure all four dependency entries use "workspace:*" so
they match the project's workspace dependency guideline.

In `@e2e/react-start/multi-plugin/src/routes/__root.tsx`:
- Around line 3-9: The import list includes an unused symbol Outlet which ESLint
flags; remove Outlet from the named imports at the top (the import statement
that currently imports HeadContent, Link, Outlet, Scripts,
createRootRouteWithContext) so only used symbols remain (e.g., HeadContent,
Link, Scripts, createRootRouteWithContext), ensuring no other references to
Outlet exist in the file before committing.
- Around line 25-60: Wrap the lazy RouterDevtools usage in a React Suspense
boundary inside RootDocument: when rendering <RouterDevtools
position="bottom-right" /> replace it with a Suspense wrapper (e.g.,
<React.Suspense fallback={null}>...</React.Suspense>) so the lazy-loaded
RouterDevtools can suspend safely; keep ReactQueryDevtools as-is and ensure you
reference the existing RouterDevtools symbol and the RootDocument component.
🧹 Nitpick comments (3)
packages/router-core/src/index.ts (1)

459-464: Consider using @internal instead of @private for these exports.

The @private JSDoc annotation is inconsistent with these being public exports from the package index. Since the AI summary indicates these types are consumed by other packages (react-router, solid-router, vue-router), consider using @internal to indicate they're meant for internal TanStack package use only, or remove the annotation if external consumption is acceptable.

📝 Suggested annotation change
-/** `@private` */
+/** `@internal` */
 export type {
   RouterPlugin,
   RouterPluginTypes,
   PluginProvidedContext,
 } from './plugin'
docs/start/framework/react/guide/context-bridge.md (1)

115-119: Consecutive blockquotes should not have blank lines between them.

The markdownlint tool flags a blank line between blockquotes (MD028). If these are related warnings, consider merging them into a single blockquote or separating them with regular text.

📝 Suggested fix
 > [!IMPORTANT]
 > The return value of `select` must be serializable. Do not include functions, class instances, or other non-serializable values. The type system will warn you if you try to return non-serializable data.
-
+>
 > [!WARNING]
 > Be careful not to bridge sensitive values (API keys, secrets, tokens) since they will be serialized and sent to the client in the HTML payload.

Alternatively, add explanatory text between them if they represent distinct concerns.

packages/router-core/src/router.ts (1)

4-4: ESLint import order flag (optional fix).

Static analysis indicates the type import from ./plugin should occur after the import from ./rewrite per the project's import ordering rules.

🔧 Suggested reorder

Move the ./plugin import after the ./rewrite import at line 44:

 import {
   composeRewrites,
   executeRewriteInput,
   executeRewriteOutput,
   rewriteBasepath,
 } from './rewrite'
+import type { PluginProvidedContext, RouterPlugin } from './plugin'

And remove it from line 4.

Comment on lines +17 to +20
"@tanstack/react-router": "workspace:^",
"@tanstack/react-router-devtools": "workspace:^",
"@tanstack/react-router-ssr-query": "workspace:^",
"@tanstack/react-start": "workspace:^",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all workspace protocol usages in e2e package.json files
find e2e -name "package.json" -type f -exec grep -l "workspace:" {} \; | while read file; do
  echo "=== $file ==="
  grep "workspace:" "$file" | head -20
done

Repository: TanStack/router

Length of output: 28399


Change workspace protocol to workspace:* for all internal dependencies.

The coding guideline requires using workspace:* for internal dependencies, but this file uses workspace:^ on lines 17-20. Update all internal @tanstack package dependencies to use workspace:* instead of workspace:^ for consistency with the documented requirements.

🤖 Prompt for AI Agents
In `@e2e/react-start/multi-plugin/package.json` around lines 17 - 20, Update the
internal `@tanstack` package dependency versions to use the workspace protocol
with a wildcard: replace instances of "workspace:^" with "workspace:*" for the
packages "@tanstack/react-router", "@tanstack/react-router-devtools",
"@tanstack/react-router-ssr-query", and "@tanstack/react-start"; ensure all four
dependency entries use "workspace:*" so they match the project's workspace
dependency guideline.

Comment on lines +3 to +9
import {
HeadContent,
Link,
Outlet,
Scripts,
createRootRouteWithContext,
} from '@tanstack/react-router'
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused Outlet import.
ESLint flags it as unused.

🧹 Proposed fix
 import {
   HeadContent,
   Link,
-  Outlet,
   Scripts,
   createRootRouteWithContext,
 } from '@tanstack/react-router'
📝 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.

Suggested change
import {
HeadContent,
Link,
Outlet,
Scripts,
createRootRouteWithContext,
} from '@tanstack/react-router'
import {
HeadContent,
Link,
Scripts,
createRootRouteWithContext,
} from '@tanstack/react-router'
🧰 Tools
🪛 ESLint

[error] 6-6: 'Outlet' is defined but never used.

(unused-imports/no-unused-imports)

🤖 Prompt for AI Agents
In `@e2e/react-start/multi-plugin/src/routes/__root.tsx` around lines 3 - 9, The
import list includes an unused symbol Outlet which ESLint flags; remove Outlet
from the named imports at the top (the import statement that currently imports
HeadContent, Link, Outlet, Scripts, createRootRouteWithContext) so only used
symbols remain (e.g., HeadContent, Link, Scripts, createRootRouteWithContext),
ensuring no other references to Outlet exist in the file before committing.

Comment on lines +25 to +60
const RouterDevtools =
process.env.NODE_ENV === 'production'
? () => null
: React.lazy(() =>
import('@tanstack/react-router-devtools').then((res) => ({
default: res.TanStackRouterDevtools,
})),
)

function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
<div className="p-2 flex gap-3 text-lg">
<Link
to="/"
activeOptions={{ exact: true }}
activeProps={{ className: 'font-bold' }}
>
Home
</Link>
<Link
to="/query-test"
activeOptions={{ exact: true }}
activeProps={{ className: 'font-bold' }}
>
Query Test
</Link>
</div>
<hr />
{children}
<RouterDevtools position="bottom-right" />
<ReactQueryDevtools buttonPosition="bottom-left" />
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

React.lazy() Suspense requirement documentation

💡 Result:

React docs explicitly state that a component created with React.lazy() must be rendered within a <Suspense> boundary so React has a fallback UI to show while the code chunk is loading. You can wrap the lazy component itself or any parent above it in the tree. [1] [2]

Official documentation pages:

  • lazy reference (shows “Lazy-loading components with Suspense” and the requirement to wrap in <Suspense>): [1]
  • <Suspense> reference (explains how Suspense shows fallback when children “suspend,” including lazy-loaded code): [2]
  • Legacy “Code-Splitting” guide (states “The lazy component should then be rendered inside a Suspense component”): [3]

Sources:
[1] https://react.dev/reference/react/lazy
[2] https://react.dev/reference/react/Suspense
[3] https://legacy.reactjs.org/docs/code-splitting.html


🏁 Script executed:

cat -n e2e/react-start/multi-plugin/src/routes/__root.tsx | head -70

Repository: TanStack/router

Length of output: 2182


Wrap the lazy RouterDevtools in Suspense.

React.lazy components must be rendered inside a Suspense boundary per React documentation. In development, RouterDevtools is a lazy component that will suspend while loading; without Suspense, this causes runtime warnings/errors.

✅ Suggested fix
-        <RouterDevtools position="bottom-right" />
+        <React.Suspense fallback={null}>
+          <RouterDevtools position="bottom-right" />
+        </React.Suspense>
🤖 Prompt for AI Agents
In `@e2e/react-start/multi-plugin/src/routes/__root.tsx` around lines 25 - 60,
Wrap the lazy RouterDevtools usage in a React Suspense boundary inside
RootDocument: when rendering <RouterDevtools position="bottom-right" /> replace
it with a Suspense wrapper (e.g., <React.Suspense
fallback={null}>...</React.Suspense>) so the lazy-loaded RouterDevtools can
suspend safely; keep ReactQueryDevtools as-is and ensure you reference the
existing RouterDevtools symbol and the RootDocument component.

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.

1 participant