Skip to content

Conversation

@schiller-manuel
Copy link
Contributor

@schiller-manuel schiller-manuel commented Dec 23, 2025

fixes #6089

Summary by CodeRabbit

  • New Features

    • i18n with Paraglide added across example React projects, including locale-aware route translations and locale switcher UI.
    • PWA manifest and localized public assets included.
  • Behavior Changes

    • Browser-facing (canonical) URLs are now tracked separately from internal routing to ensure consistent redirects and URL presentation.
  • Tests

    • Extensive end-to-end tests added to validate locale-prefixed navigation, direct localized routes, and no-redirect-loop behavior.
  • Documentation

    • Example projects and configs demonstrating i18n usage and testing added.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2025

Walkthrough

Adds two Paraglide i18n example apps (React Router and React Start) and router-core/transitioner changes that introduce a browser-visible publicHref to separate output-rewritten URLs from internal routing, plus tests and e2e Playwright configs.

Changes

Cohort / File(s) Summary
React Router i18n-Paraglide Example
e2e/react-router/i18n-paraglide/*
New e2e example: project files (package.json, vite.config.ts, tsconfig.json, .gitignore, index.html), Paraglide settings/messages (project.inlang/*, messages/*.json), router entry and routes (src/main.tsx, src/routes/*), styles, Playwright config/tests, and generated route tree.
React Start i18n-Paraglide Example
e2e/react-start/i18n-paraglide/*
New e2e example: project scaffold (package.json, vite.config.ts, tsconfig.json, .gitignore, public/*), Inlang settings/messages, generated routeTree.gen.ts, router factory (src/router.tsx), server middleware (src/server.ts), routes with loaders (src/routes/*), utils (prerender.ts, seo.ts, translated-pathnames.ts), styles, and Playwright tests.
Router core: location / rewrite
packages/router-core/src/router.ts
parseLocation/buildLocation now produce a publicHref (output-rewritten browser URL) and derive pathname from an internalPathname captured before output rewrite to avoid redirect mismatches.
Transitioners: use publicHref
packages/react-router/src/Transitioner.tsx, packages/solid-router/src/Transitioner.tsx, packages/vue-router/src/Transitioner.tsx
Transitioner comparisons changed to use latestLocation.publicHref and nextLocation.publicHref (instead of href) when deciding commit/replace behavior.
Router tests
packages/react-router/tests/router.test.tsx
New tests exercising input/output rewrite behavior, locale prefix stripping/re-adding, navigation between localized routes, and redirect-loop prevention.
E2E test updates (custom basepath suites)
e2e/*/custom-basepath/tests/navigation.spec.ts
Adjusted server-side redirect expectations to assert final destination Location headers (skip intermediate redirect).

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Router
    participant LocationEngine as Location Engine
    participant Rewrite as Rewrite Pipeline
    participant Transitioner

    Browser->>Router: Navigate to /de/about
    Router->>LocationEngine: parseLocation(url=/de/about)
    LocationEngine->>Rewrite: input rewrite (deLocalize)
    Rewrite-->>LocationEngine: /about (internal)
    LocationEngine->>LocationEngine: match internal route (/about)
    LocationEngine->>Rewrite: output rewrite (localize)
    Rewrite-->>LocationEngine: /de/about (public)
    LocationEngine-->>Router: return { href: "/about", publicHref: "/de/about", pathname: "/about" }

    Router->>Transitioner: commitLocation(nextLocation)
    Note over Transitioner: Compare publicHref values
    Transitioner->>Transitioner: latestLocation.publicHref == nextLocation.publicHref ?
    alt match
        Transitioner-->>Router: no replace/redirect
    else
        Transitioner->>Browser: update URL (replace=true)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

package: react-start

Suggested reviewers

  • nlynzaad

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 'fix: rewrite causes infinite redirects' accurately summarizes the main change - fixing infinite redirects caused by URL rewrites in the router, which directly addresses issue #6089.
Linked Issues check ✅ Passed The PR adds comprehensive i18n example projects and fixes router rewrite logic to prevent infinite redirects. Core changes: publicHref field in location objects to track browser-visible URLs separately from internal routing, URL comparison updates in Transitioner components across frameworks, and new Paraglide i18n test suites that exercise locale prefix rewrites without redirect loops.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the infinite redirect issue with URL rewrites. The e2e examples establish test scenarios for i18n rewrites, core router changes implement the publicHref mechanism, Transitioner updates apply the fix across frameworks, and test updates validate the solution.
✨ 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 fix-6089

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 700efec and 231c544.

📒 Files selected for processing (3)
  • e2e/react-start/custom-basepath/tests/navigation.spec.ts
  • e2e/solid-start/custom-basepath/tests/navigation.spec.ts
  • e2e/vue-start/custom-basepath/tests/navigation.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • e2e/solid-start/custom-basepath/tests/navigation.spec.ts
  • e2e/vue-start/custom-basepath/tests/navigation.spec.ts
  • e2e/react-start/custom-basepath/tests/navigation.spec.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • e2e/solid-start/custom-basepath/tests/navigation.spec.ts
  • e2e/vue-start/custom-basepath/tests/navigation.spec.ts
  • e2e/react-start/custom-basepath/tests/navigation.spec.ts
🧠 Learnings (5)
📓 Common learnings
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.
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
📚 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:

  • e2e/solid-start/custom-basepath/tests/navigation.spec.ts
  • e2e/vue-start/custom-basepath/tests/navigation.spec.ts
  • e2e/react-start/custom-basepath/tests/navigation.spec.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/solid-start/custom-basepath/tests/navigation.spec.ts
  • e2e/vue-start/custom-basepath/tests/navigation.spec.ts
  • e2e/react-start/custom-basepath/tests/navigation.spec.ts
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
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/solid-start/custom-basepath/tests/navigation.spec.ts
📚 Learning: 2025-12-21T12:52:35.231Z
Learnt from: Sheraff
Repo: TanStack/router PR: 6171
File: packages/router-core/src/new-process-route-tree.ts:898-898
Timestamp: 2025-12-21T12:52:35.231Z
Learning: In `packages/router-core/src/new-process-route-tree.ts`, the matching logic intentionally allows paths without trailing slashes to match index routes with trailing slashes (e.g., `/a` can match `/a/` route), but not vice-versa (e.g., `/a/` cannot match `/a` layout route). This is implemented via the condition `!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX` and is a deliberate design decision to provide better UX by being permissive with missing trailing slashes.

