Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions e2e/react-start/basic/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Route as TypeOnlyReexportRouteImport } from './routes/type-only-reexpor
import { Route as UsersRouteImport } from './routes/users'
import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2'
import { Route as ApiUsersRouteImport } from './routes/api.users'
import { Route as Issue6221DashboardRouteImport } from './routes/issue-6221.dashboard'
import { Route as MultiCookieRedirectIndexRouteImport } from './routes/multi-cookie-redirect/index'
import { Route as MultiCookieRedirectTargetRouteImport } from './routes/multi-cookie-redirect/target'
import { Route as NotFoundIndexRouteImport } from './routes/not-found/index'
Expand Down Expand Up @@ -61,6 +62,7 @@ import { Route as UsersUserIdRouteImport } from './routes/users.$userId'
import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a'
import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b'
import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id'
import { Route as Issue6221ArticleIdRouteImport } from './routes/issue-6221.article.$id'
import { Route as NotFoundDeepIndexRouteImport } from './routes/not-found/deep/index'
import { Route as NotFoundDeepBRouteRouteImport } from './routes/not-found/deep/b/route'
import { Route as NotFoundParentBoundaryIndexRouteImport } from './routes/not-found/parent-boundary/index'
Expand Down Expand Up @@ -179,6 +181,11 @@ const ApiUsersRoute = ApiUsersRouteImport.update({
path: '/api/users',
getParentRoute: () => rootRouteImport,
} as any)
const Issue6221DashboardRoute = Issue6221DashboardRouteImport.update({
id: '/issue-6221/dashboard',
path: '/issue-6221/dashboard',
getParentRoute: () => rootRouteImport,
} as any)
const MultiCookieRedirectIndexRoute =
MultiCookieRedirectIndexRouteImport.update({
id: '/multi-cookie-redirect/',
Expand Down Expand Up @@ -346,6 +353,11 @@ const ApiUsersIdRoute = ApiUsersIdRouteImport.update({
path: '/$id',
getParentRoute: () => ApiUsersRoute,
} as any)
const Issue6221ArticleIdRoute = Issue6221ArticleIdRouteImport.update({
id: '/issue-6221/article/$id',
path: '/issue-6221/article/$id',
getParentRoute: () => rootRouteImport,
} as any)
const NotFoundDeepIndexRoute = NotFoundDeepIndexRouteImport.update({
id: '/',
path: '/',
Expand Down Expand Up @@ -468,6 +480,7 @@ export interface FileRoutesByFullPath {
'/not-found/parent-boundary': typeof NotFoundParentBoundaryRouteRouteWithChildren
'/specialChars/malformed': typeof SpecialCharsMalformedRouteRouteWithChildren
'/api/users': typeof ApiUsersRouteWithChildren
'/issue-6221/dashboard': typeof Issue6221DashboardRoute
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
'/not-found/via-beforeLoad-target-root': typeof NotFoundViaBeforeLoadTargetRootRoute
Expand Down Expand Up @@ -498,6 +511,7 @@ export interface FileRoutesByFullPath {
'/layout-a': typeof LayoutLayout2LayoutARoute
'/layout-b': typeof LayoutLayout2LayoutBRoute
'/api/users/$id': typeof ApiUsersIdRoute
'/issue-6221/article/$id': typeof Issue6221ArticleIdRoute
'/not-found/parent-boundary/via-beforeLoad': typeof NotFoundParentBoundaryViaBeforeLoadRoute
'/posts/$postId/deep': typeof PostsPostIdDeepRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
Expand Down Expand Up @@ -531,6 +545,7 @@ export interface FileRoutesByTo {
'/type-only-reexport': typeof TypeOnlyReexportRoute
'/specialChars/malformed': typeof SpecialCharsMalformedRouteRouteWithChildren
'/api/users': typeof ApiUsersRouteWithChildren
'/issue-6221/dashboard': typeof Issue6221DashboardRoute
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
'/not-found/via-beforeLoad-target-root': typeof NotFoundViaBeforeLoadTargetRootRoute
Expand Down Expand Up @@ -560,6 +575,7 @@ export interface FileRoutesByTo {
'/layout-a': typeof LayoutLayout2LayoutARoute
'/layout-b': typeof LayoutLayout2LayoutBRoute
'/api/users/$id': typeof ApiUsersIdRoute
'/issue-6221/article/$id': typeof Issue6221ArticleIdRoute
'/not-found/parent-boundary/via-beforeLoad': typeof NotFoundParentBoundaryViaBeforeLoadRoute
'/posts/$postId/deep': typeof PostsPostIdDeepRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
Expand Down Expand Up @@ -602,6 +618,7 @@ export interface FileRoutesById {
'/specialChars/malformed': typeof SpecialCharsMalformedRouteRouteWithChildren
'/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren
'/api/users': typeof ApiUsersRouteWithChildren
'/issue-6221/dashboard': typeof Issue6221DashboardRoute
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
'/not-found/via-beforeLoad-target-root': typeof NotFoundViaBeforeLoadTargetRootRoute
Expand Down Expand Up @@ -632,6 +649,7 @@ export interface FileRoutesById {
'/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute
'/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute
'/api/users/$id': typeof ApiUsersIdRoute
'/issue-6221/article/$id': typeof Issue6221ArticleIdRoute
'/not-found/parent-boundary/via-beforeLoad': typeof NotFoundParentBoundaryViaBeforeLoadRoute
'/posts_/$postId/deep': typeof PostsPostIdDeepRoute
'/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute
Expand Down Expand Up @@ -674,6 +692,7 @@ export interface FileRouteTypes {
| '/not-found/parent-boundary'
| '/specialChars/malformed'
| '/api/users'
| '/issue-6221/dashboard'
| '/multi-cookie-redirect/target'
| '/not-found/via-beforeLoad'
| '/not-found/via-beforeLoad-target-root'
Expand Down Expand Up @@ -704,6 +723,7 @@ export interface FileRouteTypes {
| '/layout-a'
| '/layout-b'
| '/api/users/$id'
| '/issue-6221/article/$id'
| '/not-found/parent-boundary/via-beforeLoad'
| '/posts/$postId/deep'
| '/redirect/$target/via-beforeLoad'
Expand Down Expand Up @@ -737,6 +757,7 @@ export interface FileRouteTypes {
| '/type-only-reexport'
| '/specialChars/malformed'
| '/api/users'
| '/issue-6221/dashboard'
| '/multi-cookie-redirect/target'
| '/not-found/via-beforeLoad'
| '/not-found/via-beforeLoad-target-root'
Expand Down Expand Up @@ -766,6 +787,7 @@ export interface FileRouteTypes {
| '/layout-a'
| '/layout-b'
| '/api/users/$id'
| '/issue-6221/article/$id'
| '/not-found/parent-boundary/via-beforeLoad'
| '/posts/$postId/deep'
| '/redirect/$target/via-beforeLoad'
Expand Down Expand Up @@ -807,6 +829,7 @@ export interface FileRouteTypes {
| '/specialChars/malformed'
| '/_layout/_layout-2'
| '/api/users'
| '/issue-6221/dashboard'
| '/multi-cookie-redirect/target'
| '/not-found/via-beforeLoad'
| '/not-found/via-beforeLoad-target-root'
Expand Down Expand Up @@ -837,6 +860,7 @@ export interface FileRouteTypes {
| '/_layout/_layout-2/layout-a'
| '/_layout/_layout-2/layout-b'
| '/api/users/$id'
| '/issue-6221/article/$id'
| '/not-found/parent-boundary/via-beforeLoad'
| '/posts_/$postId/deep'
| '/redirect/$target/via-beforeLoad'
Expand Down Expand Up @@ -876,10 +900,12 @@ export interface RootRouteChildren {
TypeOnlyReexportRoute: typeof TypeOnlyReexportRoute
UsersRoute: typeof UsersRouteWithChildren
ApiUsersRoute: typeof ApiUsersRouteWithChildren
Issue6221DashboardRoute: typeof Issue6221DashboardRoute
MultiCookieRedirectTargetRoute: typeof MultiCookieRedirectTargetRoute
RedirectTargetRoute: typeof RedirectTargetRouteWithChildren
MultiCookieRedirectIndexRoute: typeof MultiCookieRedirectIndexRoute
RedirectIndexRoute: typeof RedirectIndexRoute
Issue6221ArticleIdRoute: typeof Issue6221ArticleIdRoute
PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute
FooBarQuxHereRoute: typeof FooBarQuxHereRouteWithChildren
}
Expand Down Expand Up @@ -1026,6 +1052,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiUsersRouteImport
parentRoute: typeof rootRouteImport
}
'/issue-6221/dashboard': {
id: '/issue-6221/dashboard'
path: '/issue-6221/dashboard'
fullPath: '/issue-6221/dashboard'
preLoaderRoute: typeof Issue6221DashboardRouteImport
parentRoute: typeof rootRouteImport
}
'/multi-cookie-redirect/': {
id: '/multi-cookie-redirect/'
path: '/multi-cookie-redirect'
Expand Down Expand Up @@ -1250,6 +1283,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiUsersIdRouteImport
parentRoute: typeof ApiUsersRoute
}
'/issue-6221/article/$id': {
id: '/issue-6221/article/$id'
path: '/issue-6221/article/$id'
fullPath: '/issue-6221/article/$id'
preLoaderRoute: typeof Issue6221ArticleIdRouteImport
parentRoute: typeof rootRouteImport
}
'/not-found/deep/': {
id: '/not-found/deep/'
path: '/'
Expand Down Expand Up @@ -1648,10 +1688,12 @@ const rootRouteChildren: RootRouteChildren = {
TypeOnlyReexportRoute: TypeOnlyReexportRoute,
UsersRoute: UsersRouteWithChildren,
ApiUsersRoute: ApiUsersRouteWithChildren,
Issue6221DashboardRoute: Issue6221DashboardRoute,
MultiCookieRedirectTargetRoute: MultiCookieRedirectTargetRoute,
RedirectTargetRoute: RedirectTargetRouteWithChildren,
MultiCookieRedirectIndexRoute: MultiCookieRedirectIndexRoute,
RedirectIndexRoute: RedirectIndexRoute,
Issue6221ArticleIdRoute: Issue6221ArticleIdRoute,
PostsPostIdDeepRoute: PostsPostIdDeepRoute,
FooBarQuxHereRoute: FooBarQuxHereRouteWithChildren,
}
Expand Down
42 changes: 42 additions & 0 deletions e2e/react-start/basic/src/routes/issue-6221.article.$id.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Link, createFileRoute } from '@tanstack/react-router'
import { createClientOnlyFn } from '@tanstack/react-start'

const isAuthed = createClientOnlyFn(
() => localStorage.getItem('issue-6221-auth') === 'good',
)

const fetchArticle = async (id: string) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return isAuthed()
? {
title: `Article Title for ${id}`,
content: `Article content for ${id}`,
}
: null
}

export const Route = createFileRoute('/issue-6221/article/$id')({
ssr: false,
loader: ({ params }) => fetchArticle(params.id),
head: ({ loaderData }) => ({
meta: [{ title: loaderData?.title ?? 'title n/a' }],
}),
component: Article,
})

function Article() {
const article = Route.useLoaderData()

return (
<div>
<Link to="/issue-6221/dashboard" data-testid="issue-6221-dashboard-link">
Dashboard
</Link>
{article ? (
<div data-testid="issue-6221-article">{article.content}</div>
) : (
<div>Article Not Accessible.</div>
)}
</div>
)
}
6 changes: 6 additions & 0 deletions e2e/react-start/basic/src/routes/issue-6221.dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/issue-6221/dashboard')({
head: () => ({ meta: [{ title: 'Dashboard' }] }),
component: () => <div data-testid="issue-6221-dashboard">Dashboard</div>,
})
41 changes: 41 additions & 0 deletions e2e/react-start/basic/tests/issue-6221-head.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { expect } from '@playwright/test'
import { test } from '@tanstack/router-e2e-utils'
import { isPreview } from './utils/isPreview'
import { isSpaMode } from './utils/isSpaMode'

// Ported to React from the regression app in
// https://github.com/TanStack/router/pull/6222
test.skip(
isSpaMode || isPreview,
'head() coverage for an ssr:false route requires the SSR test mode',
)

test('browser back uses fresh loaderData for both the body and head (#6221)', async ({
page,
}) => {
await page.goto('/')
await page.evaluate(() => localStorage.setItem('issue-6221-auth', 'good'))

await page.goto('/issue-6221/article/1')

await expect(page.getByTestId('issue-6221-article')).toHaveText(
'Article content for 1',
)
await expect(page).toHaveTitle('Article Title for 1')

await page.evaluate(() => localStorage.removeItem('issue-6221-auth'))
await page.reload()
await expect(page.getByText('Article Not Accessible.')).toBeVisible()
await expect(page).toHaveTitle('title n/a')

await page.evaluate(() => localStorage.setItem('issue-6221-auth', 'good'))
await page.getByTestId('issue-6221-dashboard-link').click()
await expect(page.getByTestId('issue-6221-dashboard')).toBeVisible()
await expect(page).toHaveTitle('Dashboard')

await page.goBack()
await expect(page.getByTestId('issue-6221-article')).toHaveText(
'Article content for 1',
)
await expect(page).toHaveTitle('Article Title for 1')
})
37 changes: 15 additions & 22 deletions e2e/react-start/selective-ssr/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ export const Route = createRootRoute({
}),
validateSearch: z.object({
root: ssrSchema,
issue4614: z.string().optional(),
}),
loaderDeps: ({ search }) => ({ issue4614: search.issue4614 }),
ssr: ({ search }) => {
if (typeof window !== 'undefined') {
const error = `ssr() for ${Route.id} should not be called on the client`
Expand All @@ -57,21 +55,23 @@ export const Route = createRootRoute({
console.error(error)
throw new Error(error)
}
const root = typeof window === 'undefined' ? 'server' : 'client'
const issue4614Context = `${root}:${search.issue4614 ?? 'cached'}`
const isClient = typeof window !== 'undefined'
const isServer = !isClient
const root = isServer ? 'server' : 'client'
if (typeof window !== 'undefined' && location.pathname === '/issue-4614') {
const calls = ((globalThis as any).__issue4614RootBeforeLoads ??= [])
calls.push({
cause,
preload,
root,
issue4614Context,
isClient,
isServer,
})
Comment on lines 62 to 68

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Define one typed global contract for Issue 4614 instrumentation.

The new globalThis as any casts erase the payload shape across the route and its regression test. Add a shared TypeScript declaration for these globals and reuse it everywhere.

  • e2e/react-start/selective-ssr/src/routes/__root.tsx#L62-L68: type __issue4614RootBeforeLoads.
  • e2e/react-start/selective-ssr/src/routes/issue-4614.tsx#L6-L10: type __issue4614TargetBeforeLoad.
  • e2e/react-start/selective-ssr/tests/app.spec.ts#L19-L19: read the typed root payload instead of casting to any.
📍 Affects 3 files
  • e2e/react-start/selective-ssr/src/routes/__root.tsx#L62-L68 (this comment)
  • e2e/react-start/selective-ssr/src/routes/issue-4614.tsx#L6-L10
  • e2e/react-start/selective-ssr/tests/app.spec.ts#L19-L19
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/selective-ssr/src/routes/__root.tsx` around lines 62 - 68,
Define a shared TypeScript global declaration for the Issue 4614 instrumentation
payloads, then remove the globalThis as any casts. In
e2e/react-start/selective-ssr/src/routes/__root.tsx lines 62-68, type
__issue4614RootBeforeLoads; in
e2e/react-start/selective-ssr/src/routes/issue-4614.tsx lines 6-10, type
__issue4614TargetBeforeLoad; and in
e2e/react-start/selective-ssr/tests/app.spec.ts line 19, read the typed root
payload directly. Ensure all sites reuse the same shared contract.

Source: Coding guidelines

}
return {
root,
search,
issue4614Context,
isClient,
isServer,
}
},
loader: ({ context }) => {
Expand Down Expand Up @@ -120,6 +120,12 @@ export const Route = createRootRoute({
context:{' '}
<b data-testid="root-context">{Route.useRouteContext().root}</b>
</div>
<div>
issue #4614 context:{' '}
<b data-testid="issue-4614-root-context">
{`${Route.useRouteContext().isClient}:${Route.useRouteContext().isServer}`}
</b>
</div>
<hr />
<Outlet />
</div>
Expand Down Expand Up @@ -149,21 +155,8 @@ function RootDocument({ children }: { children: React.ReactNode }) {
>
Home
</Link>
<Link
to="/issue-4614"
search={{}}
preload="intent"
data-testid="issue-4614-cached-link"
>
Issue 4614 cached parent
</Link>
<Link
to="/issue-4614"
search={{ issue4614: 'reload' }}
preload="intent"
data-testid="issue-4614-reload-link"
>
Issue 4614 reloaded parent
<Link to="/issue-4614" preload="intent" data-testid="issue-4614-link">
Issue 4614
</Link>
</div>
<hr />
Expand Down
7 changes: 3 additions & 4 deletions e2e/react-start/selective-ssr/src/routes/issue-4614.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/issue-4614')({
ssr: false,
beforeLoad: ({ context, cause, preload, search }) => {
beforeLoad: ({ context, cause, preload }) => {
;(globalThis as any).__issue4614TargetBeforeLoad = {
cause,
preload,
rootContext: context.root,
issue4614Context: context.issue4614Context,
scenario: search.issue4614 ?? 'cached',
isClient: context.isClient,
isServer: context.isServer,
}
},
component: () => <div data-testid="issue-4614-page">Issue 4614</div>,
Expand Down
Loading
Loading