Skip to content

Commit 574f848

Browse files
authored
Merge branch 'main' into fix/validation-types-circular-reference
2 parents 7a5c8ee + 2d74463 commit 574f848

File tree

174 files changed

+777
-535
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

174 files changed

+777
-535
lines changed

README.md

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ alt="TanStack Router"
1313

1414
A modern router designed for type safety, data‑driven navigation, and seamless developer experience.
1515

16-
- End‑toend type safety (routes, params, loaders)
16+
- End‑to-end type safety (routes, params, loaders)
1717
- Schema‑driven search params with validation
1818
- Built‑in caching, prefetching & invalidation
1919
- Nested layouts, transitions & error boundaries
@@ -45,25 +45,12 @@ A full‑stack framework built on Router, designed for server rendering, streami
4545

4646
<br />
4747

48-
<div align="center">
49-
<a href="https://npmjs.com/package/@tanstack/react-router">
50-
<img src="https://img.shields.io/npm/dm/@tanstack/react-router.svg" alt="npm downloads" />
51-
</a>
52-
<a href="https://github.com/tanstack/router">
53-
<img src="https://img.shields.io/github/stars/tanstack/router.svg?style=social&label=Star" alt="GitHub stars" />
54-
</a>
55-
<a href="https://bundlephobia.com/result?p=@tanstack/react-router">
56-
<img src="https://badgen.net/bundlephobia/minzip/@tanstack/react-router" alt="Bundle size" />
57-
</a>
58-
</div>
59-
60-
<div align="center">
61-
<a href="#badge">
62-
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
63-
</a>
64-
<a href="https://bestofjs.org/projects/tanstack-router"><img alt="Best of JS" src="https://img.shields.io/endpoint?url=https://bestofjs-serverless.now.sh/api/project-badge?fullName=TanStack%2Frouter%26since=daily" /></a>
65-
<a href="https://twitter.com/tan_stack"><img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/></a>
66-
</div>
48+
<p align="center">
49+
<a href="https://npmjs.com/package/@tanstack/react-router"><img src="https://img.shields.io/npm/dm/@tanstack/react-router.svg" alt="npm downloads" /></a> <a href="https://github.com/tanstack/router"><img src="https://img.shields.io/github/stars/tanstack/router.svg?style=social&label=Star" alt="GitHub stars" /></a> <a href="https://bundlephobia.com/result?p=@tanstack/react-router"><img src="https://badgen.net/bundlephobia/minzip/@tanstack/react-router" alt="Bundle size" /></a>
50+
</p>
51+
<p align="center">
52+
<a href="#badge"><img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg"></a> <a href="https://bestofjs.org/projects/tanstack-router"><img alt="Best of JS" src="https://img.shields.io/endpoint?url=https://bestofjs-serverless.now.sh/api/project-badge?fullName=TanStack%2Frouter%26since=daily" /></a> <a href="https://twitter.com/tan_stack"><img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/></a>
53+
</p>
6754

6855
<div align="center">
6956

e2e/react-start/basic/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@tanstack/react-start": "workspace:^",
2727
"express": "^5.1.0",
2828
"http-proxy-middleware": "^3.0.5",
29+
"js-cookie": "^3.0.5",
2930
"react": "^19.0.0",
3031
"react-dom": "^19.0.0",
3132
"redaxios": "^0.5.1",
@@ -35,6 +36,7 @@
3536
"@playwright/test": "^1.50.1",
3637
"@tailwindcss/postcss": "^4.1.15",
3738
"@tanstack/router-e2e-utils": "workspace:^",
39+
"@types/js-cookie": "^3.0.6",
3840
"@types/node": "^22.10.2",
3941
"@types/react": "^19.0.8",
4042
"@types/react-dom": "^19.0.3",