Applied to files:

  • e2e/solid-start/custom-basepath/tests/navigation.spec.ts
  • e2e/vue-start/custom-basepath/tests/navigation.spec.ts
⏰ 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: Preview
  • GitHub Check: Test
🔇 Additional comments (3)
e2e/react-start/custom-basepath/tests/navigation.spec.ts (1)

64-71: LGTM! Test correctly reflects optimized redirect behavior.

The updated expectations properly verify that the router now skips intermediate basepath-adding redirects and goes directly to the final destination. This optimization prevents the infinite redirect loops described in issue #6089.

e2e/solid-start/custom-basepath/tests/navigation.spec.ts (1)

64-71: LGTM! Consistent with React Start implementation.

The test updates match the React Start version, correctly verifying that server-side redirects bypass intermediate steps and go directly to the final destination. This consistency across frameworks is essential for the fix.

e2e/vue-start/custom-basepath/tests/navigation.spec.ts (1)

64-71: LGTM! Completes the consistent pattern across all frameworks.

The Vue Start test updates match both React and Solid implementations, ensuring uniform redirect behavior verification across all framework integrations. The optimized redirect logic is now properly tested in all three e2e suites.


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

@nx-cloud
Copy link

nx-cloud bot commented Dec 23, 2025

View your CI Pipeline Execution ↗ for commit 231c544

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

☁️ Nx Cloud last updated this comment at 2025-12-23 10:15:04 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 23, 2025

More templates

@tanstack/arktype-adapter

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

@tanstack/directive-functions-plugin

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/server-functions-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 231c544

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 (14)
e2e/react-start/i18n-paraglide/src/utils/seo.ts (2)

12-30: Consider filtering tags with undefined content values.

When optional parameters (description, keywords) are not provided, the corresponding meta tags will have content: undefined. Depending on how these tag descriptors are rendered, this could result in invalid HTML (e.g., <meta name="description" content="undefined">) or rendering errors.

🔎 Proposed fix

Filter out tags with undefined content before returning:

   const tags = [
     { title },
     { name: 'description', content: description },
     { name: 'keywords', content: keywords },
     { name: 'twitter:title', content: title },
     { name: 'twitter:description', content: description },
     { name: 'twitter:creator', content: '@tannerlinsley' },
     { name: 'twitter:site', content: '@tannerlinsley' },
     { name: 'og:type', content: 'website' },
     { name: 'og:title', content: title },
     { name: 'og:description', content: description },
     ...(image
       ? [
           { name: 'twitter:image', content: image },
           { name: 'twitter:card', content: 'summary_large_image' },
           { name: 'og:image', content: image },
         ]
       : []),
-  ]
+  ].filter((tag) => {
+    if ('title' in tag) return true
+    return tag.content !== undefined
+  })

   return tags

18-19: Consider parameterizing the Twitter handle.

The Twitter creator and site are hardcoded to @tannerlinsley. While this may be acceptable for an example/test project, consider making this configurable if this utility is intended to be reusable across different projects or contexts.

