From 95708f20a45c9a0ec204bfa06eec7c5dab01fdcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A2=85=EA=B2=BD?= Date: Sun, 4 Jan 2026 19:28:13 +0900 Subject: [PATCH 1/5] fix: update cleanPath to replace pipe characters with encoded value --- packages/router-core/src/path.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router-core/src/path.ts b/packages/router-core/src/path.ts index 859a900496a..4243d52d82a 100644 --- a/packages/router-core/src/path.ts +++ b/packages/router-core/src/path.ts @@ -22,7 +22,7 @@ export function joinPaths(paths: Array) { /** Remove repeated slashes from a path string. */ export function cleanPath(path: string) { // remove double slashes - return path.replace(/\/{2,}/g, '/') + return path.replace(/\/{2,}/g, '/').replace(/\|/g, '%7C') } /** Trim leading slashes (except preserving root '/'). */ From 6d1ae3e58cb9bb4f952f4c4d7100249249667edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A2=85=EA=B2=BD?= Date: Sun, 4 Jan 2026 19:36:15 +0900 Subject: [PATCH 2/5] feat: add route for special character pipe in path and corresponding tests --- .../basic-file-based/src/routeTree.gen.ts | 21 +++++++++++++++++++ .../src/routes/special|pipe.tsx | 5 +++++ .../basic-file-based/tests/app.spec.ts | 13 ++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 e2e/react-router/basic-file-based/src/routes/special|pipe.tsx diff --git a/e2e/react-router/basic-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-file-based/src/routeTree.gen.ts index 28197e4d987..06620f1dc0a 100644 --- a/e2e/react-router/basic-file-based/src/routeTree.gen.ts +++ b/e2e/react-router/basic-file-based/src/routeTree.gen.ts @@ -9,6 +9,7 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' +import { Route as SpecialChar124pipeRouteImport } from './routes/special|pipe' import { Route as RemountDepsRouteImport } from './routes/remountDeps' import { Route as PostsRouteImport } from './routes/posts' import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' @@ -118,6 +119,11 @@ import { Route as NonNestedDeepBazBarFooRouteRouteImport } from './routes/non-ne import { Route as NonNestedDeepBazBarFooIndexRouteImport } from './routes/non-nested/deep/$baz_.bar.$foo.index' import { Route as NonNestedDeepBazBarFooQuxRouteImport } from './routes/non-nested/deep/$baz_.bar.$foo_.qux' +const SpecialChar124pipeRoute = SpecialChar124pipeRouteImport.update({ + id: '/special|pipe', + path: '/special|pipe', + getParentRoute: () => rootRouteImport, +} as any) const RemountDepsRoute = RemountDepsRouteImport.update({ id: '/remountDeps', path: '/remountDeps', @@ -715,6 +721,7 @@ export interface FileRoutesByFullPath { '/notRemountDeps': typeof NotRemountDepsRoute '/posts': typeof PostsRouteWithChildren '/remountDeps': typeof RemountDepsRoute + '/special|pipe': typeof SpecialChar124pipeRoute '/non-nested/deep': typeof NonNestedDeepRouteRouteWithChildren '/non-nested/named': typeof NonNestedNamedRouteRouteWithChildren '/non-nested/path': typeof NonNestedPathRouteRouteWithChildren @@ -819,6 +826,7 @@ export interface FileRoutesByTo { '/masks': typeof MasksRouteWithChildren '/notRemountDeps': typeof NotRemountDepsRoute '/remountDeps': typeof RemountDepsRoute + '/special|pipe': typeof SpecialChar124pipeRoute '/non-nested/deep': typeof NonNestedDeepRouteRouteWithChildren '/non-nested/named': typeof NonNestedNamedRouteRouteWithChildren '/non-nested/path': typeof NonNestedPathRouteRouteWithChildren @@ -918,6 +926,7 @@ export interface FileRoutesById { '/notRemountDeps': typeof NotRemountDepsRoute '/posts': typeof PostsRouteWithChildren '/remountDeps': typeof RemountDepsRoute + '/special|pipe': typeof SpecialChar124pipeRoute '/non-nested/deep': typeof NonNestedDeepRouteRouteWithChildren '/non-nested/named': typeof NonNestedNamedRouteRouteWithChildren '/non-nested/path': typeof NonNestedPathRouteRouteWithChildren @@ -1029,6 +1038,7 @@ export interface FileRouteTypes { | '/notRemountDeps' | '/posts' | '/remountDeps' + | '/special|pipe' | '/non-nested/deep' | '/non-nested/named' | '/non-nested/path' @@ -1133,6 +1143,7 @@ export interface FileRouteTypes { | '/masks' | '/notRemountDeps' | '/remountDeps' + | '/special|pipe' | '/non-nested/deep' | '/non-nested/named' | '/non-nested/path' @@ -1231,6 +1242,7 @@ export interface FileRouteTypes { | '/notRemountDeps' | '/posts' | '/remountDeps' + | '/special|pipe' | '/non-nested/deep' | '/non-nested/named' | '/non-nested/path' @@ -1342,6 +1354,7 @@ export interface RootRouteChildren { NotRemountDepsRoute: typeof NotRemountDepsRoute PostsRoute: typeof PostsRouteWithChildren RemountDepsRoute: typeof RemountDepsRoute + SpecialChar124pipeRoute: typeof SpecialChar124pipeRoute ParamsPsNonNestedRouteRoute: typeof ParamsPsNonNestedRouteRouteWithChildren RelativeLinkRouteRoute: typeof RelativeLinkRouteRouteWithChildren RelativeUseNavigateRouteRoute: typeof RelativeUseNavigateRouteRouteWithChildren @@ -1374,6 +1387,13 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/special|pipe': { + id: '/special|pipe' + path: '/special|pipe' + fullPath: '/special|pipe' + preLoaderRoute: typeof SpecialChar124pipeRouteImport + parentRoute: typeof rootRouteImport + } '/remountDeps': { id: '/remountDeps' path: '/remountDeps' @@ -2598,6 +2618,7 @@ const rootRouteChildren: RootRouteChildren = { NotRemountDepsRoute: NotRemountDepsRoute, PostsRoute: PostsRouteWithChildren, RemountDepsRoute: RemountDepsRoute, + SpecialChar124pipeRoute: SpecialChar124pipeRoute, ParamsPsNonNestedRouteRoute: ParamsPsNonNestedRouteRouteWithChildren, RelativeLinkRouteRoute: RelativeLinkRouteRouteWithChildren, RelativeUseNavigateRouteRoute: RelativeUseNavigateRouteRouteWithChildren, diff --git a/e2e/react-router/basic-file-based/src/routes/special|pipe.tsx b/e2e/react-router/basic-file-based/src/routes/special|pipe.tsx new file mode 100644 index 00000000000..e8b6e4e567b --- /dev/null +++ b/e2e/react-router/basic-file-based/src/routes/special|pipe.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/special|pipe')({ + component: () =>
Hello /special|pipe!
, +}) diff --git a/e2e/react-router/basic-file-based/tests/app.spec.ts b/e2e/react-router/basic-file-based/tests/app.spec.ts index f644c100429..2f6d0ccb887 100644 --- a/e2e/react-router/basic-file-based/tests/app.spec.ts +++ b/e2e/react-router/basic-file-based/tests/app.spec.ts @@ -394,3 +394,16 @@ test.describe('Pathless layout routes', () => { await expect(page.locator('body')).toContainText('Not Found') }) }) + +test.describe('Special characters in route paths', () => { + test('should render route with pipe character in path', async ({ + page, + baseURL, + }) => { + await page.goto('/special|pipe') + + await expect(page.locator('body')).toContainText('Hello /special|pipe!') + + expect(page.url()).toBe(`${baseURL}/special%7Cpipe`) + }) +}) From 4eaf78d86c3bd10ed5ad37d75d5019eb49f57a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A2=85=EA=B2=BD?= Date: Wed, 7 Jan 2026 21:00:26 +0900 Subject: [PATCH 3/5] feat: add route and tests for handling pipe character in path --- e2e/react-start/basic/src/routeTree.gen.ts | 21 ++++++++++++++++ e2e/react-start/basic/src/routes/__root.tsx | 9 +++++++ .../basic/src/routes/pipe.$reference.tsx | 10 ++++++++ .../tests/pipe-character-in-path.spec.ts | 24 +++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 e2e/react-start/basic/src/routes/pipe.$reference.tsx create mode 100644 e2e/react-start/basic/tests/pipe-character-in-path.spec.ts diff --git a/e2e/react-start/basic/src/routeTree.gen.ts b/e2e/react-start/basic/src/routeTree.gen.ts index 27d5e911452..2a72e412652 100644 --- a/e2e/react-start/basic/src/routeTree.gen.ts +++ b/e2e/react-start/basic/src/routeTree.gen.ts @@ -42,6 +42,7 @@ import { Route as RawStreamSsrMixedRouteImport } from './routes/raw-stream/ssr-m import { Route as RawStreamSsrBinaryHintRouteImport } from './routes/raw-stream/ssr-binary-hint' import { Route as RawStreamClientCallRouteImport } from './routes/raw-stream/client-call' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as PipeReferenceRouteImport } from './routes/pipe.$reference' import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader' import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad' import { Route as MultiCookieRedirectTargetRouteImport } from './routes/multi-cookie-redirect/target' @@ -228,6 +229,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) +const PipeReferenceRoute = PipeReferenceRouteImport.update({ + id: '/pipe/$reference', + path: '/pipe/$reference', + getParentRoute: () => rootRouteImport, +} as any) const NotFoundViaLoaderRoute = NotFoundViaLoaderRouteImport.update({ id: '/via-loader', path: '/via-loader', @@ -343,6 +349,7 @@ export interface FileRoutesByFullPath { '/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute + '/pipe/$reference': typeof PipeReferenceRoute '/posts/$postId': typeof PostsPostIdRoute '/raw-stream/client-call': typeof RawStreamClientCallRoute '/raw-stream/ssr-binary-hint': typeof RawStreamSsrBinaryHintRoute @@ -389,6 +396,7 @@ export interface FileRoutesByTo { '/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute + '/pipe/$reference': typeof PipeReferenceRoute '/posts/$postId': typeof PostsPostIdRoute '/raw-stream/client-call': typeof RawStreamClientCallRoute '/raw-stream/ssr-binary-hint': typeof RawStreamSsrBinaryHintRoute @@ -441,6 +449,7 @@ export interface FileRoutesById { '/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute + '/pipe/$reference': typeof PipeReferenceRoute '/posts/$postId': typeof PostsPostIdRoute '/raw-stream/client-call': typeof RawStreamClientCallRoute '/raw-stream/ssr-binary-hint': typeof RawStreamSsrBinaryHintRoute @@ -494,6 +503,7 @@ export interface FileRouteTypes { | '/multi-cookie-redirect/target' | '/not-found/via-beforeLoad' | '/not-found/via-loader' + | '/pipe/$reference' | '/posts/$postId' | '/raw-stream/client-call' | '/raw-stream/ssr-binary-hint' @@ -540,6 +550,7 @@ export interface FileRouteTypes { | '/multi-cookie-redirect/target' | '/not-found/via-beforeLoad' | '/not-found/via-loader' + | '/pipe/$reference' | '/posts/$postId' | '/raw-stream/client-call' | '/raw-stream/ssr-binary-hint' @@ -591,6 +602,7 @@ export interface FileRouteTypes { | '/multi-cookie-redirect/target' | '/not-found/via-beforeLoad' | '/not-found/via-loader' + | '/pipe/$reference' | '/posts/$postId' | '/raw-stream/client-call' | '/raw-stream/ssr-binary-hint' @@ -642,6 +654,7 @@ export interface RootRouteChildren { Char45824Char54620Char48124Char44397Route: typeof Char45824Char54620Char48124Char44397Route ApiUsersRoute: typeof ApiUsersRouteWithChildren MultiCookieRedirectTargetRoute: typeof MultiCookieRedirectTargetRoute + PipeReferenceRoute: typeof PipeReferenceRoute RedirectTargetRoute: typeof RedirectTargetRouteWithChildren MultiCookieRedirectIndexRoute: typeof MultiCookieRedirectIndexRoute RedirectIndexRoute: typeof RedirectIndexRoute @@ -882,6 +895,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } + '/pipe/$reference': { + id: '/pipe/$reference' + path: '/pipe/$reference' + fullPath: '/pipe/$reference' + preLoaderRoute: typeof PipeReferenceRouteImport + parentRoute: typeof rootRouteImport + } '/not-found/via-loader': { id: '/not-found/via-loader' path: '/via-loader' @@ -1184,6 +1204,7 @@ const rootRouteChildren: RootRouteChildren = { Char45824Char54620Char48124Char44397Route, ApiUsersRoute: ApiUsersRouteWithChildren, MultiCookieRedirectTargetRoute: MultiCookieRedirectTargetRoute, + PipeReferenceRoute: PipeReferenceRoute, RedirectTargetRoute: RedirectTargetRouteWithChildren, MultiCookieRedirectIndexRoute: MultiCookieRedirectIndexRoute, RedirectIndexRoute: RedirectIndexRoute, diff --git a/e2e/react-start/basic/src/routes/__root.tsx b/e2e/react-start/basic/src/routes/__root.tsx index e1862b499c6..145b9ff76d4 100644 --- a/e2e/react-start/basic/src/routes/__root.tsx +++ b/e2e/react-start/basic/src/routes/__root.tsx @@ -181,6 +181,15 @@ function RootDocument({ children }: { children: React.ReactNode }) { > Raw Stream {' '} + + Dynamic Pipe Character + {' '} Hello {params.reference}! +} diff --git a/e2e/react-start/basic/tests/pipe-character-in-path.spec.ts b/e2e/react-start/basic/tests/pipe-character-in-path.spec.ts new file mode 100644 index 00000000000..0c57126c64b --- /dev/null +++ b/e2e/react-start/basic/tests/pipe-character-in-path.spec.ts @@ -0,0 +1,24 @@ +import { expect } from '@playwright/test' + +import { test } from '@tanstack/router-e2e-utils' + +test('encodes pipe character in href for param link', async ({ page }) => { + await page.goto('/pipe/hello|world') + + await expect(page.locator('body')).toContainText('Hello hello|world!') +}) + +test('direct navigation keeps encoded url after reload', async ({ + page, + baseURL, +}) => { + await page.goto('/pipe/hello|world') + + await expect(page.locator('body')).toContainText('Hello hello|world!') + expect(page.url()).toBe(`${baseURL}/pipe/hello%7Cworld`) + + await page.reload() + + await expect(page.locator('body')).toContainText('Hello hello|world!') + expect(page.url()).toBe(`${baseURL}/pipe/hello%7Cworld`) +}) From eab09b669eb785e308c250d1bad6115b72f15b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A2=85=EA=B2=BD?= Date: Sat, 10 Jan 2026 14:28:06 +0900 Subject: [PATCH 4/5] fixup! feat: add route for special character pipe in path and corresponding tests --- .../basic-file-based/src/routeTree.gen.ts | 42 +++++++++---------- .../basic-file-based/src/routes/__root.tsx | 9 ++++ .../src/routes/pipe.$reference.tsx | 10 +++++ .../src/routes/special|pipe.tsx | 5 --- .../basic-file-based/tests/app.spec.ts | 6 +-- 5 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 e2e/react-router/basic-file-based/src/routes/pipe.$reference.tsx delete mode 100644 e2e/react-router/basic-file-based/src/routes/special|pipe.tsx diff --git a/e2e/react-router/basic-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-file-based/src/routeTree.gen.ts index 06620f1dc0a..d8f74a9b0ff 100644 --- a/e2e/react-router/basic-file-based/src/routeTree.gen.ts +++ b/e2e/react-router/basic-file-based/src/routeTree.gen.ts @@ -9,7 +9,6 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as SpecialChar124pipeRouteImport } from './routes/special|pipe' import { Route as RemountDepsRouteImport } from './routes/remountDeps' import { Route as PostsRouteImport } from './routes/posts' import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' @@ -34,6 +33,7 @@ import { Route as StructuralSharingEnabledRouteImport } from './routes/structura import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as PipeReferenceRouteImport } from './routes/pipe.$reference' import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' import { Route as groupInsideRouteImport } from './routes/(group)/inside' @@ -119,11 +119,6 @@ import { Route as NonNestedDeepBazBarFooRouteRouteImport } from './routes/non-ne import { Route as NonNestedDeepBazBarFooIndexRouteImport } from './routes/non-nested/deep/$baz_.bar.$foo.index' import { Route as NonNestedDeepBazBarFooQuxRouteImport } from './routes/non-nested/deep/$baz_.bar.$foo_.qux' -const SpecialChar124pipeRoute = SpecialChar124pipeRouteImport.update({ - id: '/special|pipe', - path: '/special|pipe', - getParentRoute: () => rootRouteImport, -} as any) const RemountDepsRoute = RemountDepsRouteImport.update({ id: '/remountDeps', path: '/remountDeps', @@ -245,6 +240,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) +const PipeReferenceRoute = PipeReferenceRouteImport.update({ + id: '/pipe/$reference', + path: '/pipe/$reference', + getParentRoute: () => rootRouteImport, +} as any) const LayoutLayout2Route = LayoutLayout2RouteImport.update({ id: '/_layout-2', getParentRoute: () => LayoutRoute, @@ -721,7 +721,6 @@ export interface FileRoutesByFullPath { '/notRemountDeps': typeof NotRemountDepsRoute '/posts': typeof PostsRouteWithChildren '/remountDeps': typeof RemountDepsRoute - '/special|pipe': typeof SpecialChar124pipeRoute '/non-nested/deep': typeof NonNestedDeepRouteRouteWithChildren '/non-nested/named': typeof NonNestedNamedRouteRouteWithChildren '/non-nested/path': typeof NonNestedPathRouteRouteWithChildren @@ -733,6 +732,7 @@ export interface FileRoutesByFullPath { '/onlyrouteinside': typeof anotherGroupOnlyrouteinsideRoute '/inside': typeof groupInsideRoute '/lazyinside': typeof groupLazyinsideRoute + '/pipe/$reference': typeof PipeReferenceRoute '/posts/$postId': typeof PostsPostIdRoute '/redirect/$target': typeof RedirectTargetRouteWithChildren '/search-params/default': typeof SearchParamsDefaultRoute @@ -826,7 +826,6 @@ export interface FileRoutesByTo { '/masks': typeof MasksRouteWithChildren '/notRemountDeps': typeof NotRemountDepsRoute '/remountDeps': typeof RemountDepsRoute - '/special|pipe': typeof SpecialChar124pipeRoute '/non-nested/deep': typeof NonNestedDeepRouteRouteWithChildren '/non-nested/named': typeof NonNestedNamedRouteRouteWithChildren '/non-nested/path': typeof NonNestedPathRouteRouteWithChildren @@ -838,6 +837,7 @@ export interface FileRoutesByTo { '/onlyrouteinside': typeof anotherGroupOnlyrouteinsideRoute '/inside': typeof groupInsideRoute '/lazyinside': typeof groupLazyinsideRoute + '/pipe/$reference': typeof PipeReferenceRoute '/posts/$postId': typeof PostsPostIdRoute '/search-params/default': typeof SearchParamsDefaultRoute '/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute @@ -926,7 +926,6 @@ export interface FileRoutesById { '/notRemountDeps': typeof NotRemountDepsRoute '/posts': typeof PostsRouteWithChildren '/remountDeps': typeof RemountDepsRoute - '/special|pipe': typeof SpecialChar124pipeRoute '/non-nested/deep': typeof NonNestedDeepRouteRouteWithChildren '/non-nested/named': typeof NonNestedNamedRouteRouteWithChildren '/non-nested/path': typeof NonNestedPathRouteRouteWithChildren @@ -941,6 +940,7 @@ export interface FileRoutesById { '/(group)/inside': typeof groupInsideRoute '/(group)/lazyinside': typeof groupLazyinsideRoute '/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren + '/pipe/$reference': typeof PipeReferenceRoute '/posts/$postId': typeof PostsPostIdRoute '/redirect/$target': typeof RedirectTargetRouteWithChildren '/search-params/default': typeof SearchParamsDefaultRoute @@ -1038,7 +1038,6 @@ export interface FileRouteTypes { | '/notRemountDeps' | '/posts' | '/remountDeps' - | '/special|pipe' | '/non-nested/deep' | '/non-nested/named' | '/non-nested/path' @@ -1050,6 +1049,7 @@ export interface FileRouteTypes { | '/onlyrouteinside' | '/inside' | '/lazyinside' + | '/pipe/$reference' | '/posts/$postId' | '/redirect/$target' | '/search-params/default' @@ -1143,7 +1143,6 @@ export interface FileRouteTypes { | '/masks' | '/notRemountDeps' | '/remountDeps' - | '/special|pipe' | '/non-nested/deep' | '/non-nested/named' | '/non-nested/path' @@ -1155,6 +1154,7 @@ export interface FileRouteTypes { | '/onlyrouteinside' | '/inside' | '/lazyinside' + | '/pipe/$reference' | '/posts/$postId' | '/search-params/default' | '/structural-sharing/$enabled' @@ -1242,7 +1242,6 @@ export interface FileRouteTypes { | '/notRemountDeps' | '/posts' | '/remountDeps' - | '/special|pipe' | '/non-nested/deep' | '/non-nested/named' | '/non-nested/path' @@ -1257,6 +1256,7 @@ export interface FileRouteTypes { | '/(group)/inside' | '/(group)/lazyinside' | '/_layout/_layout-2' + | '/pipe/$reference' | '/posts/$postId' | '/redirect/$target' | '/search-params/default' @@ -1354,7 +1354,6 @@ export interface RootRouteChildren { NotRemountDepsRoute: typeof NotRemountDepsRoute PostsRoute: typeof PostsRouteWithChildren RemountDepsRoute: typeof RemountDepsRoute - SpecialChar124pipeRoute: typeof SpecialChar124pipeRoute ParamsPsNonNestedRouteRoute: typeof ParamsPsNonNestedRouteRouteWithChildren RelativeLinkRouteRoute: typeof RelativeLinkRouteRouteWithChildren RelativeUseNavigateRouteRoute: typeof RelativeUseNavigateRouteRouteWithChildren @@ -1362,6 +1361,7 @@ export interface RootRouteChildren { groupLayoutRoute: typeof groupLayoutRouteWithChildren groupInsideRoute: typeof groupInsideRoute groupLazyinsideRoute: typeof groupLazyinsideRoute + PipeReferenceRoute: typeof PipeReferenceRoute RedirectTargetRoute: typeof RedirectTargetRouteWithChildren StructuralSharingEnabledRoute: typeof StructuralSharingEnabledRoute ParamsPsIndexRoute: typeof ParamsPsIndexRoute @@ -1387,13 +1387,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/special|pipe': { - id: '/special|pipe' - path: '/special|pipe' - fullPath: '/special|pipe' - preLoaderRoute: typeof SpecialChar124pipeRouteImport - parentRoute: typeof rootRouteImport - } '/remountDeps': { id: '/remountDeps' path: '/remountDeps' @@ -1562,6 +1555,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } + '/pipe/$reference': { + id: '/pipe/$reference' + path: '/pipe/$reference' + fullPath: '/pipe/$reference' + preLoaderRoute: typeof PipeReferenceRouteImport + parentRoute: typeof rootRouteImport + } '/_layout/_layout-2': { id: '/_layout/_layout-2' path: '' @@ -2618,7 +2618,6 @@ const rootRouteChildren: RootRouteChildren = { NotRemountDepsRoute: NotRemountDepsRoute, PostsRoute: PostsRouteWithChildren, RemountDepsRoute: RemountDepsRoute, - SpecialChar124pipeRoute: SpecialChar124pipeRoute, ParamsPsNonNestedRouteRoute: ParamsPsNonNestedRouteRouteWithChildren, RelativeLinkRouteRoute: RelativeLinkRouteRouteWithChildren, RelativeUseNavigateRouteRoute: RelativeUseNavigateRouteRouteWithChildren, @@ -2626,6 +2625,7 @@ const rootRouteChildren: RootRouteChildren = { groupLayoutRoute: groupLayoutRouteWithChildren, groupInsideRoute: groupInsideRoute, groupLazyinsideRoute: groupLazyinsideRoute, + PipeReferenceRoute: PipeReferenceRoute, RedirectTargetRoute: RedirectTargetRouteWithChildren, StructuralSharingEnabledRoute: StructuralSharingEnabledRoute, ParamsPsIndexRoute: ParamsPsIndexRoute, diff --git a/e2e/react-router/basic-file-based/src/routes/__root.tsx b/e2e/react-router/basic-file-based/src/routes/__root.tsx index dde8139a6e2..ff04634fcb3 100644 --- a/e2e/react-router/basic-file-based/src/routes/__root.tsx +++ b/e2e/react-router/basic-file-based/src/routes/__root.tsx @@ -156,6 +156,15 @@ function RootComponent() { > Masks {' '} + + Dynamic Pipe Character + {' '} Hello {params.reference}! +} diff --git a/e2e/react-router/basic-file-based/src/routes/special|pipe.tsx b/e2e/react-router/basic-file-based/src/routes/special|pipe.tsx deleted file mode 100644 index e8b6e4e567b..00000000000 --- a/e2e/react-router/basic-file-based/src/routes/special|pipe.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/special|pipe')({ - component: () =>
Hello /special|pipe!
, -}) diff --git a/e2e/react-router/basic-file-based/tests/app.spec.ts b/e2e/react-router/basic-file-based/tests/app.spec.ts index 2f6d0ccb887..43f429ad2a7 100644 --- a/e2e/react-router/basic-file-based/tests/app.spec.ts +++ b/e2e/react-router/basic-file-based/tests/app.spec.ts @@ -400,10 +400,10 @@ test.describe('Special characters in route paths', () => { page, baseURL, }) => { - await page.goto('/special|pipe') + await page.goto('/pipe/hello|world') - await expect(page.locator('body')).toContainText('Hello /special|pipe!') + await expect(page.locator('body')).toContainText('Hello hello|world!') - expect(page.url()).toBe(`${baseURL}/special%7Cpipe`) + expect(page.url()).toBe(`${baseURL}/pipe/hello%7Cworld`) }) }) From 8a683d919e1c17122acd495101e3769372612932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A2=85=EA=B2=BD?= Date: Sat, 10 Jan 2026 14:43:52 +0900 Subject: [PATCH 5/5] fixup! fix: update cleanPath to replace pipe characters with encoded value --- packages/router-core/src/path.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router-core/src/path.ts b/packages/router-core/src/path.ts index 4243d52d82a..7f382fa6bd3 100644 --- a/packages/router-core/src/path.ts +++ b/packages/router-core/src/path.ts @@ -19,7 +19,7 @@ export function joinPaths(paths: Array) { ) } -/** Remove repeated slashes from a path string. */ +/** Remove repeated slashes and replace | to %7C from a path string. */ export function cleanPath(path: string) { // remove double slashes return path.replace(/\/{2,}/g, '/').replace(/\|/g, '%7C')