Skip to content

Conversation

@birkskyum
Copy link
Member

@birkskyum birkskyum commented Dec 17, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Added server-side rendering (SSR) support with selective rendering capabilities for improved performance and SEO
    • Added shell component support to wrap root routes
    • Exported ClientOnly component for granular control over client-side rendering
  • Tests

    • Added end-to-end test suite for selective SSR scenarios
  • Chores

    • Added project configuration and infrastructure for SSR testing

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 17, 2025

Walkthrough

This PR introduces a new end-to-end test project demonstrating selective server-side rendering (SSR) for Vue, along with core Vue Router framework enhancements to support SSR behavior, including shell components, ClientOnly rendering, and improved pending state propagation through match data.

Changes

Cohort / File(s) Summary
E2E SSR Project Configuration
e2e/vue-start/selective-ssr/.gitignore, .prettierignore, package.json, playwright.config.ts, postcss.config.mjs, tsconfig.json, vite.config.ts
Adds project setup, build/test ignore patterns, dependency manifest, Playwright e2e config with dynamic test server port, PostCSS/Tailwind integration, TypeScript config with strict mode and path aliases, and Vite config with TanStack Vue Start and Vue plugins.
E2E SSR Project Routes
e2e/vue-start/selective-ssr/src/router.tsx, routeTree.gen.ts, routes/__root.tsx, routes/index.tsx, routes/posts.tsx, routes/posts.$postId.tsx
Defines router factory, generated route tree with full path/ID/search mappings, and file-based routes with SSR guards, beforeLoad/loader hooks, and client/server context validation. Root route includes head meta tags, shell component, and outlet rendering. Child routes render test UI with SSR data display and outlet.
E2E SSR Project Support
e2e/vue-start/selective-ssr/src/search.ts, src/styles/app.css
Adds Zod schema for route search parameters (ssr, expected data location, render context) and base Tailwind CSS styling with light/dark mode support.
E2E SSR Testing
e2e/vue-start/selective-ssr/tests/app.spec.ts
Introduces Playwright test suite validating 7 test cases, each exercising different SSR/rendering configurations by navigating routes and asserting expected data/render locations match actual loader and context values.
Vue Router Core—SSR & Rendering
packages/vue-router/src/Match.tsx
Extends router state selector to include ssr and _displayPending fields; propagates these into MatchInner; adds pending/ClientOnly rendering paths; integrates scroll restoration for root routes; adds shell component wrapping for root routes; handles hydration by avoiding Fragment mismatches.
Vue Router Core—Public API
packages/vue-router/src/index.tsx
Exports new ClientOnly component from ./ClientOnly.
Vue Router Core—Lifecycle & Component Loading
packages/vue-router/src/Transitioner.tsx, src/lazyRouteComponent.tsx
Transitioner: moves pending-to-idle state transition to dedicated watcher branch and adds mount-time initialization. lazyRouteComponent: removes exported ClientOnly and useHydrated helpers, now imports ClientOnly and uses slot-based pattern for SSR-disabled case.
Vue Router Tests
packages/vue-router/tests/shellComponent.test.tsx, tests/store-updates-during-navigation.test.tsx
Adds new test for shell component wrapping root route content; updates existing navigation test expectation from 7 to 6 updates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Match.tsx: Dense SSR/pending rendering logic with hydration handling requires careful verification of control flow and state propagation.
  • lazyRouteComponent.tsx: Refactored ClientOnly usage pattern from children-based to slot-based approach; verify slot consumption is correct.
  • Transitioner.tsx: State management refactoring around pending-to-idle transitions; confirm initialization and watcher logic are sound.
  • routeTree.gen.ts: Verify generated route tree structure, module augmentations, and type safety.
  • Route definitions (__root.tsx, posts.tsx, posts.$postId.tsx): Validate SSR guards, beforeLoad/loader hooks, and client/server context checks align with test expectations.

Possibly related PRs

  • TanStack/router#6118: Modifies SSR/hydration script paths and lifecycle (packages/vue-router ScriptOnce/Scripts).
  • TanStack/router#5964: Adds ssr field to route matches and propagates into hydration logic.
  • TanStack/router#6110: Adds file-based route tree and @tanstack/vue-router module augmentations.