e2e/react-start/basic/src/routeTree.gen.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { Route as SearchParamsIndexRouteImport } from './routes/search-params/in
2828
import { Route as RedirectIndexRouteImport } from './routes/redirect/index'
2929
import { Route as PostsIndexRouteImport } from './routes/posts.index'
3030
import { Route as NotFoundIndexRouteImport } from './routes/not-found/index'
31+
import { Route as MultiCookieRedirectIndexRouteImport } from './routes/multi-cookie-redirect/index'
3132
import { Route as UsersUserIdRouteImport } from './routes/users.$userId'
3233
import { Route as SearchParamsLoaderThrowsRedirectRouteImport } from './routes/search-params/loader-throws-redirect'
3334
import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default'
@@ -36,6 +37,7 @@ import { Route as PostsPostIdRouteImport } from './routes/posts.$postId'
3637
import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader'
3738
import { Route as NotFoundViaHeadRouteImport } from './routes/not-found/via-head'
3839
import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad'
40+
import { Route as MultiCookieRedirectTargetRouteImport } from './routes/multi-cookie-redirect/target'
3941
import { Route as ApiUsersRouteImport } from './routes/api.users'
4042
import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2'
4143
import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index'
@@ -139,6 +141,12 @@ const NotFoundIndexRoute = NotFoundIndexRouteImport.update({
139141
path: '/',
140142
getParentRoute: () => NotFoundRouteRoute,
141143
} as any)
144+
const MultiCookieRedirectIndexRoute =
145+
MultiCookieRedirectIndexRouteImport.update({
146+
id: '/multi-cookie-redirect/',
147+
path: '/multi-cookie-redirect/',
148+
getParentRoute: () => rootRouteImport,
149+
} as any)
142150
const UsersUserIdRoute = UsersUserIdRouteImport.update({
143151
id: '/$userId',
144152
path: '/$userId',
@@ -180,6 +188,12 @@ const NotFoundViaBeforeLoadRoute = NotFoundViaBeforeLoadRouteImport.update({
180188
path: '/via-beforeLoad',
181189
getParentRoute: () => NotFoundRouteRoute,
182190
} as any)
191+
const MultiCookieRedirectTargetRoute =
192+
MultiCookieRedirectTargetRouteImport.update({
193+
id: '/multi-cookie-redirect/target',
194+
path: '/multi-cookie-redirect/target',
195+
getParentRoute: () => rootRouteImport,
196+
} as any)
183197
const ApiUsersRoute = ApiUsersRouteImport.update({
184198
id: '/api/users',
185199
path: '/api/users',
@@ -277,6 +291,7 @@ export interface FileRoutesByFullPath {
277291
'/users': typeof UsersRouteWithChildren
278292
'/대한민국': typeof Char45824Char54620Char48124Char44397Route
279293
'/api/users': typeof ApiUsersRouteWithChildren
294+
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
280295
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
281296
'/not-found/via-head': typeof NotFoundViaHeadRoute
282297
'/not-found/via-loader': typeof NotFoundViaLoaderRoute
@@ -285,6 +300,7 @@ export interface FileRoutesByFullPath {
285300
'/search-params/default': typeof SearchParamsDefaultRoute
286301
'/search-params/loader-throws-redirect': typeof SearchParamsLoaderThrowsRedirectRoute
287302
'/users/$userId': typeof UsersUserIdRoute
303+
'/multi-cookie-redirect': typeof MultiCookieRedirectIndexRoute
288304
'/not-found/': typeof NotFoundIndexRoute
289305
'/posts/': typeof PostsIndexRoute
290306
'/redirect': typeof RedirectIndexRoute
@@ -313,13 +329,15 @@ export interface FileRoutesByTo {
313329
'/stream': typeof StreamRoute
314330
'/대한민국': typeof Char45824Char54620Char48124Char44397Route
315331
'/api/users': typeof ApiUsersRouteWithChildren
332+
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
316333
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
317334
'/not-found/via-head': typeof NotFoundViaHeadRoute
318335
'/not-found/via-loader': typeof NotFoundViaLoaderRoute
319336
'/posts/$postId': typeof PostsPostIdRoute
320337
'/search-params/default': typeof SearchParamsDefaultRoute
321338
'/search-params/loader-throws-redirect': typeof SearchParamsLoaderThrowsRedirectRoute
322339
'/users/$userId': typeof UsersUserIdRoute
340+
'/multi-cookie-redirect': typeof MultiCookieRedirectIndexRoute
323341
'/not-found': typeof NotFoundIndexRoute
324342
'/posts': typeof PostsIndexRoute
325343
'/redirect': typeof RedirectIndexRoute
@@ -354,6 +372,7 @@ export interface FileRoutesById {
354372
'/대한민국': typeof Char45824Char54620Char48124Char44397Route
355373
'/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren
356374
'/api/users': typeof ApiUsersRouteWithChildren
375+
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
357376
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
358377
'/not-found/via-head': typeof NotFoundViaHeadRoute
359378
'/not-found/via-loader': typeof NotFoundViaLoaderRoute
@@ -362,6 +381,7 @@ export interface FileRoutesById {
362381
'/search-params/default': typeof SearchParamsDefaultRoute
363382
'/search-params/loader-throws-redirect': typeof SearchParamsLoaderThrowsRedirectRoute
364383
'/users/$userId': typeof UsersUserIdRoute
384+
'/multi-cookie-redirect/': typeof MultiCookieRedirectIndexRoute
365385
'/not-found/': typeof NotFoundIndexRoute
366386
'/posts/': typeof PostsIndexRoute
367387
'/redirect/': typeof RedirectIndexRoute
@@ -397,6 +417,7 @@ export interface FileRouteTypes {
397417
| '/users'
398418
| '/대한민국'
399419
| '/api/users'
420+
| '/multi-cookie-redirect/target'
400421
| '/not-found/via-beforeLoad'
401422
| '/not-found/via-head'
402423
| '/not-found/via-loader'
@@ -405,6 +426,7 @@ export interface FileRouteTypes {
405426
| '/search-params/default'
406427
| '/search-params/loader-throws-redirect'
407428
| '/users/$userId'
429+
| '/multi-cookie-redirect'
408430
| '/not-found/'
409431
| '/posts/'
410432
| '/redirect'
@@ -433,13 +455,15 @@ export interface FileRouteTypes {
433455
| '/stream'
434456
| '/대한민국'
435457
| '/api/users'
458+
| '/multi-cookie-redirect/target'
436459
| '/not-found/via-beforeLoad'
437460
| '/not-found/via-head'
438461
| '/not-found/via-loader'
439462
| '/posts/$postId'
440463
| '/search-params/default'
441464
| '/search-params/loader-throws-redirect'
442465
| '/users/$userId'
466+
| '/multi-cookie-redirect'
443467
| '/not-found'
444468
| '/posts'
445469
| '/redirect'
@@ -473,6 +497,7 @@ export interface FileRouteTypes {
473497
| '/대한민국'
474498
| '/_layout/_layout-2'
475499
| '/api/users'
500+
| '/multi-cookie-redirect/target'
476501
| '/not-found/via-beforeLoad'
477502
| '/not-found/via-head'
478503
| '/not-found/via-loader'
@@ -481,6 +506,7 @@ export interface FileRouteTypes {
481506
| '/search-params/default'
482507
| '/search-params/loader-throws-redirect'
483508
| '/users/$userId'
509+
| '/multi-cookie-redirect/'
484510
| '/not-found/'
485511
| '/posts/'
486512
| '/redirect/'
@@ -516,7 +542,9 @@ export interface RootRouteChildren {
516542
UsersRoute: typeof UsersRouteWithChildren
517543
Char45824Char54620Char48124Char44397Route: typeof Char45824Char54620Char48124Char44397Route
518544
ApiUsersRoute: typeof ApiUsersRouteWithChildren
545+
MultiCookieRedirectTargetRoute: typeof MultiCookieRedirectTargetRoute
519546
RedirectTargetRoute: typeof RedirectTargetRouteWithChildren
547+
MultiCookieRedirectIndexRoute: typeof MultiCookieRedirectIndexRoute
520548
RedirectIndexRoute: typeof RedirectIndexRoute
521549
PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute
522550
FooBarQuxRoute: typeof FooBarQuxRouteWithChildren
@@ -643,6 +671,13 @@ declare module '@tanstack/react-router' {
643671
preLoaderRoute: typeof NotFoundIndexRouteImport
644672
parentRoute: typeof NotFoundRouteRoute
645673
}
674+
'/multi-cookie-redirect/': {
675+
id: '/multi-cookie-redirect/'
676+
path: '/multi-cookie-redirect'
677+
fullPath: '/multi-cookie-redirect'
678+
preLoaderRoute: typeof MultiCookieRedirectIndexRouteImport
679+
parentRoute: typeof rootRouteImport
680+
}
646681
'/users/$userId': {
647682
id: '/users/$userId'
648683
path: '/$userId'
@@ -699,6 +734,13 @@ declare module '@tanstack/react-router' {
699734
preLoaderRoute: typeof NotFoundViaBeforeLoadRouteImport
700735
parentRoute: typeof NotFoundRouteRoute
701736
}
737+
'/multi-cookie-redirect/target': {
738+
id: '/multi-cookie-redirect/target'
739+
path: '/multi-cookie-redirect/target'
740+
fullPath: '/multi-cookie-redirect/target'
741+
preLoaderRoute: typeof MultiCookieRedirectTargetRouteImport
742+
parentRoute: typeof rootRouteImport
743+
}
702744
'/api/users': {
703745
id: '/api/users'
704746
path: '/api/users'
@@ -973,7 +1015,9 @@ const rootRouteChildren: RootRouteChildren = {
9731015
Char45824Char54620Char48124Char44397Route:
9741016
Char45824Char54620Char48124Char44397Route,
9751017
ApiUsersRoute: ApiUsersRouteWithChildren,
1018+
MultiCookieRedirectTargetRoute: MultiCookieRedirectTargetRoute,
9761019
RedirectTargetRoute: RedirectTargetRouteWithChildren,
1020+
MultiCookieRedirectIndexRoute: MultiCookieRedirectIndexRoute,
9771021
RedirectIndexRoute: RedirectIndexRoute,
9781022
PostsPostIdDeepRoute: PostsPostIdDeepRoute,
9791023
FooBarQuxRoute: FooBarQuxRouteWithChildren,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { createFileRoute, redirect } from '@tanstack/react-router'
2+
import { createServerFn } from '@tanstack/react-start'
3+
import { setCookie } from '@tanstack/react-start/server'
4+
5+
const setMultipleCookiesAndRedirect = createServerFn().handler(() => {
6+
// Set multiple cookies before redirecting
7+
// This tests that multiple Set-Cookie headers are preserved during redirect
8+
setCookie('session', 'session-value', { path: '/' })
9+
setCookie('csrf', 'csrf-token-value', { path: '/' })
10+
setCookie('theme', 'dark', { path: '/' })
11+
12+
throw redirect({ to: '/multi-cookie-redirect/target' })
13+
})
14+
15+
export const Route = createFileRoute('/multi-cookie-redirect/')({
16+
loader: () => setMultipleCookiesAndRedirect(),
17+
component: () => null,
18+
})
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { createFileRoute } from '@tanstack/react-router'
2+
import Cookies from 'js-cookie'
3+
import React, { useEffect } from 'react'
4+
5+
export const Route = createFileRoute('/multi-cookie-redirect/target')({
6+
component: RouteComponent,
7+
})
8+
9+
function RouteComponent() {
10+
const [cookies, setCookies] = React.useState<Record<string, string>>({})
11+
12+
useEffect(() => {
13+
setCookies({
14+
session: Cookies.get('session') || '',
15+
csrf: Cookies.get('csrf') || '',
16+
theme: Cookies.get('theme') || '',
17+
})
18+
}, [])
19+
20+
return (
21+
<div>
22+
<h1 data-testid="multi-cookie-redirect-target">
23+
Multi Cookie Redirect Target
24+
</h1>
25+
<div>
26+
<p>
27+
Session cookie:{' '}
28+
<span data-testid="cookie-session">{cookies.session}</span>
29+
</p>
30+
<p>
31+
CSRF cookie: <span data-testid="cookie-csrf">{cookies.csrf}</span>
32+
</p>
33+
<p>
34+
Theme cookie: <span data-testid="cookie-theme">{cookies.theme}</span>
35+
</p>
36+
</div>
37+
</div>
38+
)
39+
}

e2e/react-start/basic/tests/redirect.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,23 @@ test.describe('redirects', () => {
228228
})
229229
})
230230
})
231+
232+
test('multiple Set-Cookie headers are preserved on redirect', async ({
233+
page,
234+
}) => {
235+
// This test verifies that multiple Set-Cookie headers are not lost during redirect
236+
await page.goto('/multi-cookie-redirect')
237+
238+
// Wait for redirect to complete
239+
await page.waitForURL(/\/multi-cookie-redirect\/target/)
240+
241+
// Should redirect to target page
242+
await expect(page.getByTestId('multi-cookie-redirect-target')).toBeVisible()
243+
expect(page.url()).toContain('/multi-cookie-redirect/target')
244+
245+
// Verify all three cookies were preserved during the redirect
246+
await expect(page.getByTestId('cookie-session')).toHaveText('session-value')
247+
await expect(page.getByTestId('cookie-csrf')).toHaveText('csrf-token-value')
248+
await expect(page.getByTestId('cookie-theme')).toHaveText('dark')
249+
})
231250
})

e2e/react-start/selective-ssr/src/routes/__root.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,6 @@ export const Route = createRootRoute({
4242
}
4343
},
4444
beforeLoad: ({ search }) => {
45-
if (typeof window !== 'undefined') {
46-
if (Route.options.ssr !== undefined) {
47-
const error = `ssr() for ${Route.id} should have been deleted from the Route options on the client`
48-
console.error(error)
49-
throw new Error(error)
50-
}
51-
}
5245
console.log(
5346
`beforeLoad for ${Route.id} called on the ${typeof window !== 'undefined' ? 'client' : 'server'}`,
5447
)

e2e/react-start/selective-ssr/src/routes/posts.$postId.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@ export const Route = createFileRoute('/posts/$postId')({
1515
}
1616
},
1717
beforeLoad: ({ search }) => {
18-
if (typeof window !== 'undefined') {
19-
if (Route.options.ssr !== undefined) {
20-
const error = `ssr() for ${Route.id} should have been deleted from the Route options on the client`
21-
console.error(error)
22-
throw new Error(error)
23-
}
24-
}
2518
console.log(
2619
`beforeLoad for ${Route.id} called on the ${typeof window !== 'undefined' ? 'client' : 'server'}`,
2720
)

e2e/react-start/selective-ssr/src/routes/posts.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@ export const Route = createFileRoute('/posts')({
1515
}
1616
},
1717
beforeLoad: ({ search }) => {
18-
if (typeof window !== 'undefined') {
19-
if (Route.options.ssr !== undefined) {
20-
const error = `ssr() for ${Route.id} should have been deleted from the Route options on the client`
21-
console.error(error)
22-
throw new Error(error)
23-
}
24-
}
2518
console.log(
2619
`beforeLoad for ${Route.id} called on the ${typeof window !== 'undefined' ? 'client' : 'server'}`,
2720
)

e2e/solid-start/basic/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@tanstack/solid-start": "workspace:^",
2727
"express": "^5.1.0",
2828
"http-proxy-middleware": "^3.0.5",
29+
"js-cookie": "^3.0.5",
2930
"redaxios": "^0.5.1",
3031
"solid-js": "^1.9.10",
3132
"tailwind-merge": "^2.6.0",
@@ -36,6 +37,7 @@
3637
"@playwright/test": "^1.50.1",
3738
"@tailwindcss/postcss": "^4.1.15",
3839
"@tanstack/router-e2e-utils": "workspace:^",
40+
"@types/js-cookie": "^3.0.6",
3941
"@types/node": "^22.10.2",
4042
"combinate": "^1.1.11",
4143
"postcss": "^8.5.1",

0 commit comments

Comments
 (0)