Skip to content

Comments

fix(router-core): dehydrate SSR match IDs with non-path encoding#6742

Merged
Sheraff merged 6 commits intomainfrom
codex/fix-ssr-match-id-encoding
Feb 23, 2026
Merged

fix(router-core): dehydrate SSR match IDs with non-path encoding#6742
Sheraff merged 6 commits intomainfrom
codex/fix-ssr-match-id-encoding

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Feb 23, 2026

Summary

  • add SSR match-id codec helpers that replace / with \0 during dehydration and restore during hydration
  • apply encoding only in SSR hydration payload (dehydrateMatch + lastMatchId) so client-side navigation/matching hot paths stay unchanged
  • decode before hydration lookup and SPA-mode comparison, and add targeted tests for codec round-trip and hydration behavior

Testing

  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:unit --outputStyle=stream --skipRemoteCache -- tests/hydrate.test.ts tests/ssr-match-id.test.ts
  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:types --outputStyle=stream --skipRemoteCache
  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:eslint --outputStyle=stream --skipRemoteCache

Fixes #6739

Summary by CodeRabbit

  • Bug Fixes

    • Improved server-side rendering hydration to correctly decode encoded route match identifiers, preventing incorrect route reloads during hydration.
  • New Features

    • Added stable encode/decode utilities for SSR match identifiers to ensure safe transmission and restoration of route IDs.
  • Tests

    • Added unit and integration tests validating match ID encoding/decoding and hydration behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 478b019 and 58c8720.

📒 Files selected for processing (1)
  • packages/router-core/src/ssr/ssr-client.ts

📝 Walkthrough

Walkthrough

Adds SSR match-ID encoding/decoding to prevent URL-shaped match IDs in serialized SSR state: server-side IDs are dehydrated (slashes replaced), client-side hydration decodes them before lookup and SPA-mode checks, and tests validate the transformations and hydration behavior.

Changes

Cohort / File(s) Summary
SSR Match ID Utilities
packages/router-core/src/ssr/ssr-match-id.ts
New module exporting dehydrateSsrMatchId() and hydrateSsrMatchId() to replace '/''\0' in match IDs.
Server-Side Dehydration
packages/router-core/src/ssr/ssr-server.ts
Apply dehydrateSsrMatchId() to each match.id and to lastMatchId when producing dehydrated router state.
Client-Side Hydration
packages/router-core/src/ssr/ssr-client.ts
Use a local dehydratedRouter, decode each dehydrated match i and lastMatchId via hydrateSsrMatchId(); reference dehydratedRouter.matches for client lookup.
Tests
packages/router-core/tests/ssr-match-id.test.ts, packages/router-core/tests/hydrate.test.ts
Unit tests for encode/decode and an integration test ensuring dehydrated IDs are decoded before hydration lookup and do not trigger unwanted router.load() calls.

Sequence Diagram(s)

sequenceDiagram
  participant Server as Server (SSR)
  participant Browser as Client (browser)
  participant Router as Router runtime

  Server->>Server: dehydrate match IDs\n(dehydrateSsrMatchId: '/' → '\0')
  Server->>Browser: embed dehydrated router state\n(window.$_TSR)
  Browser->>Browser: read local dehydratedRouter
  Browser->>Browser: hydrate IDs\n(hydrateSsrMatchId: '\0' → '/')
  Browser->>Router: perform hydration lookup using decoded IDs
  Router-->>Browser: restore matches / skip load if already resolved
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • nlynzaad

Poem

🐰 I swapped the slashes for silent signs,
So crawlers won't chase phantom lines,
Dehydrated IDs tuck safe and small,
Hydrated back when the client calls,
Hoppity hop — no crawlers at all!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding non-path encoding for SSR match IDs to prevent crawler extraction.
Linked Issues check ✅ Passed The PR directly addresses issue #6739 by implementing SSR match-ID encoding to prevent path-like serialization that crawlers extract.
Out of Scope Changes check ✅ Passed All changes focus on SSR match-ID encoding/decoding utilities and their integration; no unrelated modifications detected.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/fix-ssr-match-id-encoding

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

@nx-cloud
Copy link

nx-cloud bot commented Feb 23, 2026

View your CI Pipeline Execution ↗ for commit a61b1be

Command Status Duration Result
nx run tanstack-router-e2e-bundle-size:build --... ✅ Succeeded 1m 22s View ↗

☁️ Nx Cloud last updated this comment at 2026-02-23 15:46:32 UTC

@github-actions
Copy link

github-actions bot commented Feb 23, 2026

Bundle Size Benchmarks

  • Commit: a4fb82ecc8bd
  • Measured at: 2026-02-23T15:34:17.616Z
  • Baseline source: history:d6af3a28c350
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 86.72 KiB 0 B (0.00%) 272.45 KiB 75.31 KiB ▅▅▅▅▅██▁▁
react-router.full 89.75 KiB 0 B (0.00%) 282.78 KiB 78.01 KiB ███████▄▁
solid-router.minimal 35.98 KiB 0 B (0.00%) 107.59 KiB 32.28 KiB █████▆▆▂▁
solid-router.full 40.31 KiB 0 B (0.00%) 120.64 KiB 36.15 KiB █████▇▇▃▁
vue-router.minimal 51.82 KiB 0 B (0.00%) 147.59 KiB 46.58 KiB █████▆▆▂▁
vue-router.full 56.67 KiB 0 B (0.00%) 163.18 KiB 50.90 KiB █████▆▆▃▁
react-start.minimal 99.27 KiB +51 B (+0.05%) 311.58 KiB 85.90 KiB ▅▅▅▅▅▅▅▁▁█
react-start.full 102.60 KiB +58 B (+0.06%) 321.36 KiB 88.69 KiB ███████▄▁▅
solid-start.minimal 48.29 KiB +68 B (+0.14%) 145.16 KiB 42.75 KiB ▇▇▇▇▇▆▆▂▁█
solid-start.full 53.76 KiB +67 B (+0.12%) 161.08 KiB 47.38 KiB █████▇▇▃▁█

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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 23, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: a61b1be

@Sheraff Sheraff merged commit 14b5499 into main Feb 23, 2026
7 checks passed
@Sheraff Sheraff deleted the codex/fix-ssr-match-id-encoding branch February 23, 2026 16:12
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.

SSR dehydrated match IDs are URL-shaped strings — Google crawls them as phantom URLs

1 participant