Suggested labels

package: vue-start-client

Suggested reviewers

  • schiller-manuel
  • nlynzaad

Poem

🐰 Routes now render with selective care,
On server first, then client prep,
Shell components wrap the viewport fair,
While pending states take their step.
ClientOnly whispers "hydrate, don't leap"—
SSR dreams for Vue run deep! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'test(vue-start): selective-ssr suite' directly relates to the main changes, which add a comprehensive end-to-end test suite for selective server-side rendering with Vue Start.
✨ 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 vue-start-selective-ssr

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 and usage tips.

@nx-cloud
Copy link

nx-cloud bot commented Dec 17, 2025

View your CI Pipeline Execution ↗ for commit be5f56f

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

☁️ Nx Cloud last updated this comment at 2025-12-17 17:05:26 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 17, 2025

More templates

@tanstack/arktype-adapter

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

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@6124

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@6124

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: be5f56f

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: 2

🧹 Nitpick comments (7)
packages/vue-router/src/Transitioner.tsx (1)

139-145: Good addition for SSR state initialization.

This ensures that when the component mounts and nothing is pending (e.g., SSR hydration scenario where routes are pre-resolved), the store status transitions from 'pending' to 'idle'. The conditional check s.status === 'pending' provides safety against unnecessary updates.

Consider adding a brief comment explaining this handles the SSR hydration case for better code clarity:

  Vue.onMounted(() => {
    isMounted.value = true
+   // In SSR hydration scenarios, routes may already be resolved at mount time
+   // Transition status from pending to idle if nothing is pending
    if (!isAnyPending.value) {
      router.__store.setState((s) =>
        s.status === 'pending'
          ? { ...s, status: 'idle', resolvedLocation: s.location }
          : s,
      )
    }
  })
packages/vue-router/src/Match.tsx (2)

114-123: Consider stronger typing for ShellComponent.

The as any type assertion works but loses type safety. Consider using the actual component type if available in RootRouteOptions.


354-368: Consider consolidating duplicate pending component logic.

Both _displayPending and _forcePending checks render PendingComponent identically. Consider consolidating:

-      if (match.value._displayPending) {
-        const PendingComponent =
-          route.value.options.pendingComponent ??
-          router.options.defaultPendingComponent
-
-        return PendingComponent ? Vue.h(PendingComponent) : null
-      }
-
-      if (match.value._forcePending) {
+      if (match.value._displayPending || match.value._forcePending) {
         const PendingComponent =
           route.value.options.pendingComponent ??
           router.options.defaultPendingComponent

         return PendingComponent ? Vue.h(PendingComponent) : null
       }

Unless these are expected to diverge in behavior, consolidation improves maintainability.

e2e/vue-start/selective-ssr/package.json (1)

14-22: Use workspace:* for internal dependencies.

The coding guidelines specify using workspace:* for internal dependencies. This file uses workspace:^ instead:

   "dependencies": {
-    "@tanstack/vue-router": "workspace:^",
-    "@tanstack/vue-start": "workspace:^",
+    "@tanstack/vue-router": "workspace:*",
+    "@tanstack/vue-start": "workspace:*",
     "vue": "^3.5.25",
     "zod": "^3.24.2"
   },
   "devDependencies": {
     "@tailwindcss/postcss": "^4.1.15",
-    "@tanstack/router-e2e-utils": "workspace:^",
+    "@tanstack/router-e2e-utils": "workspace:*",
e2e/vue-start/selective-ssr/src/routes/posts.$postId.tsx (1)

2-2: Minor: Non-standard zod import style.

Using import z from 'zod' works but the conventional import is import { z } from 'zod'. Consider updating for consistency with common patterns.

-import z from 'zod'
+import { z } from 'zod'
e2e/vue-start/selective-ssr/src/routes/posts.tsx (1)

2-2: Minor: Non-standard zod import style (same as sibling route).

Same suggestion as posts.$postId.tsx - consider using import { z } from 'zod' for consistency.

-import z from 'zod'
+import { z } from 'zod'
e2e/vue-start/selective-ssr/src/routes/__root.tsx (1)

13-13: Inconsistent zod import style across route files.

This file uses import { z } from 'zod' while sibling routes (posts.tsx, posts.$postId.tsx) use import z from 'zod'. Consider aligning all files to use the same import style for consistency.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c77213 and be5f56f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • e2e/vue-start/selective-ssr/.gitignore (1 hunks)
  • e2e/vue-start/selective-ssr/.prettierignore (1 hunks)
  • e2e/vue-start/selective-ssr/package.json (1 hunks)
  • e2e/vue-start/selective-ssr/playwright.config.ts (1 hunks)
  • e2e/vue-start/selective-ssr/postcss.config.mjs (1 hunks)
  • e2e/vue-start/selective-ssr/src/routeTree.gen.ts (1 hunks)
  • e2e/vue-start/selective-ssr/src/router.tsx (1 hunks)
  • e2e/vue-start/selective-ssr/src/routes/__root.tsx (1 hunks)
  • e2e/vue-start/selective-ssr/src/routes/index.tsx (1 hunks)
  • e2e/vue-start/selective-ssr/src/routes/posts.$postId.tsx (1 hunks)
  • e2e/vue-start/selective-ssr/src/routes/posts.tsx (1 hunks)
  • e2e/vue-start/selective-ssr/src/search.ts (1 hunks)
  • e2e/vue-start/selective-ssr/src/styles/app.css (1 hunks)
  • e2e/vue-start/selective-ssr/tests/app.spec.ts (1 hunks)
  • e2e/vue-start/selective-ssr/tsconfig.json (1 hunks)
  • e2e/vue-start/selective-ssr/vite.config.ts (1 hunks)
  • packages/vue-router/src/Match.tsx (7 hunks)
  • packages/vue-router/src/Transitioner.tsx (2 hunks)
  • packages/vue-router/src/index.tsx (1 hunks)
  • packages/vue-router/src/lazyRouteComponent.tsx (2 hunks)
  • packages/vue-router/tests/shellComponent.test.tsx (1 hunks)
  • packages/vue-router/tests/store-updates-during-navigation.test.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • e2e/vue-start/selective-ssr/src/search.ts
  • packages/vue-router/src/Transitioner.tsx
  • e2e/vue-start/selective-ssr/src/router.tsx
  • e2e/vue-start/selective-ssr/src/routes/posts.$postId.tsx
  • packages/vue-router/tests/shellComponent.test.tsx
  • e2e/vue-start/selective-ssr/src/routes/posts.tsx
  • e2e/vue-start/selective-ssr/tests/app.spec.ts
  • e2e/vue-start/selective-ssr/src/routeTree.gen.ts
  • packages/vue-router/tests/store-updates-during-navigation.test.tsx
  • packages/vue-router/src/lazyRouteComponent.tsx
  • e2e/vue-start/selective-ssr/src/routes/__root.tsx
  • e2e/vue-start/selective-ssr/src/routes/index.tsx
  • packages/vue-router/src/index.tsx
  • e2e/vue-start/selective-ssr/vite.config.ts
  • e2e/vue-start/selective-ssr/playwright.config.ts
  • packages/vue-router/src/Match.tsx
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • e2e/vue-start/selective-ssr/src/search.ts
  • packages/vue-router/src/Transitioner.tsx
  • e2e/vue-start/selective-ssr/src/router.tsx
  • e2e/vue-start/selective-ssr/src/routes/posts.$postId.tsx
  • packages/vue-router/tests/shellComponent.test.tsx
  • e2e/vue-start/selective-ssr/src/routes/posts.tsx
  • e2e/vue-start/selective-ssr/tests/app.spec.ts
  • e2e/vue-start/selective-ssr/src/routeTree.gen.ts
  • packages/vue-router/tests/store-updates-during-navigation.test.tsx
  • packages/vue-router/src/lazyRouteComponent.tsx
  • e2e/vue-start/selective-ssr/src/routes/__root.tsx
  • e2e/vue-start/selective-ssr/src/routes/index.tsx
  • packages/vue-router/src/index.tsx
  • e2e/vue-start/selective-ssr/vite.config.ts
  • e2e/vue-start/selective-ssr/playwright.config.ts
  • packages/vue-router/src/Match.tsx
**/package.json

📄 CodeRabbit inference engine (AGENTS.md)

Use workspace protocol workspace:* for internal dependencies in package.json files

Files:

  • e2e/vue-start/selective-ssr/package.json
🧠 Learnings (11)
📚 Learning: 2025-11-02T16:16:24.898Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: In packages/start-client-core/src/client/hydrateStart.ts, the `import/no-duplicates` ESLint disable is necessary for imports from `#tanstack-router-entry` and `#tanstack-start-entry` because both aliases resolve to the same placeholder file (`fake-start-entry.js`) in package.json during static analysis, even though they resolve to different files at runtime.

Applied to files:

  • e2e/vue-start/selective-ssr/.prettierignore
  • e2e/vue-start/selective-ssr/src/router.tsx
  • e2e/vue-start/selective-ssr/src/routeTree.gen.ts
  • packages/vue-router/src/lazyRouteComponent.tsx
  • e2e/vue-start/selective-ssr/package.json
  • e2e/vue-start/selective-ssr/tsconfig.json
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router

Applied to files:

  • e2e/vue-start/selective-ssr/.prettierignore
  • e2e/vue-start/selective-ssr/src/router.tsx
  • e2e/vue-start/selective-ssr/src/routeTree.gen.ts
  • e2e/vue-start/selective-ssr/src/routes/__root.tsx
  • e2e/vue-start/selective-ssr/src/routes/index.tsx
  • e2e/vue-start/selective-ssr/tsconfig.json
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • e2e/vue-start/selective-ssr/.prettierignore
  • e2e/vue-start/selective-ssr/src/router.tsx
  • e2e/vue-start/selective-ssr/src/routeTree.gen.ts
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • e2e/vue-start/selective-ssr/.prettierignore
  • e2e/vue-start/selective-ssr/src/router.tsx
  • packages/vue-router/tests/shellComponent.test.tsx
  • e2e/vue-start/selective-ssr/tests/app.spec.ts
  • e2e/vue-start/selective-ssr/src/routeTree.gen.ts
  • packages/vue-router/tests/store-updates-during-navigation.test.tsx
  • e2e/vue-start/selective-ssr/src/routes/__root.tsx
  • e2e/vue-start/selective-ssr/src/routes/index.tsx
📚 Learning: 2025-12-17T02:17:47.423Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:47.423Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.

Applied to files:

  • e2e/vue-start/selective-ssr/src/router.tsx
  • e2e/vue-start/selective-ssr/src/routeTree.gen.ts
  • packages/vue-router/src/lazyRouteComponent.tsx
  • e2e/vue-start/selective-ssr/src/routes/__root.tsx
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.

Applied to files:

  • e2e/vue-start/selective-ssr/src/routes/posts.$postId.tsx
  • e2e/vue-start/selective-ssr/src/routes/posts.tsx
  • e2e/vue-start/selective-ssr/tests/app.spec.ts
  • e2e/vue-start/selective-ssr/src/routes/__root.tsx
  • e2e/vue-start/selective-ssr/src/routes/index.tsx
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Use file-based routing in `src/routes/` directories or code-based routing with route definitions

Applied to files:

  • e2e/vue-start/selective-ssr/src/routes/posts.$postId.tsx
  • e2e/vue-start/selective-ssr/src/routeTree.gen.ts
  • e2e/vue-start/selective-ssr/src/routes/index.tsx
📚 Learning: 2025-10-09T12:59:02.129Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/src/styles/app.css:19-21
Timestamp: 2025-10-09T12:59:02.129Z
Learning: In e2e test directories (paths containing `e2e/`), accessibility concerns like outline suppression patterns are less critical since the code is for testing purposes, not production use.

Applied to files:

  • e2e/vue-start/selective-ssr/tests/app.spec.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/vue-router/src/index.tsx
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript strict mode with extensive type safety for all code

Applied to files:

  • e2e/vue-start/selective-ssr/tsconfig.json
📚 Learning: 2025-10-09T12:59:14.842Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/public/site.webmanifest:2-3
Timestamp: 2025-10-09T12:59:14.842Z
Learning: In e2e test fixtures (files under e2e directories), empty or placeholder values in configuration files like site.webmanifest are acceptable and should not be flagged unless the test specifically validates those fields.

Applied to files:

  • e2e/vue-start/selective-ssr/playwright.config.ts
🧬 Code graph analysis (6)
e2e/vue-start/selective-ssr/src/router.tsx (1)
packages/vue-router/src/index.tsx (2)
  • createRouter (268-268)
  • Register (87-87)
e2e/vue-start/selective-ssr/tests/app.spec.ts (1)
e2e/solid-start/selective-ssr/tests/app.spec.ts (1)
  • test (6-41)
e2e/vue-start/selective-ssr/src/routeTree.gen.ts (3)
packages/router-generator/tests/generator/types-disabled/routeTree.nonnested.snapshot.js (3)
  • PostsRoute (17-21)
  • PostsRouteWithChildren (42-42)
  • PostsRouteChildren (38-40)
packages/vue-router/src/index.tsx (3)
  • FileRouteTypes (161-161)
  • FileRoutesByPath (102-102)
  • Register (87-87)
e2e/vue-start/selective-ssr/src/router.tsx (1)
  • getRouter (4-11)
e2e/vue-start/selective-ssr/src/routes/index.tsx (4)
packages/vue-router/src/index.tsx (4)
  • Route (249-249)
  • createFileRoute (132-132)
  • linkOptions (143-143)
  • Link (143-143)
e2e/vue-start/selective-ssr/src/routes/__root.tsx (1)
  • Route (17-112)
e2e/vue-start/selective-ssr/src/routes/posts.$postId.tsx (1)
  • Route (5-82)
e2e/vue-start/selective-ssr/src/routes/posts.tsx (1)
  • Route (5-84)
e2e/vue-start/selective-ssr/playwright.config.ts (1)
scripts/set-ts-version.js (1)
  • packageJson (33-33)
packages/vue-router/src/Match.tsx (1)
packages/vue-router/src/index.tsx (8)
  • RootRouteOptions (104-104)
  • ClientOnly (349-349)
  • CatchNotFound (306-306)
  • CatchBoundary (128-128)
  • ErrorComponent (128-128)
  • isNotFound (307-307)
  • rootRouteId (12-12)
  • ScrollRestoration (283-283)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (23)
packages/vue-router/src/Transitioner.tsx (1)

211-217: Improved pending-to-idle state transition handling.

This ensures the store status is updated to 'idle' as soon as isAnyPending becomes false, before any events are emitted in the subsequent logic (lines 220-230). This ordering guarantees consistent state when events fire and aligns with the PR's goal of more predictable SSR rendering behavior.

The logic correctly distinguishes between:

  • Status update (lines 211-217): Runs when nothing is pending and status is still 'pending'
  • Event emission (lines 220-230): Runs when transitioning from pending to not-pending
e2e/vue-start/selective-ssr/.prettierignore (1)

1-4: LGTM!

Standard ignore patterns for build outputs and autogenerated files. The routeTree.gen.ts exclusion is appropriate since these files are autogenerated and should not be manually modified.

packages/vue-router/src/index.tsx (1)

349-349: LGTM!

Clean addition of ClientOnly to the public API, supporting the selective SSR functionality introduced in this PR.

e2e/vue-start/selective-ssr/tsconfig.json (1)

1-23: LGTM!

TypeScript configuration follows best practices with strict: true enabled, modern ES2022 target, and proper Vue JSX support. The bundler module resolution is appropriate for a Vite-based project.

e2e/vue-start/selective-ssr/playwright.config.ts (1)

1-34: LGTM!

Well-structured Playwright configuration with dynamic port allocation, proper build/start sequencing, and appropriate CI detection for server reuse.

packages/vue-router/src/Match.tsx (3)

71-72: LGTM!

SSR state propagation through the selector is correctly implemented, enabling downstream rendering logic to make SSR-aware decisions.


148-166: Verify _displayPending should trigger ClientOnly wrapping.

When _displayPending is true, the content is wrapped in ClientOnly with a pending fallback. Confirm this is intentional—if _displayPending is for transitions that happen after hydration, the ClientOnly wrapper may be unnecessary.


202-219: LGTM!

Good hydration-aware implementation. Returning a single child directly when possible avoids Vue Fragment hydration mismatches.

e2e/vue-start/selective-ssr/postcss.config.mjs (1)

1-5: LGTM! Correct Tailwind v4 PostCSS configuration.

The configuration correctly uses the @tailwindcss/postcss plugin, which is the standard setup for Tailwind CSS v4's new architecture.

packages/vue-router/tests/store-updates-during-navigation.test.tsx (1)

304-304: LGTM! Update count reduction reflects optimization.

The reduced expectation from 7 to 6 updates is consistent with the PR's goal of improving SSR behavior and router efficiency. Fewer store updates during navigation is a positive outcome.

e2e/vue-start/selective-ssr/vite.config.ts (1)

1-19: LGTM! Standard Vite configuration for Vue SSR testing.

The configuration correctly sets up TanStack Vue Start with Vue and Vue JSX support, along with TypeScript path resolution. The server port (3000) aligns with the e2e testing setup.

e2e/vue-start/selective-ssr/src/styles/app.css (1)

1-30: LGTM! Correct Tailwind v4 CSS setup.

The file properly uses Tailwind v4's new @import 'tailwindcss' syntax and sets up appropriate base styles with color scheme support and dark mode utilities.

e2e/vue-start/selective-ssr/src/search.ts (1)

1-16: LGTM! Well-structured Zod schema for SSR configuration.

The schema clearly defines the SSR test configuration options with appropriate literal types for data and render expectations, supporting the selective SSR test scenarios.

e2e/vue-start/selective-ssr/src/router.tsx (1)

1-17: LGTM! Standard router setup with proper type augmentation.

The router factory correctly integrates the generated route tree with scroll restoration enabled. The module augmentation ensures type safety throughout the application.

e2e/vue-start/selective-ssr/tests/app.spec.ts (1)

1-41: LGTM! Comprehensive e2e test suite for selective SSR.

The test suite thoroughly exercises all 7 test cases, verifying that data, loader, and context values match expectations for each route under different SSR configurations. The use of parallel assertions and proper waiting strategies ensures reliable test execution.

packages/vue-router/src/lazyRouteComponent.tsx (2)

3-3: LGTM!

Clean import of ClientOnly from its dedicated module, replacing the previously inlined implementation.


132-140: LGTM! Proper Vue 3 slot-based rendering pattern.

The updated ClientOnly usage correctly passes the fallback as a prop and the component render as a default slot function. This ensures the Outlet renders during SSR while the actual component loads client-side after hydration.

packages/vue-router/tests/shellComponent.test.tsx (1)

7-26: LGTM! Good coverage for shellComponent feature.

The test correctly verifies that shellComponent wraps the root route content. Using findByTestId (async) handles the initial router loading gracefully.

e2e/vue-start/selective-ssr/src/routes/posts.$postId.tsx (1)

5-81: LGTM! Well-structured route for selective SSR testing.

The route correctly implements:

  • Server-only ssr function with client-side guard
  • Environment-aware beforeLoad and loader with validation
  • Component with proper Vue ref access (search.value, loaderData.value, context.value)
  • Runtime checks that validate execution context for e2e test assertions
e2e/vue-start/selective-ssr/src/routes/posts.tsx (1)

5-84: LGTM! Parent route correctly structured with Outlet.

The route properly:

  • Validates the posts search parameter
  • Implements SSR guards and environment checks
  • Renders child routes via <Outlet />

Consistent with the established pattern for this e2e test suite.

e2e/vue-start/selective-ssr/src/routes/__root.tsx (2)

114-157: LGTM! Well-structured shell component.

RootDocument correctly:

  • Uses useRouterState with a selector to extract only needed state
  • Wraps dynamic router state in ClientOnly to prevent hydration mismatches
  • Properly invokes the default slot with optional chaining (slots.default?.())
  • Provides the complete HTML document structure for SSR

17-112: LGTM! Comprehensive root route configuration.

The root route properly configures:

  • Document head with meta tags and stylesheet
  • Search validation with ssrSchema
  • Server-only ssr function with client guard
  • Environment-aware beforeLoad and loader
  • shellComponent for document wrapper
  • Component with proper hook usage and test IDs
e2e/vue-start/selective-ssr/src/routeTree.gen.ts (1)

1-112: Autogenerated file - no review needed.

This file is automatically generated by TanStack Router and should not be manually modified or reviewed. Any changes would be overwritten by the code generator.

Based on learnings, files named routeTree.gen.ts in TanStack Router repositories are autogenerated and should not be reviewed.

Comment on lines +13 to +14
/public/build# Sentry Config File
.env.sentry-build-plugin
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

Fix comment placement.

The comment is concatenated with the path on line 13. This should be on a separate line for clarity:

-/public/build# Sentry Config File
+/public/build
+# Sentry Config File
📝 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
/public/build# Sentry Config File
.env.sentry-build-plugin
/public/build
# Sentry Config File
.env.sentry-build-plugin
🤖 Prompt for AI Agents
In e2e/vue-start/selective-ssr/.gitignore around lines 13 to 14, the inline
comment is concatenated with the path; separate the comment and the path onto
two lines by placing the comment (“# Sentry Config File”) on its own line and
the file path (.env.sentry-build-plugin) on the following line so the comment is
not appended to the entry.

Comment on lines +155 to +170
function Home() {
const links = testCases.map((t, index) => {
const key = `testcase-${index}-link`

return (
<div>
<Link data-testid={key} {...t.link} reloadDocument={true}>
root: {JSON.stringify(t.link.search.root.ssr ?? 'undefined')} posts:{' '}
{JSON.stringify(t.link.search.posts.ssr ?? 'undefined')} $postId:{' '}
{JSON.stringify(t.link.search.postId.ssr ?? 'undefined')}
</Link>
<br />
<br />
</div>
)
})
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

Add key prop to mapped elements.

The div elements in the map callback lack a key prop, which is required for Vue's efficient list rendering and will cause console warnings.

Apply this diff:

-      <div>
+      <div key={key}>
         <Link data-testid={key} {...t.link} reloadDocument={true}>
📝 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
function Home() {
const links = testCases.map((t, index) => {
const key = `testcase-${index}-link`
return (
<div>
<Link data-testid={key} {...t.link} reloadDocument={true}>
root: {JSON.stringify(t.link.search.root.ssr ?? 'undefined')} posts:{' '}
{JSON.stringify(t.link.search.posts.ssr ?? 'undefined')} $postId:{' '}
{JSON.stringify(t.link.search.postId.ssr ?? 'undefined')}
</Link>
<br />
<br />
</div>
)
})
function Home() {
const links = testCases.map((t, index) => {
const key = `testcase-${index}-link`
return (
<div key={key}>
<Link data-testid={key} {...t.link} reloadDocument={true}>
root: {JSON.stringify(t.link.search.root.ssr ?? 'undefined')} posts:{' '}
{JSON.stringify(t.link.search.posts.ssr ?? 'undefined')} $postId:{' '}
{JSON.stringify(t.link.search.postId.ssr ?? 'undefined')}
</Link>
<br />
<br />
</div>
)
})
🤖 Prompt for AI Agents
In e2e/vue-start/selective-ssr/src/routes/index.tsx around lines 155 to 170, the
top-level div returned from the map lacks a key prop causing list-render
warnings; add the existing key variable as the key prop on that div (i.e., set
key={key}) so each mapped element has a stable unique key for Vue's list
rendering.

@birkskyum birkskyum merged commit 751f0ff into main Dec 17, 2025
6 checks passed
@birkskyum birkskyum deleted the vue-start-selective-ssr branch December 17, 2025 17:07
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.

2 participants