🔎 Optional enhancement
 export const seo = ({
   title,
   description,
   keywords,
   image,
+  twitterHandle = '@tannerlinsley',
 }: {
   title: string
   description?: string
   image?: string
   keywords?: string
+  twitterHandle?: string
 }) => {
   const tags = [
     { title },
     { name: 'description', content: description },
     { name: 'keywords', content: keywords },
     { name: 'twitter:title', content: title },
     { name: 'twitter:description', content: description },
-    { name: 'twitter:creator', content: '@tannerlinsley' },
-    { name: 'twitter:site', content: '@tannerlinsley' },
+    { name: 'twitter:creator', content: twitterHandle },
+    { name: 'twitter:site', content: twitterHandle },
e2e/react-start/i18n-paraglide/tests/navigation.spec.ts (1)

82-101: Good test coverage for the redirect loop fix.

The test correctly verifies that a single redirect doesn't cause a loop. One minor observation: Line 96 asserts followUp.status() < 400, which would allow a 3xx redirect status. If the goal is to ensure no further redirects occur, consider asserting < 300 or explicitly checking for 200.

🔎 Optional: Stricter assertion for no further redirects
     // Following the redirect should not cause another redirect loop
     const followUp = await page.request.get(location!, { maxRedirects: 0 })
     // The follow-up should be a 200 or another valid response, not another redirect to the same location
-    expect(followUp.status()).toBeLessThan(400)
+    expect(followUp.status()).toBe(200)
e2e/react-start/i18n-paraglide/src/routes/index.tsx (1)

21-29: Component correctly demonstrates loader data usage.

The third <h2> at Line 27 re-invokes m.example_message() on the client side. If this duplication is intentional to demonstrate client-side i18n, consider adding a brief comment to clarify the purpose for future readers.

e2e/react-router/i18n-paraglide/tests/navigation.spec.ts (1)

197-218: Consider strengthening the back navigation assertion.

The comment on line 216 acknowledges uncertainty about the expected state after goBack(). While the current loose assertion is acceptable for verifying no redirect loops occur, consider capturing the URL before the locale switch to make the expected back navigation target explicit.

Suggested improvement
     // Navigate to about
     await page.getByTestId('about-link').click()
     await page.waitForLoadState('networkidle')
+    const aboutUrlBeforeLocaleSwitch = page.url()
 
     // Switch to German
     await page.getByTestId('locale-de').click()
     await page.waitForLoadState('networkidle')
     expect(page.url()).toContain('/de/ueber')
 
     // Go back
     await page.goBack()
     await page.waitForLoadState('networkidle')
 
-    // Should be on about page (English or previous state)
+    // Should return to about page before locale switch
     await expect(page.getByTestId('about-content')).toBeVisible()
+    expect(page.url()).toBe(aboutUrlBeforeLocaleSwitch)
packages/solid-router/src/Transitioner.tsx (1)

58-63: Aligns Solid Transitioner with public-facing canonical URL semantics

Switching the mount-time equality check to compare trimPathRight(latestLocation.publicHref) vs trimPathRight(nextLocation.publicHref) is correct and matches the server-side beforeLoad redirect check. This keeps internal pathname/href decoupled from the browser-visible URL, preventing rewrite-based redirect loops while still normalizing trailing slashes via trimPathRight.

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

126-131: Vue adapter correctly switched to publicHref for canonical URL check

Using trimPathRight(router.latestLocation.publicHref) vs trimPathRight(nextLocation.publicHref) here matches the new router-core contract and the React/Solid adapters. This ensures the Vue transitioner respects the browser-facing canonical URL while avoiding unnecessary commits when only internal href differs.

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

1191-1218: Clean separation between internal matching path and public-facing URL in parseLocation

Capturing internalPathname before applying the output rewrite and then:

  • Using decodePath(internalPathname) for pathname (routing/matching), and
  • Deriving publicHref from executeRewriteOutput on a cloned URL,

is the right split. It guarantees:

  • Route matching only depends on input rewrites and basepath handling.
  • publicHref always reflects the final output-rewritten browser URL, in sync with buildLocation.

This is exactly what’s needed to make parseLocation and buildLocation agree on publicHref and avoid redirect loops under i18n-style rewrites.


1793-1807: buildLocation now returns a stable publicHref while keeping internal href

The new return shape:

  • Applies executeRewriteOutput to the URL.
  • Sets:
    • publicHref from the rewritten URL (what should go into history / be compared on the client & server).
    • href from the pre-output fullPath (internal router href).
    • url to the rewritten URL (used downstream e.g. in redirects).

This matches parseLocation’s publicHref semantics and gives a single source of truth for the browser-visible URL, which is what the Transitioners and server beforeLoad now rely on.


1876-1963: History writes correctly switched to publicHref, while dedup still uses internal href

Within commitLocation:

  • URL dedup still checks isSameUrl via trimmed internal href, which keeps the “don’t push duplicates” behavior tied to router-internal paths.
  • The actual history write now uses nextHistory.publicHref instead of nextHistory.href, so the browser URL always reflects the output rewrite (and any basepath) rather than the internal routing path.

This combination is sensible: it avoids spurious history entries while ensuring the user sees the canonical URL. Just be aware of the deliberate distinction: internal equality uses href, external navigation uses publicHref.


2084-2105: Server beforeLoad redirect check now aligned with publicHref and origin

The updated server-side check:

if (
  this.latestLocation.publicHref !== nextLocation.publicHref ||
  nextLocation.url.origin !== this.origin
) {
  const href = this.getParsedLocationHref(nextLocation)
  throw redirect({ href })
}

is a good match for the new model:

  • It compares publicHref (not the internal href), so input/output rewrites that only affect the browser URL no longer cause false mismatches.
  • The separate origin comparison ensures cross-origin rewrites still produce a proper HTTP redirect with a full absolute Location.

This is the critical fix that prevents infinite redirect loops under i18n output rewrites.


2425-2443: getParsedLocationHref/resolveRedirect preserve same-origin path-only Location headers

Using getParsedLocationHref(location) to derive the HTTP Location header inside resolveRedirect:

  • Strips this.origin from location.url.href when origins match, yielding /path?... for same-origin redirects.
  • Leaves absolute URLs intact when location.url.origin !== this.origin, so cross-origin redirects remain fully qualified.

This keeps the intentional behavior described in the learnings (same-origin → path-only, cross-origin → absolute) while integrating with the new publicHref/rewrite pipeline. The change to avoid using publicHref directly here is correct.

packages/react-router/src/Transitioner.tsx (1)

55-61: React Transitioner correctly moved canonical check to publicHref

Comparing trimPathRight(router.latestLocation.publicHref) against trimPathRight(nextLocation.publicHref) during mount ensures:

  • The SPA normalizes the browser URL to the same canonical public URL the server uses.
  • Internal rewrites that only affect routing no longer trigger unnecessary client-side replaces or loops.

This is consistent with router-core’s new publicHref contract and the other framework adapters.

packages/react-router/tests/router.test.tsx (1)

3128-3367: New i18n/publicHref tests exercise the right invariants for rewrite behavior

The added tests around locale-prefix rewrites:

  • Cover input-only i18n rewrites, navigation between localized routes, and direct entry on localized URLs.
  • Assert both internal pathname (de-localized) and publicHref/history pathname (localized), matching the new publicHref contract.
  • Explicitly verify that parseLocation and buildLocation compute the same publicHref for the same logical route, which is the key invariant to prevent redirect loops.

This is a solid regression suite for the reported issue; any future change that reintroduces a mismatch between internal and public URLs should now be caught here.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 358a770 and 700efec.

⛔ Files ignored due to path filters (5)
  • e2e/react-start/i18n-paraglide/public/favicon.ico is excluded by !**/*.ico
  • e2e/react-start/i18n-paraglide/public/logo192.png is excluded by !**/*.png
  • e2e/react-start/i18n-paraglide/public/logo512.png is excluded by !**/*.png
  • e2e/react-start/i18n-paraglide/src/logo.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (48)
  • e2e/react-router/i18n-paraglide/.gitignore
  • e2e/react-router/i18n-paraglide/index.html
  • e2e/react-router/i18n-paraglide/messages/de.json
  • e2e/react-router/i18n-paraglide/messages/en.json
  • e2e/react-router/i18n-paraglide/package.json
  • e2e/react-router/i18n-paraglide/playwright.config.ts
  • e2e/react-router/i18n-paraglide/project.inlang/.gitignore
  • e2e/react-router/i18n-paraglide/project.inlang/project_id
  • e2e/react-router/i18n-paraglide/project.inlang/settings.json
  • e2e/react-router/i18n-paraglide/src/main.tsx
  • e2e/react-router/i18n-paraglide/src/routes/__root.tsx
  • e2e/react-router/i18n-paraglide/src/routes/about.tsx
  • e2e/react-router/i18n-paraglide/src/routes/index.tsx
  • e2e/react-router/i18n-paraglide/src/styles.css
  • e2e/react-router/i18n-paraglide/tests/navigation.spec.ts
  • e2e/react-router/i18n-paraglide/tsconfig.json
  • e2e/react-router/i18n-paraglide/vite.config.ts
  • e2e/react-start/i18n-paraglide/.gitignore
  • e2e/react-start/i18n-paraglide/.prettierignore
  • e2e/react-start/i18n-paraglide/.vscode/extensions.json
  • e2e/react-start/i18n-paraglide/.vscode/settings.json
  • e2e/react-start/i18n-paraglide/messages/de.json
  • e2e/react-start/i18n-paraglide/messages/en.json
  • e2e/react-start/i18n-paraglide/package.json
  • e2e/react-start/i18n-paraglide/playwright.config.ts
  • e2e/react-start/i18n-paraglide/project.inlang/.gitignore
  • e2e/react-start/i18n-paraglide/project.inlang/project_id
  • e2e/react-start/i18n-paraglide/project.inlang/settings.json
  • e2e/react-start/i18n-paraglide/public/manifest.json
  • e2e/react-start/i18n-paraglide/public/robots.txt
  • e2e/react-start/i18n-paraglide/src/routeTree.gen.ts
  • e2e/react-start/i18n-paraglide/src/router.tsx
  • e2e/react-start/i18n-paraglide/src/routes/__root.tsx
  • e2e/react-start/i18n-paraglide/src/routes/about.tsx
  • e2e/react-start/i18n-paraglide/src/routes/index.tsx
  • e2e/react-start/i18n-paraglide/src/server.ts
  • e2e/react-start/i18n-paraglide/src/styles.css
  • e2e/react-start/i18n-paraglide/src/utils/prerender.ts
  • e2e/react-start/i18n-paraglide/src/utils/seo.ts
  • e2e/react-start/i18n-paraglide/src/utils/translated-pathnames.ts
  • e2e/react-start/i18n-paraglide/tests/navigation.spec.ts
  • e2e/react-start/i18n-paraglide/tsconfig.json
  • e2e/react-start/i18n-paraglide/vite.config.ts
  • packages/react-router/src/Transitioner.tsx
  • packages/react-router/tests/router.test.tsx
  • packages/router-core/src/router.ts
  • packages/solid-router/src/Transitioner.tsx
  • packages/vue-router/src/Transitioner.tsx
🧰 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/react-start/i18n-paraglide/src/utils/translated-pathnames.ts
  • e2e/react-router/i18n-paraglide/src/routes/about.tsx
  • packages/react-router/src/Transitioner.tsx
  • e2e/react-start/i18n-paraglide/src/server.ts
  • e2e/react-start/i18n-paraglide/src/utils/prerender.ts
  • packages/vue-router/src/Transitioner.tsx
  • e2e/react-router/i18n-paraglide/src/routes/index.tsx
  • e2e/react-start/i18n-paraglide/src/routes/index.tsx
  • e2e/react-router/i18n-paraglide/vite.config.ts
  • e2e/react-start/i18n-paraglide/src/router.tsx
  • e2e/react-router/i18n-paraglide/tests/navigation.spec.ts
  • packages/solid-router/src/Transitioner.tsx
  • e2e/react-start/i18n-paraglide/src/routes/__root.tsx
  • e2e/react-router/i18n-paraglide/src/routes/__root.tsx
  • e2e/react-router/i18n-paraglide/src/main.tsx
  • e2e/react-router/i18n-paraglide/playwright.config.ts
  • packages/react-router/tests/router.test.tsx
  • e2e/react-start/i18n-paraglide/src/utils/seo.ts
  • e2e/react-start/i18n-paraglide/src/routes/about.tsx
  • e2e/react-start/i18n-paraglide/src/routeTree.gen.ts
  • packages/router-core/src/router.ts
  • e2e/react-start/i18n-paraglide/vite.config.ts
  • e2e/react-start/i18n-paraglide/playwright.config.ts
  • e2e/react-start/i18n-paraglide/tests/navigation.spec.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • e2e/react-start/i18n-paraglide/src/utils/translated-pathnames.ts
  • e2e/react-router/i18n-paraglide/src/routes/about.tsx
  • packages/react-router/src/Transitioner.tsx
  • e2e/react-start/i18n-paraglide/src/server.ts
  • e2e/react-start/i18n-paraglide/src/utils/prerender.ts
  • packages/vue-router/src/Transitioner.tsx
  • e2e/react-router/i18n-paraglide/src/routes/index.tsx
  • e2e/react-start/i18n-paraglide/src/routes/index.tsx
  • e2e/react-router/i18n-paraglide/vite.config.ts
  • e2e/react-start/i18n-paraglide/src/router.tsx
  • e2e/react-router/i18n-paraglide/tests/navigation.spec.ts
  • packages/solid-router/src/Transitioner.tsx
  • e2e/react-start/i18n-paraglide/src/routes/__root.tsx
  • e2e/react-router/i18n-paraglide/src/routes/__root.tsx
  • e2e/react-router/i18n-paraglide/src/main.tsx
  • e2e/react-router/i18n-paraglide/playwright.config.ts
  • packages/react-router/tests/router.test.tsx
  • e2e/react-start/i18n-paraglide/src/utils/seo.ts
  • e2e/react-start/i18n-paraglide/src/routes/about.tsx
  • e2e/react-start/i18n-paraglide/src/routeTree.gen.ts
  • packages/router-core/src/router.ts
  • e2e/react-start/i18n-paraglide/vite.config.ts
  • e2e/react-start/i18n-paraglide/playwright.config.ts
  • e2e/react-start/i18n-paraglide/tests/navigation.spec.ts
**/package.json

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • e2e/react-router/i18n-paraglide/package.json
  • e2e/react-start/i18n-paraglide/package.json
🧠 Learnings (13)
📓 Common learnings
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.
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
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/react-start/i18n-paraglide/src/utils/translated-pathnames.ts
  • e2e/react-router/i18n-paraglide/src/routes/about.tsx
  • e2e/react-start/i18n-paraglide/src/utils/prerender.ts
  • e2e/react-router/i18n-paraglide/src/routes/index.tsx
  • e2e/react-start/i18n-paraglide/src/router.tsx
  • e2e/react-start/i18n-paraglide/src/routes/__root.tsx
  • e2e/react-router/i18n-paraglide/src/routes/__root.tsx
  • e2e/react-router/i18n-paraglide/src/main.tsx
  • packages/react-router/tests/router.test.tsx
  • e2e/react-start/i18n-paraglide/src/routes/about.tsx
  • e2e/react-start/i18n-paraglide/src/routeTree.gen.ts
  • packages/router-core/src/router.ts
📚 Learning: 2025-12-21T12:52:35.231Z
Learnt from: Sheraff
Repo: TanStack/router PR: 6171
File: packages/router-core/src/new-process-route-tree.ts:898-898
Timestamp: 2025-12-21T12:52:35.231Z
Learning: In `packages/router-core/src/new-process-route-tree.ts`, the matching logic intentionally allows paths without trailing slashes to match index routes with trailing slashes (e.g., `/a` can match `/a/` route), but not vice-versa (e.g., `/a/` cannot match `/a` layout route). This is implemented via the condition `!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX` and is a deliberate design decision to provide better UX by being permissive with missing trailing slashes.

Applied to files:

  • e2e/react-start/i18n-paraglide/src/utils/translated-pathnames.ts
  • packages/vue-router/src/Transitioner.tsx
  • packages/solid-router/src/Transitioner.tsx
  • e2e/react-router/i18n-paraglide/src/routes/__root.tsx
  • packages/react-router/tests/router.test.tsx
  • e2e/react-start/i18n-paraglide/src/routeTree.gen.ts
  • packages/router-core/src/router.ts
📚 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/react-start/i18n-paraglide/.vscode/settings.json
  • e2e/react-router/i18n-paraglide/src/routes/about.tsx
  • e2e/react-router/i18n-paraglide/src/routes/index.tsx
  • e2e/react-start/i18n-paraglide/.prettierignore
  • e2e/react-router/i18n-paraglide/.gitignore
  • e2e/react-start/i18n-paraglide/src/routes/about.tsx
  • e2e/react-start/i18n-paraglide/src/routeTree.gen.ts
  • e2e/react-start/i18n-paraglide/.gitignore
📚 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/react-start/i18n-paraglide/.vscode/settings.json
  • e2e/react-start/i18n-paraglide/.prettierignore
  • e2e/react-start/i18n-paraglide/src/router.tsx
  • e2e/react-router/i18n-paraglide/tests/navigation.spec.ts
  • e2e/react-router/i18n-paraglide/package.json
  • e2e/react-router/i18n-paraglide/.gitignore
  • packages/react-router/tests/router.test.tsx
  • e2e/react-start/i18n-paraglide/src/routes/about.tsx
  • e2e/react-start/i18n-paraglide/src/routeTree.gen.ts
  • e2e/react-start/i18n-paraglide/tests/navigation.spec.ts
📚 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/react-router/i18n-paraglide/src/routes/about.tsx
  • e2e/react-router/i18n-paraglide/src/routes/index.tsx
  • e2e/react-start/i18n-paraglide/.prettierignore
  • e2e/react-start/i18n-paraglide/src/routes/index.tsx
  • e2e/react-router/i18n-paraglide/vite.config.ts
  • e2e/react-start/i18n-paraglide/src/router.tsx
  • e2e/react-start/i18n-paraglide/src/routes/__root.tsx
  • e2e/react-router/i18n-paraglide/tsconfig.json
  • e2e/react-router/i18n-paraglide/src/routes/__root.tsx
  • e2e/react-router/i18n-paraglide/index.html
  • e2e/react-router/i18n-paraglide/package.json
  • e2e/react-router/i18n-paraglide/src/main.tsx
  • e2e/react-router/i18n-paraglide/.gitignore
  • packages/react-router/tests/router.test.tsx
  • e2e/react-start/i18n-paraglide/src/routes/about.tsx
  • e2e/react-start/i18n-paraglide/src/routeTree.gen.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/react-router/src/Transitioner.tsx
  • packages/vue-router/src/Transitioner.tsx
  • e2e/react-router/i18n-paraglide/tests/navigation.spec.ts
  • packages/solid-router/src/Transitioner.tsx
  • e2e/react-router/i18n-paraglide/src/routes/__root.tsx
  • packages/react-router/tests/router.test.tsx
  • packages/router-core/src/router.ts
  • e2e/react-start/i18n-paraglide/tests/navigation.spec.ts
📚 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/react-start/i18n-paraglide/.prettierignore
  • e2e/react-router/i18n-paraglide/vite.config.ts
  • e2e/react-start/i18n-paraglide/tsconfig.json
  • e2e/react-router/i18n-paraglide/tsconfig.json
  • e2e/react-router/i18n-paraglide/package.json
  • e2e/react-router/i18n-paraglide/src/main.tsx
  • e2e/react-start/i18n-paraglide/package.json
  • e2e/react-start/i18n-paraglide/src/routeTree.gen.ts
📚 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/react-start/i18n-paraglide/tsconfig.json
  • e2e/react-router/i18n-paraglide/tsconfig.json
📚 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/react-router/i18n-paraglide/index.html
📚 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/react-router/i18n-paraglide/playwright.config.ts
  • e2e/react-start/i18n-paraglide/playwright.config.ts
📚 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/react-start/i18n-paraglide/src/routes/about.tsx
  • e2e/react-start/i18n-paraglide/src/routeTree.gen.ts
📚 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/react-start/i18n-paraglide/tests/navigation.spec.ts
🧬 Code graph analysis (13)
e2e/react-start/i18n-paraglide/src/utils/translated-pathnames.ts (1)
e2e/react-start/i18n-paraglide/src/routeTree.gen.ts (1)
  • FileRoutesByTo (30-33)
e2e/react-router/i18n-paraglide/src/routes/about.tsx (2)
e2e/react-router/i18n-paraglide/src/routes/__root.tsx (1)
  • Route (10-68)
e2e/react-router/i18n-paraglide/src/routes/index.tsx (1)
  • Route (4-6)
e2e/react-start/i18n-paraglide/src/utils/prerender.ts (1)
packages/router-core/src/route.ts (1)
  • path (1553-1555)
packages/vue-router/src/Transitioner.tsx (1)
packages/vue-router/src/index.tsx (1)
  • trimPathRight (8-8)
e2e/react-router/i18n-paraglide/src/routes/index.tsx (2)
e2e/react-router/i18n-paraglide/src/routes/__root.tsx (1)
  • Route (10-68)
e2e/react-router/i18n-paraglide/src/routes/about.tsx (1)
  • Route (4-6)
e2e/react-start/i18n-paraglide/src/routes/index.tsx (2)
e2e/react-start/i18n-paraglide/src/routes/__root.tsx (1)
  • Route (12-30)
e2e/react-start/i18n-paraglide/src/routes/about.tsx (1)
  • Route (4-6)
e2e/react-start/i18n-paraglide/src/routes/__root.tsx (2)
e2e/react-start/i18n-paraglide/src/routes/about.tsx (1)
  • Route (4-6)
e2e/react-start/i18n-paraglide/src/routes/index.tsx (1)
  • Route (11-19)
e2e/react-router/i18n-paraglide/src/routes/__root.tsx (2)
e2e/react-router/i18n-paraglide/src/routes/about.tsx (1)
  • Route (4-6)
e2e/react-router/i18n-paraglide/src/routes/index.tsx (1)
  • Route (4-6)
packages/react-router/tests/router.test.tsx (2)
packages/react-router/src/index.tsx (7)
  • createRootRoute (254-254)
  • Outlet (239-239)
  • createRoute (251-251)
  • createMemoryHistory (114-114)
  • createRouter (269-269)
  • RouterProvider (279-279)
  • Link (144-144)
packages/react-router/src/Match.tsx (1)
  • Outlet (316-366)
e2e/react-start/i18n-paraglide/src/utils/seo.ts (1)
scripts/llms-generate.mjs (1)
  • title (101-101)
e2e/react-start/i18n-paraglide/src/routes/about.tsx (2)
e2e/react-start/i18n-paraglide/src/routes/__root.tsx (1)
  • Route (12-30)
e2e/react-start/i18n-paraglide/src/routes/index.tsx (1)
  • Route (11-19)
e2e/react-start/i18n-paraglide/src/routeTree.gen.ts (1)
e2e/react-start/i18n-paraglide/src/router.tsx (1)
  • getRouter (8-18)
packages/router-core/src/router.ts (2)
packages/router-core/src/rewrite.ts (1)
  • executeRewriteOutput (80-93)
packages/router-core/src/utils.ts (1)
  • decodePath (507-520)
⏰ 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 (48)
e2e/react-start/i18n-paraglide/.vscode/extensions.json (1)

1-3: LGTM!

The VSCode extensions recommendation is well-placed. Suggesting the inlang extension improves the developer experience for contributors working on this i18n example.

e2e/react-start/i18n-paraglide/project.inlang/.gitignore (1)

1-1: LGTM!

Ignoring the cache directory is standard practice for project configuration. This prevents build artifacts and temporary files from being committed.

e2e/react-router/i18n-paraglide/project.inlang/.gitignore (1)

1-1: LGTM!

The single-line gitignore rule is appropriate for the inlang project subdirectory context.

e2e/react-start/i18n-paraglide/.gitignore (1)

1-10: LGTM!

The patterns appropriately cover common React Start artifacts (.nitro, .tanstack, .output) and standard development ignores.

Verify that the React Start example does not need explicit rules for src/routeTree.gen.ts and src/paraglide (which the React Router sibling example does ignore). If these files are generated in a different location or handled by dist-ssr, no action is needed.

e2e/react-router/i18n-paraglide/.gitignore (1)

1-7: LGTM!

The patterns correctly ignore autogenerated files (src/routeTree.gen.ts, src/paraglide) and build artifacts. Port and test-result files are appropriately excluded.

e2e/react-start/i18n-paraglide/public/robots.txt (1)

1-3: LGTM! Standard robots.txt for e2e test example.

The permissive configuration is appropriate for test infrastructure.

e2e/react-start/i18n-paraglide/public/manifest.json (1)

1-25: LGTM! Standard PWA manifest for e2e test example.

The manifest configuration is appropriate for the test infrastructure supporting the i18n-paraglide example.

e2e/react-router/i18n-paraglide/project.inlang/project_id (1)

1-1: LGTM! Standard Inlang project identifier.

This file correctly contains the Inlang project identifier for the e2e test infrastructure.

e2e/react-start/i18n-paraglide/.prettierignore (1)

1-4: LGTM! Appropriate ignore patterns.

The ignore patterns correctly exclude build artifacts, lock files, and autogenerated files (routeTree.gen.ts) from Prettier formatting.

e2e/react-start/i18n-paraglide/project.inlang/project_id (1)

1-1: LGTM! Standard Inlang project identifier.

This file correctly contains the Inlang project identifier for the React Start e2e test infrastructure.

e2e/react-start/i18n-paraglide/src/styles.css (1)

1-1: LGTM! Correct Tailwind CSS v4 import syntax.

The single @import 'tailwindcss'; statement is the proper way to import Tailwind CSS in v4, replacing the older @tailwind directives.

e2e/react-start/i18n-paraglide/project.inlang/settings.json (1)

1-12: LGTM! Properly configured Inlang project settings.

The configuration correctly sets up English and German locales with appropriate plugins and message paths for the e2e test infrastructure.

e2e/react-router/i18n-paraglide/messages/en.json (1)

1-7: LGTM! Properly structured translation messages.

The English translation file follows the Inlang message format schema correctly, with appropriate placeholder syntax for dynamic content.

e2e/react-start/i18n-paraglide/src/server.ts (1)

1-8: LGTM! Correct middleware integration pattern.

The server entry correctly wraps the React Start handler with Paraglide i18n middleware, enabling locale detection and URL rewriting for the e2e test infrastructure. The type signatures are explicit and appropriate.

e2e/react-start/i18n-paraglide/src/utils/prerender.ts (1)

3-8: The current prerender configuration correctly includes all locale variants. The router's automatic link discovery (enabled via crawlLinks by default) handles locale expansion seamlessly: when prerendered pages are fetched, the router's output rewrite generates href attributes with locale prefixes (e.g., /en/about, /de/ueber), which the crawler automatically discovers and prerendered. This is verified by the comprehensive Playwright tests that confirm access to all locale variants (/en, /de, /en/about, /de/ueber).

e2e/react-start/i18n-paraglide/tsconfig.json (1)

1-29: LGTM! Well-configured TypeScript setup.

The configuration follows best practices with strict mode enabled, modern ES2022 target, and appropriate bundler settings. The path alias setup aligns with the project structure.

e2e/react-start/i18n-paraglide/src/utils/translated-pathnames.ts (2)

47-56: LGTM! Route mappings are clear and consistent.

The exported translatedPathnames correctly maps English and German localized paths for the root and about routes.


18-30: The regex patterns are not currently used by any routes in this file.

The toUrlPattern function defines patterns for catch-all (/$$), optional parameters ({-$param}), and named parameters ($param), but the project's actual routes (/ and /about) are simple static paths. These regex transformations are not exercised by the current translatedPathnames export. If dynamic or parameterized routes are added in the future, these patterns should be tested against actual route definitions.

e2e/react-router/i18n-paraglide/src/styles.css (1)

1-1: LGTM! Standard Tailwind CSS v4 import.

This follows the new Tailwind v4 import syntax as documented in the library context.

e2e/react-router/i18n-paraglide/index.html (1)

1-18: LGTM! Standard HTML entry point.

The HTML structure is appropriate for a React application with correct meta tags and script loading.

e2e/react-start/i18n-paraglide/.vscode/settings.json (1)

1-11: LGTM! Appropriate VS Code settings for generated files.

These settings correctly exclude routeTree.gen.ts from watching, searching, and mark it as read-only, which aligns with best practices for auto-generated files.

e2e/react-start/i18n-paraglide/vite.config.ts (1)

8-47: Plugin order and URL patterns are correct.

The paraglideVitePlugin runs first as required to initialize the localization structure before TanStack Start processes the routes. The URL patterns correctly map the defined routes (/ and /about) with locale prefixes (/en, /de), and the catch-all pattern /:path(.*)? appropriately handles dynamic or unmatched routes.

e2e/react-start/i18n-paraglide/playwright.config.ts (1)

22-27: Configuration follows established patterns across the codebase.

The webServer.command setup is consistent with other e2e tests in the repository (e.g., e2e/vue-start/ projects). Both pnpm build and pnpm start scripts exist in package.json, and the environment variables VITE_SERVER_PORT and PORT are properly set before command execution. The srvx static server respects the PORT variable across the codebase, and the command syntax is standard for all target platforms. No changes needed.

e2e/react-start/i18n-paraglide/messages/en.json (1)

1-8: LGTM! Well-structured i18n message file.

The message format follows the Inlang schema correctly with appropriate parameterized messages. All message keys (example_message, server_message, about_message, home_page, about_page) are properly used throughout the codebase.

e2e/react-router/i18n-paraglide/messages/de.json (1)

1-7: LGTM! Valid i18n resource file.

The German translation file is properly structured with correct schema reference and translation keys that align with the English counterpart.

e2e/react-start/i18n-paraglide/messages/de.json (1)

1-8: LGTM! Valid i18n resource file.

The German translation file is properly structured with correct schema reference and includes appropriate keys for the React Start SSR context.

e2e/react-router/i18n-paraglide/tsconfig.json (1)

14-16: LGTM! Path alias configuration is correct.

The @/* alias mapping to ./src/* follows common conventions and will work correctly with the project structure.

e2e/react-router/i18n-paraglide/project.inlang/settings.json (1)

1-12: LGTM! Valid Inlang configuration.

The settings file correctly defines the base locale, supported locales, and message path pattern that aligns with the actual file structure (./messages/en.json, ./messages/de.json).

e2e/react-router/i18n-paraglide/src/routes/index.tsx (1)

1-18: LGTM! Route implementation follows TanStack Router patterns correctly.

The file-based route is properly configured with createFileRoute('/'), the component correctly renders the localized message with parameters, and includes appropriate test attributes for e2e testing.

e2e/react-start/i18n-paraglide/src/routes/about.tsx (1)

1-10: LGTM! Clean about route implementation.

The file-based route is properly configured with createFileRoute('/about') and the component correctly renders the localized message. The implementation is clean and appropriate for e2e testing.

e2e/react-start/i18n-paraglide/package.json (2)

14-16: LGTM! Internal dependencies correctly use workspace protocol.

The TanStack packages correctly use workspace:^ for internal dependencies, which aligns with the coding guidelines.


17-18: React 19.0.0 is officially supported by TanStack Router and no compatibility issues are documented. The project is testing against the workspace version, which ensures compatibility with the development branch. No action needed.

e2e/react-router/i18n-paraglide/package.json (2)

15-15: LGTM! Internal dependency correctly uses workspace protocol.

The @tanstack/react-router package correctly uses workspace:^ for the internal dependency, which aligns with the coding guidelines.


16-17: TanStack Router is compatible with React 19—both v18.x.x and v19.x.x are supported per the official documentation.

e2e/react-router/i18n-paraglide/src/routes/about.tsx (1)

1-10: LGTM!

Clean route implementation following the established pattern. The data-testid attribute enables reliable e2e test targeting.

e2e/react-start/i18n-paraglide/src/routes/__root.tsx (1)

1-85: LGTM!

Well-structured SSR root component with proper HTML shell rendering. The locale switcher pattern with data-active-locale provides a clean way to style the active locale. The absence of a beforeLoad redirect hook is appropriate here since redirects are handled server-side in the Start framework.

e2e/react-router/i18n-paraglide/src/main.tsx (2)

10-22: Core router configuration for the i18n rewrite fix.

The rewrite.input/output configuration correctly separates the internal routing URL (de-localized) from the browser-visible URL (localized). This is the key mechanism that prevents the infinite redirect loop by allowing the router to match routes on canonical paths while displaying locale-prefixed paths to users.


24-29: Type registration pattern is correct.

Module augmentation properly registers the router type for type-safe usage throughout the application.

e2e/react-router/i18n-paraglide/vite.config.ts (1)

8-49: LGTM!

Well-configured Vite setup with Paraglide URL patterns correctly mapping both the canonical routes (/, /about) and their localized variants. The catch-all pattern at the end ensures all other routes are also properly localized.

e2e/react-start/i18n-paraglide/src/routes/index.tsx (1)

5-9: Server function demonstrates i18n on the server side.

The inputValidator is a passthrough, which is acceptable for this e2e demo. In production code, you'd typically add proper validation.

e2e/react-router/i18n-paraglide/src/routes/__root.tsx (2)

10-19: Client-side redirect handling for SPA i18n.

The beforeLoad hook correctly sets the document language and handles locale-based redirects for the SPA scenario. Using decision.redirectUrl.href for the redirect target is appropriate here.


20-68: LGTM!

The component structure is clean with proper navigation links and locale switcher. The data-testid attributes enable reliable e2e test targeting.

e2e/react-start/i18n-paraglide/src/router.tsx (1)

7-18: Router factory with i18n rewrite configuration.

The factory pattern is appropriate for SSR scenarios. The rewrite configuration mirrors the SPA setup, ensuring consistent locale URL transformation.

Note: Unlike the SPA main.tsx, this file doesn't include the module augmentation for type safety (declare module '@tanstack/react-router' { interface Register { router: ... } }). If type inference for route parameters is needed elsewhere in the Start app, consider adding type registration.

e2e/react-router/i18n-paraglide/playwright.config.ts (1)

1-35: LGTM!

The Playwright configuration follows the established patterns in this repository. The dynamic port allocation via getTestServerPort, webServer setup with build/preview commands, and CI-aware reuseExistingServer setting are all appropriately configured for the i18n e2e tests.

e2e/react-router/i18n-paraglide/tests/navigation.spec.ts (3)

1-7: Good test documentation.

The header comment clearly explains the purpose of these tests and the rewrite API's bidirectional URL transformation (de-localization for route matching, localization for display). This provides valuable context for future maintainers.


8-105: Comprehensive i18n navigation test coverage.

This test suite effectively validates the fix for issue #6089 by covering:

  • Page loads without redirect loops (the core regression)
  • Locale-prefixed routing (/de, /de/ueber)
  • Navigation with translated URLs
  • Locale switching with proper URL updates
  • Link href localization

The use of data-testid selectors ensures stable test targeting.


221-246: LGTM!

These tests effectively verify that the rewrite output function correctly localizes link href attributes based on the current locale—German links include /de prefix with translated paths, while English links remain unprefixed.

e2e/react-start/i18n-paraglide/src/routeTree.gen.ts (1)

1-86: Skipping review of autogenerated file.

This file is automatically generated by TanStack Router and should not be manually modified. Based on learnings, routeTree.gen.ts files are excluded from review.

{
"compilerOptions": {
"strict": true,
"noImplicitAny": false,
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

Remove noImplicitAny: false to align with strict type safety guidelines.

The coding guidelines require "extensive type safety for all code." Setting noImplicitAny: false weakens type checking and contradicts the strict: true setting on Line 3. When strict is enabled, noImplicitAny defaults to true, so this line explicitly disables an important safety check.

As per coding guidelines, use TypeScript strict mode with extensive type safety for all code.

🔎 Recommended fix
   "compilerOptions": {
     "strict": true,
-    "noImplicitAny": false,
     "esModuleInterop": 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
"noImplicitAny": false,
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
🤖 Prompt for AI Agents
In e2e/react-router/i18n-paraglide/tsconfig.json around line 4, the compiler
option "noImplicitAny": false must be removed (or set to true) to comply with
strict type safety; edit the tsconfig to delete that line so the "strict": true
setting isn't contradicted (or explicitly set "noImplicitAny": true if you
prefer explicitness), then run the TypeScript build to fix any resulting
implicit-any errors.

Comment on lines +20 to +22
{ name: 'og:type', content: 'website' },
{ name: 'og:title', content: title },
{ name: 'og:description', content: description },
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 property attribute for Open Graph meta tags.

Open Graph tags should use the property attribute instead of name according to the Open Graph protocol specification. Social media platforms (Facebook, LinkedIn, etc.) look for property="og:*" and may not recognize these tags correctly with name="og:*".

🔎 Proposed fix
-    { name: 'og:type', content: 'website' },
-    { name: 'og:title', content: title },
-    { name: 'og:description', content: description },
+    { property: 'og:type', content: 'website' },
+    { property: 'og:title', content: title },
+    { property: 'og:description', content: description },

And for line 27 in the conditional image block:

-          { name: 'og:image', content: image },
+          { property: 'og:image', content: image },

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In e2e/react-start/i18n-paraglide/src/utils/seo.ts around lines 20-22 (and the
conditional image block at line 27), replace meta entries that use name: 'og:*'
with property: 'og:*' so Open Graph tags are rendered with the property
attribute (e.g., property: 'og:type', property: 'og:title', property:
'og:description', and the og:image entry in the conditional block); keep the
content values unchanged and ensure any code that maps these objects to actual
<meta> elements uses the property key when rendering Open Graph tags.

@schiller-manuel schiller-manuel merged commit 17c07d9 into main Dec 23, 2025
6 checks passed
@schiller-manuel schiller-manuel deleted the fix-6089 branch December 23, 2025 11:37
schiller-manuel added a commit that referenced this pull request Dec 24, 2025
this reverts #6201

the correct fix from #6172 was applied instead.
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 Report: Paraglide i18n Broken Since v1.141.2

2 participants