From 2e96440045965b762ce2b5449765e91479db8eff Mon Sep 17 00:00:00 2001 From: Remi Oduyemi Date: Wed, 29 Oct 2025 10:07:07 +0000 Subject: [PATCH 01/18] fix: catch errors thrown in hydrate --- e2e/react-start/basic/src/routeTree.gen.ts | 21 ++ .../basic/src/routes/not-found/index.tsx | 10 + .../basic/src/routes/not-found/via-head.tsx | 23 ++ e2e/react-start/basic/tests/not-found.spec.ts | 5 +- e2e/react-start/basic/vite.config.ts | 1 + e2e/solid-start/basic/src/routeTree.gen.ts | 21 ++ .../basic/src/routes/not-found/index.tsx | 10 + .../basic/src/routes/not-found/via-head.tsx | 23 ++ e2e/solid-start/basic/tests/not-found.spec.ts | 5 +- packages/router-core/src/ssr/ssr-client.ts | 6 +- packages/router-core/tests/hydrate.test.ts | 255 ++++++++++++++++++ 11 files changed, 374 insertions(+), 6 deletions(-) create mode 100644 e2e/react-start/basic/src/routes/not-found/via-head.tsx create mode 100644 e2e/solid-start/basic/src/routes/not-found/via-head.tsx create mode 100644 packages/router-core/tests/hydrate.test.ts diff --git a/e2e/react-start/basic/src/routeTree.gen.ts b/e2e/react-start/basic/src/routeTree.gen.ts index e29bb26d3ce..1f798d17fa5 100644 --- a/e2e/react-start/basic/src/routeTree.gen.ts +++ b/e2e/react-start/basic/src/routeTree.gen.ts @@ -34,6 +34,7 @@ import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/ import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader' +import { Route as NotFoundViaHeadRouteImport } from './routes/not-found/via-head' import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad' import { Route as ApiUsersRouteImport } from './routes/api.users' import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' @@ -169,6 +170,11 @@ const NotFoundViaLoaderRoute = NotFoundViaLoaderRouteImport.update({ path: '/via-loader', getParentRoute: () => NotFoundRouteRoute, } as any) +const NotFoundViaHeadRoute = NotFoundViaHeadRouteImport.update({ + id: '/via-head', + path: '/via-head', + getParentRoute: () => NotFoundRouteRoute, +} as any) const NotFoundViaBeforeLoadRoute = NotFoundViaBeforeLoadRouteImport.update({ id: '/via-beforeLoad', path: '/via-beforeLoad', @@ -272,6 +278,7 @@ export interface FileRoutesByFullPath { '/대한민국': typeof Char45824Char54620Char48124Char44397Route '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute + '/not-found/via-head': typeof NotFoundViaHeadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute '/redirect/$target': typeof RedirectTargetRouteWithChildren @@ -307,6 +314,7 @@ export interface FileRoutesByTo { '/대한민국': typeof Char45824Char54620Char48124Char44397Route '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute + '/not-found/via-head': typeof NotFoundViaHeadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute '/search-params/default': typeof SearchParamsDefaultRoute @@ -347,6 +355,7 @@ export interface FileRoutesById { '/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute + '/not-found/via-head': typeof NotFoundViaHeadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute '/redirect/$target': typeof RedirectTargetRouteWithChildren @@ -389,6 +398,7 @@ export interface FileRouteTypes { | '/대한민국' | '/api/users' | '/not-found/via-beforeLoad' + | '/not-found/via-head' | '/not-found/via-loader' | '/posts/$postId' | '/redirect/$target' @@ -424,6 +434,7 @@ export interface FileRouteTypes { | '/대한민국' | '/api/users' | '/not-found/via-beforeLoad' + | '/not-found/via-head' | '/not-found/via-loader' | '/posts/$postId' | '/search-params/default' @@ -463,6 +474,7 @@ export interface FileRouteTypes { | '/_layout/_layout-2' | '/api/users' | '/not-found/via-beforeLoad' + | '/not-found/via-head' | '/not-found/via-loader' | '/posts/$postId' | '/redirect/$target' @@ -673,6 +685,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NotFoundViaLoaderRouteImport parentRoute: typeof NotFoundRouteRoute } + '/not-found/via-head': { + id: '/not-found/via-head' + path: '/via-head' + fullPath: '/not-found/via-head' + preLoaderRoute: typeof NotFoundViaHeadRouteImport + parentRoute: typeof NotFoundRouteRoute + } '/not-found/via-beforeLoad': { id: '/not-found/via-beforeLoad' path: '/via-beforeLoad' @@ -797,12 +816,14 @@ declare module '@tanstack/react-router' { interface NotFoundRouteRouteChildren { NotFoundViaBeforeLoadRoute: typeof NotFoundViaBeforeLoadRoute + NotFoundViaHeadRoute: typeof NotFoundViaHeadRoute NotFoundViaLoaderRoute: typeof NotFoundViaLoaderRoute NotFoundIndexRoute: typeof NotFoundIndexRoute } const NotFoundRouteRouteChildren: NotFoundRouteRouteChildren = { NotFoundViaBeforeLoadRoute: NotFoundViaBeforeLoadRoute, + NotFoundViaHeadRoute: NotFoundViaHeadRoute, NotFoundViaLoaderRoute: NotFoundViaLoaderRoute, NotFoundIndexRoute: NotFoundIndexRoute, } diff --git a/e2e/react-start/basic/src/routes/not-found/index.tsx b/e2e/react-start/basic/src/routes/not-found/index.tsx index e754f83c74b..722813fca36 100644 --- a/e2e/react-start/basic/src/routes/not-found/index.tsx +++ b/e2e/react-start/basic/src/routes/not-found/index.tsx @@ -25,6 +25,16 @@ export const Route = createFileRoute('/not-found/')({ via-loader +
+ + via-head + +
) }, diff --git a/e2e/react-start/basic/src/routes/not-found/via-head.tsx b/e2e/react-start/basic/src/routes/not-found/via-head.tsx new file mode 100644 index 00000000000..72107945a44 --- /dev/null +++ b/e2e/react-start/basic/src/routes/not-found/via-head.tsx @@ -0,0 +1,23 @@ +import { createFileRoute, notFound } from '@tanstack/react-router' + +export const Route = createFileRoute('/not-found/via-head')({ + head: () => { + throw notFound() + }, + component: RouteComponent, + notFoundComponent: () => { + return ( +
+ Not Found "/not-found/via-head"! +
+ ) + }, +}) + +function RouteComponent() { + return ( +
+ Hello "/not-found/via-head"! +
+ ) +} \ No newline at end of file diff --git a/e2e/react-start/basic/tests/not-found.spec.ts b/e2e/react-start/basic/tests/not-found.spec.ts index bde0a1a02a3..099e4077697 100644 --- a/e2e/react-start/basic/tests/not-found.spec.ts +++ b/e2e/react-start/basic/tests/not-found.spec.ts @@ -9,6 +9,7 @@ const combinate = (combinateImport as any).default as typeof combinateImport test.use({ whitelistErrors: [ /Failed to load resource: the server responded with a status of 404/, + 'Error during route context hydration: {isNotFound: true}', ], }) test.describe('not-found', () => { @@ -25,7 +26,7 @@ test.describe('not-found', () => { test.describe('throw notFound()', () => { const navigationTestMatrix = combinate({ // TODO beforeLoad! - thrower: [/* 'beforeLoad',*/ 'loader'] as const, + thrower: [/* 'beforeLoad',*/ 'head', 'loader'] as const, preload: [false, true] as const, }) @@ -57,7 +58,7 @@ test.describe('not-found', () => { const directVisitTestMatrix = combinate({ // TODO beforeLoad! - thrower: [/* 'beforeLoad',*/ 'loader'] as const, + thrower: [/* 'beforeLoad',*/ 'head', 'loader'] as const, }) directVisitTestMatrix.forEach(({ thrower }) => { diff --git a/e2e/react-start/basic/vite.config.ts b/e2e/react-start/basic/vite.config.ts index c34468b880f..cd5df8449fa 100644 --- a/e2e/react-start/basic/vite.config.ts +++ b/e2e/react-start/basic/vite.config.ts @@ -20,6 +20,7 @@ const prerenderConfiguration = { '/redirect', '/i-do-not-exist', '/not-found/via-beforeLoad', + '/not-found/via-head', '/not-found/via-loader', ].some((p) => page.path.includes(p)), maxRedirects: 100, diff --git a/e2e/solid-start/basic/src/routeTree.gen.ts b/e2e/solid-start/basic/src/routeTree.gen.ts index aac25901997..65a3894ba59 100644 --- a/e2e/solid-start/basic/src/routeTree.gen.ts +++ b/e2e/solid-start/basic/src/routeTree.gen.ts @@ -32,6 +32,7 @@ import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/ import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader' +import { Route as NotFoundViaHeadRouteImport } from './routes/not-found/via-head' import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad' import { Route as ApiUsersRouteImport } from './routes/api/users' import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' @@ -163,6 +164,11 @@ const NotFoundViaLoaderRoute = NotFoundViaLoaderRouteImport.update({ path: '/via-loader', getParentRoute: () => NotFoundRouteRoute, } as any) +const NotFoundViaHeadRoute = NotFoundViaHeadRouteImport.update({ + id: '/via-head', + path: '/via-head', + getParentRoute: () => NotFoundRouteRoute, +} as any) const NotFoundViaBeforeLoadRoute = NotFoundViaBeforeLoadRouteImport.update({ id: '/via-beforeLoad', path: '/via-beforeLoad', @@ -252,6 +258,7 @@ export interface FileRoutesByFullPath { '/대한민국': typeof Char45824Char54620Char48124Char44397Route '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute + '/not-found/via-head': typeof NotFoundViaHeadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute '/redirect/$target': typeof RedirectTargetRouteWithChildren @@ -285,6 +292,7 @@ export interface FileRoutesByTo { '/대한민국': typeof Char45824Char54620Char48124Char44397Route '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute + '/not-found/via-head': typeof NotFoundViaHeadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute '/search-params/default': typeof SearchParamsDefaultRoute @@ -324,6 +332,7 @@ export interface FileRoutesById { '/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute + '/not-found/via-head': typeof NotFoundViaHeadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute '/redirect/$target': typeof RedirectTargetRouteWithChildren @@ -363,6 +372,7 @@ export interface FileRouteTypes { | '/대한민국' | '/api/users' | '/not-found/via-beforeLoad' + | '/not-found/via-head' | '/not-found/via-loader' | '/posts/$postId' | '/redirect/$target' @@ -396,6 +406,7 @@ export interface FileRouteTypes { | '/대한민국' | '/api/users' | '/not-found/via-beforeLoad' + | '/not-found/via-head' | '/not-found/via-loader' | '/posts/$postId' | '/search-params/default' @@ -434,6 +445,7 @@ export interface FileRouteTypes { | '/_layout/_layout-2' | '/api/users' | '/not-found/via-beforeLoad' + | '/not-found/via-head' | '/not-found/via-loader' | '/posts/$postId' | '/redirect/$target' @@ -640,6 +652,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof NotFoundViaLoaderRouteImport parentRoute: typeof NotFoundRouteRoute } + '/not-found/via-head': { + id: '/not-found/via-head' + path: '/via-head' + fullPath: '/not-found/via-head' + preLoaderRoute: typeof NotFoundViaHeadRouteImport + parentRoute: typeof NotFoundRouteRoute + } '/not-found/via-beforeLoad': { id: '/not-found/via-beforeLoad' path: '/via-beforeLoad' @@ -743,12 +762,14 @@ declare module '@tanstack/solid-router' { interface NotFoundRouteRouteChildren { NotFoundViaBeforeLoadRoute: typeof NotFoundViaBeforeLoadRoute + NotFoundViaHeadRoute: typeof NotFoundViaHeadRoute NotFoundViaLoaderRoute: typeof NotFoundViaLoaderRoute NotFoundIndexRoute: typeof NotFoundIndexRoute } const NotFoundRouteRouteChildren: NotFoundRouteRouteChildren = { NotFoundViaBeforeLoadRoute: NotFoundViaBeforeLoadRoute, + NotFoundViaHeadRoute: NotFoundViaHeadRoute, NotFoundViaLoaderRoute: NotFoundViaLoaderRoute, NotFoundIndexRoute: NotFoundIndexRoute, } diff --git a/e2e/solid-start/basic/src/routes/not-found/index.tsx b/e2e/solid-start/basic/src/routes/not-found/index.tsx index 34c8ef61469..54eff41247c 100644 --- a/e2e/solid-start/basic/src/routes/not-found/index.tsx +++ b/e2e/solid-start/basic/src/routes/not-found/index.tsx @@ -25,6 +25,16 @@ export const Route = createFileRoute('/not-found/')({ via-loader +
+ + via-head + +
) }, diff --git a/e2e/solid-start/basic/src/routes/not-found/via-head.tsx b/e2e/solid-start/basic/src/routes/not-found/via-head.tsx new file mode 100644 index 00000000000..feefd921cab --- /dev/null +++ b/e2e/solid-start/basic/src/routes/not-found/via-head.tsx @@ -0,0 +1,23 @@ +import { createFileRoute, notFound } from '@tanstack/solid-router' + +export const Route = createFileRoute('/not-found/via-head')({ + head: () => { + throw notFound() + }, + component: RouteComponent, + notFoundComponent: () => { + return ( +
+ Not Found "/not-found/via-head"! +
+ ) + }, +}) + +function RouteComponent() { + return ( +
+ Hello "/not-found/via-head"! +
+ ) +} \ No newline at end of file diff --git a/e2e/solid-start/basic/tests/not-found.spec.ts b/e2e/solid-start/basic/tests/not-found.spec.ts index e1a69eefc65..82b48f1b3f2 100644 --- a/e2e/solid-start/basic/tests/not-found.spec.ts +++ b/e2e/solid-start/basic/tests/not-found.spec.ts @@ -9,6 +9,7 @@ const combinate = (combinateImport as any).default as typeof combinateImport test.use({ whitelistErrors: [ /Failed to load resource: the server responded with a status of 404/, + 'Error during route context hydration: {isNotFound: true}', ], }) test.describe('not-found', () => { @@ -25,7 +26,7 @@ test.describe('not-found', () => { test.describe('throw notFound()', () => { const navigationTestMatrix = combinate({ // TODO beforeLoad! - thrower: [/* 'beforeLoad',*/ 'loader'] as const, + thrower: [/* 'beforeLoad',*/ 'head', 'loader'] as const, preload: [false, true] as const, }) @@ -56,7 +57,7 @@ test.describe('not-found', () => { const directVisitTestMatrix = combinate({ // TODO beforeLoad! - thrower: [/* 'beforeLoad',*/ 'loader'] as const, + thrower: [/* 'beforeLoad',*/ 'head', 'loader'] as const, }) directVisitTestMatrix.forEach(({ thrower }) => { diff --git a/packages/router-core/src/ssr/ssr-client.ts b/packages/router-core/src/ssr/ssr-client.ts index 736fbeac6ca..1b66f0543be 100644 --- a/packages/router-core/src/ssr/ssr-client.ts +++ b/packages/router-core/src/ssr/ssr-client.ts @@ -216,8 +216,10 @@ export async function hydrate(router: AnyRouter): Promise { match.headScripts = headFnContent?.scripts match.styles = headFnContent?.styles match.scripts = scripts - }), - ) + }) + ).catch((err) => { + console.error('Error during route context hydration:', err) + }) const isSpaMode = matches[matches.length - 1]!.id !== lastMatchId const hasSsrFalseMatches = matches.some((m) => m.ssr === false) diff --git a/packages/router-core/tests/hydrate.test.ts b/packages/router-core/tests/hydrate.test.ts new file mode 100644 index 00000000000..3bc5706d44f --- /dev/null +++ b/packages/router-core/tests/hydrate.test.ts @@ -0,0 +1,255 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { hydrate } from '../src/ssr/ssr-client' +import type { TsrSsrGlobal } from '../src/ssr/ssr-client' +import { + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, + notFound, +} from '../../react-router/dist/esm' +import type { AnyRouteMatch } from '../src' + +describe('hydrate', () => { + let mockWindow: { $_TSR?: TsrSsrGlobal } + let mockRouter: any + let mockHead: any + + beforeEach(() => { + // Reset global window mock + mockWindow = {} + ;(global as any).window = mockWindow + + // Reset mock head function + mockHead = vi.fn() + + const history = createMemoryHistory({ initialEntries: ['/'] }) + + const rootRoute = createRootRoute({}) + + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () => 'Index', + notFoundComponent: () => 'Not Found', + head: mockHead, + }) + + const otherRoute = createRoute({ + getParentRoute: () => indexRoute, + path: '/other', + component: () => 'Other', + }) + + const routeTree = rootRoute.addChildren([indexRoute.addChildren([otherRoute])]) + + mockRouter = createRouter({ routeTree, history, isServer: true }) + }) + + afterEach(() => { + vi.resetAllMocks() + delete (global as any).window + }) + + it('should throw error if window.$_TSR is not available', async () => { + await expect(hydrate(mockRouter)).rejects.toThrow( + 'Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!' + ) + }) + + it('should throw error if window.$_TSR.router is not available', async () => { + mockWindow.$_TSR = { + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + // router is missing + } as any + + await expect(hydrate(mockRouter)).rejects.toThrow( + 'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!' + ) + }) + + it('should initialize serialization adapters when provided', async () => { + const mockSerializer = { + key: 'testAdapter', + fromSerializable: vi.fn(), + toSerializable: vi.fn(), + test: vi.fn().mockReturnValue(true), + '~types': { + input: {}, + output: {}, + extends: {}, + }, + } + + mockRouter.options.serializationAdapters = [mockSerializer] + + const mockMatches = [ + { id: '/', routeId: '/', index: 0, _nonReactive: {} }, + ] + mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) + mockRouter.state.matches = mockMatches + + const mockBuffer = [vi.fn(), vi.fn()] + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: [], + }, + c: vi.fn(), + p: vi.fn(), + buffer: mockBuffer, + initialized: false, + } + + await hydrate(mockRouter) + + expect(mockWindow.$_TSR!.t).toBeInstanceOf(Map) + expect(mockWindow.$_TSR!.t?.get('testAdapter')).toBe(mockSerializer.fromSerializable) + expect(mockBuffer[0]).toHaveBeenCalled() + expect(mockBuffer[1]).toHaveBeenCalled() + expect(mockWindow.$_TSR!.initialized).toBe(true) + }) + + it('should handle empty serialization adapters', async () => { + mockRouter.options.serializationAdapters = [] + + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: [], + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + expect(mockWindow.$_TSR!.t).toBeUndefined() + expect(mockWindow.$_TSR!.initialized).toBe(true) + }) + + it('should set manifest in router.ssr', async () => { + const testManifest = { routes: {} } + mockWindow.$_TSR = { + router: { + manifest: testManifest, + dehydratedData: {}, + lastMatchId: '/', + matches: [], + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + expect(mockRouter.ssr).toEqual({ + manifest: testManifest, + }) + }) + + it('should hydrate matches', async () => { + const mockMatches = [ + { + id: '/', + routeId: '/', + index: 0, + ssr: undefined, + _nonReactive: {}, + }, + { + id: '/other', + routeId: '/other', + index: 1, + ssr: undefined, + _nonReactive: {}, + }, + ] + + const dehydratedMatches = [ + { + i: '/', + l: { indexData: 'server-data' }, + s: 'success' as const, + ssr: true, + u: Date.now(), + }, + ] + + mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) + mockRouter.state.matches = mockMatches + + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: dehydratedMatches, + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + const { id, loaderData, ssr, status } = mockMatches[0] as AnyRouteMatch + expect(id).toBe('/') + expect(loaderData).toEqual({ indexData: 'server-data' }) + expect(status).toBe('success') + expect(ssr).toBe(true) + }) + + it('should handle errors during route context hydration', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + mockHead.mockImplementation(() => { + throw notFound() + }) + + const mockMatches = [ + { id: '/', routeId: '/', index: 0, ssr: true, _nonReactive: {} }, + ] + + mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) + mockRouter.state.matches = mockMatches + + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: [ + { + i: '/', + l: { data: 'test' }, + s: 'success', + ssr: true, + u: Date.now(), + }, + ], + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + expect(consoleSpy).toHaveBeenCalledWith('Error during route context hydration:', { 'isNotFound': true }) + + consoleSpy.mockRestore() + }) +}) From f9ee4a5766cc5e4f57f2c51fdd2acd15e25aa54f Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 21:08:34 +0000 Subject: [PATCH 02/18] ci: apply automated fixes --- .../basic/src/routes/not-found/via-head.tsx | 2 +- .../basic/src/routes/not-found/via-head.tsx | 2 +- packages/router-core/src/ssr/ssr-client.ts | 2 +- packages/router-core/tests/hydrate.test.ts | 47 ++++++++++--------- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/e2e/react-start/basic/src/routes/not-found/via-head.tsx b/e2e/react-start/basic/src/routes/not-found/via-head.tsx index 72107945a44..7cd09f9fa31 100644 --- a/e2e/react-start/basic/src/routes/not-found/via-head.tsx +++ b/e2e/react-start/basic/src/routes/not-found/via-head.tsx @@ -20,4 +20,4 @@ function RouteComponent() { Hello "/not-found/via-head"! ) -} \ No newline at end of file +} diff --git a/e2e/solid-start/basic/src/routes/not-found/via-head.tsx b/e2e/solid-start/basic/src/routes/not-found/via-head.tsx index feefd921cab..51806db45e1 100644 --- a/e2e/solid-start/basic/src/routes/not-found/via-head.tsx +++ b/e2e/solid-start/basic/src/routes/not-found/via-head.tsx @@ -20,4 +20,4 @@ function RouteComponent() { Hello "/not-found/via-head"! ) -} \ No newline at end of file +} diff --git a/packages/router-core/src/ssr/ssr-client.ts b/packages/router-core/src/ssr/ssr-client.ts index 1b66f0543be..dec6056c90d 100644 --- a/packages/router-core/src/ssr/ssr-client.ts +++ b/packages/router-core/src/ssr/ssr-client.ts @@ -216,7 +216,7 @@ export async function hydrate(router: AnyRouter): Promise { match.headScripts = headFnContent?.scripts match.styles = headFnContent?.styles match.scripts = scripts - }) + }), ).catch((err) => { console.error('Error during route context hydration:', err) }) diff --git a/packages/router-core/tests/hydrate.test.ts b/packages/router-core/tests/hydrate.test.ts index 3bc5706d44f..174340fdb7d 100644 --- a/packages/router-core/tests/hydrate.test.ts +++ b/packages/router-core/tests/hydrate.test.ts @@ -28,20 +28,22 @@ describe('hydrate', () => { const rootRoute = createRootRoute({}) const indexRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/', - component: () => 'Index', - notFoundComponent: () => 'Not Found', - head: mockHead, + getParentRoute: () => rootRoute, + path: '/', + component: () => 'Index', + notFoundComponent: () => 'Not Found', + head: mockHead, }) const otherRoute = createRoute({ - getParentRoute: () => indexRoute, - path: '/other', - component: () => 'Other', + getParentRoute: () => indexRoute, + path: '/other', + component: () => 'Other', }) - const routeTree = rootRoute.addChildren([indexRoute.addChildren([otherRoute])]) + const routeTree = rootRoute.addChildren([ + indexRoute.addChildren([otherRoute]), + ]) mockRouter = createRouter({ routeTree, history, isServer: true }) }) @@ -53,7 +55,7 @@ describe('hydrate', () => { it('should throw error if window.$_TSR is not available', async () => { await expect(hydrate(mockRouter)).rejects.toThrow( - 'Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!' + 'Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!', ) }) @@ -67,7 +69,7 @@ describe('hydrate', () => { } as any await expect(hydrate(mockRouter)).rejects.toThrow( - 'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!' + 'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!', ) }) @@ -85,10 +87,8 @@ describe('hydrate', () => { } mockRouter.options.serializationAdapters = [mockSerializer] - - const mockMatches = [ - { id: '/', routeId: '/', index: 0, _nonReactive: {} }, - ] + + const mockMatches = [{ id: '/', routeId: '/', index: 0, _nonReactive: {} }] mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) mockRouter.state.matches = mockMatches @@ -109,7 +109,9 @@ describe('hydrate', () => { await hydrate(mockRouter) expect(mockWindow.$_TSR!.t).toBeInstanceOf(Map) - expect(mockWindow.$_TSR!.t?.get('testAdapter')).toBe(mockSerializer.fromSerializable) + expect(mockWindow.$_TSR!.t?.get('testAdapter')).toBe( + mockSerializer.fromSerializable, + ) expect(mockBuffer[0]).toHaveBeenCalled() expect(mockBuffer[1]).toHaveBeenCalled() expect(mockWindow.$_TSR!.initialized).toBe(true) @@ -214,10 +216,10 @@ describe('hydrate', () => { it('should handle errors during route context hydration', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - mockHead.mockImplementation(() => { - throw notFound() + mockHead.mockImplementation(() => { + throw notFound() }) - + const mockMatches = [ { id: '/', routeId: '/', index: 0, ssr: true, _nonReactive: {} }, ] @@ -248,8 +250,11 @@ describe('hydrate', () => { await hydrate(mockRouter) - expect(consoleSpy).toHaveBeenCalledWith('Error during route context hydration:', { 'isNotFound': true }) - + expect(consoleSpy).toHaveBeenCalledWith( + 'Error during route context hydration:', + { isNotFound: true }, + ) + consoleSpy.mockRestore() }) }) From 492a898cd9febc0ab158a202e42d307211bc152f Mon Sep 17 00:00:00 2001 From: Remi Oduyemi Date: Wed, 19 Nov 2025 11:52:59 +0000 Subject: [PATCH 03/18] chore: update match.error and rethrow if not isNotFound --- e2e/react-start/basic/tests/not-found.spec.ts | 2 +- .../test-results/.last-run.json | 4 + e2e/solid-start/basic/tests/not-found.spec.ts | 2 +- .../test-results/.last-run.json | 4 + packages/router-core/src/ssr/ssr-client.ts | 101 ++++++++++-------- packages/router-core/tests/hydrate.test.ts | 6 +- 6 files changed, 71 insertions(+), 48 deletions(-) create mode 100644 e2e/solid-start/basic-solid-query/test-results/.last-run.json create mode 100644 e2e/solid-start/query-integration/test-results/.last-run.json diff --git a/e2e/react-start/basic/tests/not-found.spec.ts b/e2e/react-start/basic/tests/not-found.spec.ts index 099e4077697..447d1fd7322 100644 --- a/e2e/react-start/basic/tests/not-found.spec.ts +++ b/e2e/react-start/basic/tests/not-found.spec.ts @@ -9,7 +9,7 @@ const combinate = (combinateImport as any).default as typeof combinateImport test.use({ whitelistErrors: [ /Failed to load resource: the server responded with a status of 404/, - 'Error during route context hydration: {isNotFound: true}', + /NotFound error during hydration for routeId:.*/, ], }) test.describe('not-found', () => { diff --git a/e2e/solid-start/basic-solid-query/test-results/.last-run.json b/e2e/solid-start/basic-solid-query/test-results/.last-run.json new file mode 100644 index 00000000000..5fca3f84bc7 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "failed", + "failedTests": [] +} \ No newline at end of file diff --git a/e2e/solid-start/basic/tests/not-found.spec.ts b/e2e/solid-start/basic/tests/not-found.spec.ts index 82b48f1b3f2..3268c001fdd 100644 --- a/e2e/solid-start/basic/tests/not-found.spec.ts +++ b/e2e/solid-start/basic/tests/not-found.spec.ts @@ -9,7 +9,7 @@ const combinate = (combinateImport as any).default as typeof combinateImport test.use({ whitelistErrors: [ /Failed to load resource: the server responded with a status of 404/, - 'Error during route context hydration: {isNotFound: true}', + /NotFound error during hydration for routeId:.*/, ], }) test.describe('not-found', () => { diff --git a/e2e/solid-start/query-integration/test-results/.last-run.json b/e2e/solid-start/query-integration/test-results/.last-run.json new file mode 100644 index 00000000000..5fca3f84bc7 --- /dev/null +++ b/e2e/solid-start/query-integration/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "failed", + "failedTests": [] +} \ No newline at end of file diff --git a/packages/router-core/src/ssr/ssr-client.ts b/packages/router-core/src/ssr/ssr-client.ts index 5688eb32e9c..5fd6b508d77 100644 --- a/packages/router-core/src/ssr/ssr-client.ts +++ b/packages/router-core/src/ssr/ssr-client.ts @@ -1,4 +1,5 @@ import invariant from 'tiny-invariant' +import { isNotFound } from '../not-found' import { batch } from '@tanstack/store' import { createControlledPromise } from '../utils' import type { AnyRouteMatch, MakeRouteMatch } from '../Matches' @@ -179,56 +180,68 @@ export async function hydrate(router: AnyRouter): Promise { // 2) execute `head()` and `scripts()` for each match await Promise.all( router.state.matches.map(async (match) => { - const route = router.looseRoutesById[match.routeId]! + try { + const route = router.looseRoutesById[match.routeId]! + + const parentMatch = router.state.matches[match.index - 1] + const parentContext = parentMatch?.context ?? router.options.context + + // `context()` was already executed by `matchRoutes`, however route context was not yet fully reconstructed + // so run it again and merge route context + if (route.options.context) { + const contextFnContext: RouteContextOptions = { + deps: match.loaderDeps, + params: match.params, + context: parentContext ?? {}, + location: router.state.location, + navigate: (opts: any) => + router.navigate({ ...opts, _fromLocation: router.state.location }), + buildLocation: router.buildLocation, + cause: match.cause, + abortController: match.abortController, + preload: false, + matches, + } + match.__routeContext = + route.options.context(contextFnContext) ?? undefined + } - const parentMatch = router.state.matches[match.index - 1] - const parentContext = parentMatch?.context ?? router.options.context + match.context = { + ...parentContext, + ...match.__routeContext, + ...match.__beforeLoadContext, + } - // `context()` was already executed by `matchRoutes`, however route context was not yet fully reconstructed - // so run it again and merge route context - if (route.options.context) { - const contextFnContext: RouteContextOptions = { - deps: match.loaderDeps, + const assetContext = { + matches: router.state.matches, + match, params: match.params, - context: parentContext ?? {}, - location: router.state.location, - navigate: (opts: any) => - router.navigate({ ...opts, _fromLocation: router.state.location }), - buildLocation: router.buildLocation, - cause: match.cause, - abortController: match.abortController, - preload: false, - matches, + loaderData: match.loaderData, } - match.__routeContext = - route.options.context(contextFnContext) ?? undefined - } - - match.context = { - ...parentContext, - ...match.__routeContext, - ...match.__beforeLoadContext, - } - - const assetContext = { - matches: router.state.matches, - match, - params: match.params, - loaderData: match.loaderData, + const headFnContent = await route.options.head?.(assetContext) + + const scripts = await route.options.scripts?.(assetContext) + + match.meta = headFnContent?.meta + match.links = headFnContent?.links + match.headScripts = headFnContent?.scripts + match.styles = headFnContent?.styles + match.scripts = scripts + } catch (err) { + if (isNotFound(err)) { + match.error = { isNotFound: true } + console.error( + `NotFound error during hydration for routeId: ${match.routeId}`, + err, + ) + } else { + match.error = err as any + console.error(`Error during hydration for route ${match.routeId}:`, err) + throw err + } } - const headFnContent = await route.options.head?.(assetContext) - - const scripts = await route.options.scripts?.(assetContext) - - match.meta = headFnContent?.meta - match.links = headFnContent?.links - match.headScripts = headFnContent?.scripts - match.styles = headFnContent?.styles - match.scripts = scripts }), - ).catch((err) => { - console.error('Error during route context hydration:', err) - }) + ) const isSpaMode = matches[matches.length - 1]!.id !== lastMatchId const hasSsrFalseMatches = matches.some((m) => m.ssr === false) diff --git a/packages/router-core/tests/hydrate.test.ts b/packages/router-core/tests/hydrate.test.ts index 174340fdb7d..859b2cd2b96 100644 --- a/packages/router-core/tests/hydrate.test.ts +++ b/packages/router-core/tests/hydrate.test.ts @@ -251,8 +251,10 @@ describe('hydrate', () => { await hydrate(mockRouter) expect(consoleSpy).toHaveBeenCalledWith( - 'Error during route context hydration:', - { isNotFound: true }, + 'NotFound error during hydration for routeId: /', + expect.objectContaining({ + isNotFound: true, + }), ) consoleSpy.mockRestore() From b2eb15a5b38a0f6820f10d0220021cb0a8f6f768 Mon Sep 17 00:00:00 2001 From: Remi Oduyemi Date: Wed, 19 Nov 2025 11:56:34 +0000 Subject: [PATCH 04/18] chore: remove test runs --- e2e/solid-start/basic-solid-query/test-results/.last-run.json | 4 ---- e2e/solid-start/query-integration/test-results/.last-run.json | 4 ---- 2 files changed, 8 deletions(-) delete mode 100644 e2e/solid-start/basic-solid-query/test-results/.last-run.json delete mode 100644 e2e/solid-start/query-integration/test-results/.last-run.json diff --git a/e2e/solid-start/basic-solid-query/test-results/.last-run.json b/e2e/solid-start/basic-solid-query/test-results/.last-run.json deleted file mode 100644 index 5fca3f84bc7..00000000000 --- a/e2e/solid-start/basic-solid-query/test-results/.last-run.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": "failed", - "failedTests": [] -} \ No newline at end of file diff --git a/e2e/solid-start/query-integration/test-results/.last-run.json b/e2e/solid-start/query-integration/test-results/.last-run.json deleted file mode 100644 index 5fca3f84bc7..00000000000 --- a/e2e/solid-start/query-integration/test-results/.last-run.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": "failed", - "failedTests": [] -} \ No newline at end of file From 0043a5c04df6ae79a6829136356ab78d6858eaf7 Mon Sep 17 00:00:00 2001 From: Remi <31855291+roduyemi@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:04:28 +0000 Subject: [PATCH 05/18] chore(lint): Update imports in hydrate.test.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/router-core/tests/hydrate.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/router-core/tests/hydrate.test.ts b/packages/router-core/tests/hydrate.test.ts index 859b2cd2b96..9956e7ca35f 100644 --- a/packages/router-core/tests/hydrate.test.ts +++ b/packages/router-core/tests/hydrate.test.ts @@ -1,6 +1,4 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import { hydrate } from '../src/ssr/ssr-client' -import type { TsrSsrGlobal } from '../src/ssr/ssr-client' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { createMemoryHistory, createRootRoute, @@ -8,6 +6,8 @@ import { createRouter, notFound, } from '../../react-router/dist/esm' +import { hydrate } from '../src/ssr/ssr-client' +import type { TsrSsrGlobal } from '../src/ssr/ssr-client' import type { AnyRouteMatch } from '../src' describe('hydrate', () => { From cdf1237731ca06379b62ae617b9724683d9bbd73 Mon Sep 17 00:00:00 2001 From: Remi Oduyemi Date: Wed, 19 Nov 2025 12:11:11 +0000 Subject: [PATCH 06/18] chore: assert match.error equals isNotFound --- packages/router-core/tests/hydrate.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/router-core/tests/hydrate.test.ts b/packages/router-core/tests/hydrate.test.ts index 9956e7ca35f..b6e2c2983b9 100644 --- a/packages/router-core/tests/hydrate.test.ts +++ b/packages/router-core/tests/hydrate.test.ts @@ -250,6 +250,9 @@ describe('hydrate', () => { await hydrate(mockRouter) + const match = mockRouter.state.matches[0] as AnyRouteMatch + expect(match.error).toEqual({ isNotFound: true }) + expect(consoleSpy).toHaveBeenCalledWith( 'NotFound error during hydration for routeId: /', expect.objectContaining({ From e6aad52219bbfe6059ee025e634a3feb7a65b2e7 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:21:29 +0000 Subject: [PATCH 07/18] ci: apply automated fixes --- packages/router-core/src/ssr/ssr-client.ts | 32 +++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/router-core/src/ssr/ssr-client.ts b/packages/router-core/src/ssr/ssr-client.ts index 5fd6b508d77..0028752c817 100644 --- a/packages/router-core/src/ssr/ssr-client.ts +++ b/packages/router-core/src/ssr/ssr-client.ts @@ -195,7 +195,10 @@ export async function hydrate(router: AnyRouter): Promise { context: parentContext ?? {}, location: router.state.location, navigate: (opts: any) => - router.navigate({ ...opts, _fromLocation: router.state.location }), + router.navigate({ + ...opts, + _fromLocation: router.state.location, + }), buildLocation: router.buildLocation, cause: match.cause, abortController: match.abortController, @@ -227,18 +230,21 @@ export async function hydrate(router: AnyRouter): Promise { match.headScripts = headFnContent?.scripts match.styles = headFnContent?.styles match.scripts = scripts - } catch (err) { - if (isNotFound(err)) { - match.error = { isNotFound: true } - console.error( - `NotFound error during hydration for routeId: ${match.routeId}`, - err, - ) - } else { - match.error = err as any - console.error(`Error during hydration for route ${match.routeId}:`, err) - throw err - } + } catch (err) { + if (isNotFound(err)) { + match.error = { isNotFound: true } + console.error( + `NotFound error during hydration for routeId: ${match.routeId}`, + err, + ) + } else { + match.error = err as any + console.error( + `Error during hydration for route ${match.routeId}:`, + err, + ) + throw err + } } }), ) From b8bdcad318743988ec9b0cf726c3324e5bbaba3e Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Wed, 19 Nov 2025 16:40:25 +0100 Subject: [PATCH 08/18] eslint import order --- packages/router-core/src/ssr/ssr-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router-core/src/ssr/ssr-client.ts b/packages/router-core/src/ssr/ssr-client.ts index 0028752c817..4f52ca66a24 100644 --- a/packages/router-core/src/ssr/ssr-client.ts +++ b/packages/router-core/src/ssr/ssr-client.ts @@ -1,6 +1,6 @@ import invariant from 'tiny-invariant' -import { isNotFound } from '../not-found' import { batch } from '@tanstack/store' +import { isNotFound } from '../not-found' import { createControlledPromise } from '../utils' import type { AnyRouteMatch, MakeRouteMatch } from '../Matches' import type { AnyRouter } from '../router' From 56819effccf0e8b56d79ef0a93f1374bed7f821d Mon Sep 17 00:00:00 2001 From: Remi Oduyemi Date: Wed, 19 Nov 2025 15:53:50 +0000 Subject: [PATCH 09/18] fix: use string for notFound whitelist error --- e2e/react-start/basic/tests/not-found.spec.ts | 2 +- e2e/solid-start/basic/tests/not-found.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/react-start/basic/tests/not-found.spec.ts b/e2e/react-start/basic/tests/not-found.spec.ts index 447d1fd7322..0b5acc3b782 100644 --- a/e2e/react-start/basic/tests/not-found.spec.ts +++ b/e2e/react-start/basic/tests/not-found.spec.ts @@ -9,7 +9,7 @@ const combinate = (combinateImport as any).default as typeof combinateImport test.use({ whitelistErrors: [ /Failed to load resource: the server responded with a status of 404/, - /NotFound error during hydration for routeId:.*/, + 'NotFound error during hydration for routeId', ], }) test.describe('not-found', () => { diff --git a/e2e/solid-start/basic/tests/not-found.spec.ts b/e2e/solid-start/basic/tests/not-found.spec.ts index 3268c001fdd..1bed8e991c9 100644 --- a/e2e/solid-start/basic/tests/not-found.spec.ts +++ b/e2e/solid-start/basic/tests/not-found.spec.ts @@ -9,7 +9,7 @@ const combinate = (combinateImport as any).default as typeof combinateImport test.use({ whitelistErrors: [ /Failed to load resource: the server responded with a status of 404/, - /NotFound error during hydration for routeId:.*/, + 'NotFound error during hydration for routeId', ], }) test.describe('not-found', () => { From b4383214cd6e41e6c94b5e27691d6199cded7575 Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Wed, 19 Nov 2025 17:20:53 +0100 Subject: [PATCH 10/18] use source file from react-router --- packages/router-core/tests/hydrate.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/router-core/tests/hydrate.test.ts b/packages/router-core/tests/hydrate.test.ts index b6e2c2983b9..526dcd47da8 100644 --- a/packages/router-core/tests/hydrate.test.ts +++ b/packages/router-core/tests/hydrate.test.ts @@ -5,7 +5,7 @@ import { createRoute, createRouter, notFound, -} from '../../react-router/dist/esm' +} from '../../react-router' import { hydrate } from '../src/ssr/ssr-client' import type { TsrSsrGlobal } from '../src/ssr/ssr-client' import type { AnyRouteMatch } from '../src' @@ -108,13 +108,13 @@ describe('hydrate', () => { await hydrate(mockRouter) - expect(mockWindow.$_TSR!.t).toBeInstanceOf(Map) - expect(mockWindow.$_TSR!.t?.get('testAdapter')).toBe( + expect(mockWindow.$_TSR.t).toBeInstanceOf(Map) + expect(mockWindow.$_TSR.t?.get('testAdapter')).toBe( mockSerializer.fromSerializable, ) expect(mockBuffer[0]).toHaveBeenCalled() expect(mockBuffer[1]).toHaveBeenCalled() - expect(mockWindow.$_TSR!.initialized).toBe(true) + expect(mockWindow.$_TSR.initialized).toBe(true) }) it('should handle empty serialization adapters', async () => { @@ -135,8 +135,8 @@ describe('hydrate', () => { await hydrate(mockRouter) - expect(mockWindow.$_TSR!.t).toBeUndefined() - expect(mockWindow.$_TSR!.initialized).toBe(true) + expect(mockWindow.$_TSR.t).toBeUndefined() + expect(mockWindow.$_TSR.initialized).toBe(true) }) it('should set manifest in router.ssr', async () => { From 52cb3e0d6165b28294cf6dfd0bf0a10a9779b5e6 Mon Sep 17 00:00:00 2001 From: Remi Oduyemi Date: Wed, 19 Nov 2025 16:48:29 +0000 Subject: [PATCH 11/18] chore: move hydrate test to react-router --- .../{router-core => react-router}/tests/hydrate.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename packages/{router-core => react-router}/tests/hydrate.test.ts (97%) diff --git a/packages/router-core/tests/hydrate.test.ts b/packages/react-router/tests/hydrate.test.ts similarity index 97% rename from packages/router-core/tests/hydrate.test.ts rename to packages/react-router/tests/hydrate.test.ts index 526dcd47da8..5bbeecd8d46 100644 --- a/packages/router-core/tests/hydrate.test.ts +++ b/packages/react-router/tests/hydrate.test.ts @@ -5,9 +5,9 @@ import { createRoute, createRouter, notFound, -} from '../../react-router' -import { hydrate } from '../src/ssr/ssr-client' -import type { TsrSsrGlobal } from '../src/ssr/ssr-client' +} from '../src' +import { hydrate } from '../../router-core/src/ssr/ssr-client' +import type { TsrSsrGlobal } from '../../router-core/src/ssr/ssr-client' import type { AnyRouteMatch } from '../src' describe('hydrate', () => { From 424fe830dab71a5d000614b2266538c84917ffff Mon Sep 17 00:00:00 2001 From: Remi Oduyemi Date: Wed, 19 Nov 2025 17:06:07 +0000 Subject: [PATCH 12/18] chore: add hydrate test to solid-router --- packages/solid-router/tests/hydrate.test.ts | 265 ++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 packages/solid-router/tests/hydrate.test.ts diff --git a/packages/solid-router/tests/hydrate.test.ts b/packages/solid-router/tests/hydrate.test.ts new file mode 100644 index 00000000000..5bbeecd8d46 --- /dev/null +++ b/packages/solid-router/tests/hydrate.test.ts @@ -0,0 +1,265 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, + notFound, +} from '../src' +import { hydrate } from '../../router-core/src/ssr/ssr-client' +import type { TsrSsrGlobal } from '../../router-core/src/ssr/ssr-client' +import type { AnyRouteMatch } from '../src' + +describe('hydrate', () => { + let mockWindow: { $_TSR?: TsrSsrGlobal } + let mockRouter: any + let mockHead: any + + beforeEach(() => { + // Reset global window mock + mockWindow = {} + ;(global as any).window = mockWindow + + // Reset mock head function + mockHead = vi.fn() + + const history = createMemoryHistory({ initialEntries: ['/'] }) + + const rootRoute = createRootRoute({}) + + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () => 'Index', + notFoundComponent: () => 'Not Found', + head: mockHead, + }) + + const otherRoute = createRoute({ + getParentRoute: () => indexRoute, + path: '/other', + component: () => 'Other', + }) + + const routeTree = rootRoute.addChildren([ + indexRoute.addChildren([otherRoute]), + ]) + + mockRouter = createRouter({ routeTree, history, isServer: true }) + }) + + afterEach(() => { + vi.resetAllMocks() + delete (global as any).window + }) + + it('should throw error if window.$_TSR is not available', async () => { + await expect(hydrate(mockRouter)).rejects.toThrow( + 'Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!', + ) + }) + + it('should throw error if window.$_TSR.router is not available', async () => { + mockWindow.$_TSR = { + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + // router is missing + } as any + + await expect(hydrate(mockRouter)).rejects.toThrow( + 'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!', + ) + }) + + it('should initialize serialization adapters when provided', async () => { + const mockSerializer = { + key: 'testAdapter', + fromSerializable: vi.fn(), + toSerializable: vi.fn(), + test: vi.fn().mockReturnValue(true), + '~types': { + input: {}, + output: {}, + extends: {}, + }, + } + + mockRouter.options.serializationAdapters = [mockSerializer] + + const mockMatches = [{ id: '/', routeId: '/', index: 0, _nonReactive: {} }] + mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) + mockRouter.state.matches = mockMatches + + const mockBuffer = [vi.fn(), vi.fn()] + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: [], + }, + c: vi.fn(), + p: vi.fn(), + buffer: mockBuffer, + initialized: false, + } + + await hydrate(mockRouter) + + expect(mockWindow.$_TSR.t).toBeInstanceOf(Map) + expect(mockWindow.$_TSR.t?.get('testAdapter')).toBe( + mockSerializer.fromSerializable, + ) + expect(mockBuffer[0]).toHaveBeenCalled() + expect(mockBuffer[1]).toHaveBeenCalled() + expect(mockWindow.$_TSR.initialized).toBe(true) + }) + + it('should handle empty serialization adapters', async () => { + mockRouter.options.serializationAdapters = [] + + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: [], + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + expect(mockWindow.$_TSR.t).toBeUndefined() + expect(mockWindow.$_TSR.initialized).toBe(true) + }) + + it('should set manifest in router.ssr', async () => { + const testManifest = { routes: {} } + mockWindow.$_TSR = { + router: { + manifest: testManifest, + dehydratedData: {}, + lastMatchId: '/', + matches: [], + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + expect(mockRouter.ssr).toEqual({ + manifest: testManifest, + }) + }) + + it('should hydrate matches', async () => { + const mockMatches = [ + { + id: '/', + routeId: '/', + index: 0, + ssr: undefined, + _nonReactive: {}, + }, + { + id: '/other', + routeId: '/other', + index: 1, + ssr: undefined, + _nonReactive: {}, + }, + ] + + const dehydratedMatches = [ + { + i: '/', + l: { indexData: 'server-data' }, + s: 'success' as const, + ssr: true, + u: Date.now(), + }, + ] + + mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) + mockRouter.state.matches = mockMatches + + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: dehydratedMatches, + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + const { id, loaderData, ssr, status } = mockMatches[0] as AnyRouteMatch + expect(id).toBe('/') + expect(loaderData).toEqual({ indexData: 'server-data' }) + expect(status).toBe('success') + expect(ssr).toBe(true) + }) + + it('should handle errors during route context hydration', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + mockHead.mockImplementation(() => { + throw notFound() + }) + + const mockMatches = [ + { id: '/', routeId: '/', index: 0, ssr: true, _nonReactive: {} }, + ] + + mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) + mockRouter.state.matches = mockMatches + + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: [ + { + i: '/', + l: { data: 'test' }, + s: 'success', + ssr: true, + u: Date.now(), + }, + ], + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + const match = mockRouter.state.matches[0] as AnyRouteMatch + expect(match.error).toEqual({ isNotFound: true }) + + expect(consoleSpy).toHaveBeenCalledWith( + 'NotFound error during hydration for routeId: /', + expect.objectContaining({ + isNotFound: true, + }), + ) + + consoleSpy.mockRestore() + }) +}) From 3bf6d4133d6158d96676b29f6261bb62618c5ff4 Mon Sep 17 00:00:00 2001 From: Remi Oduyemi Date: Wed, 19 Nov 2025 18:31:39 +0000 Subject: [PATCH 13/18] chore: delete solid-router hydrate.test --- packages/solid-router/tests/hydrate.test.ts | 265 -------------------- 1 file changed, 265 deletions(-) delete mode 100644 packages/solid-router/tests/hydrate.test.ts diff --git a/packages/solid-router/tests/hydrate.test.ts b/packages/solid-router/tests/hydrate.test.ts deleted file mode 100644 index 5bbeecd8d46..00000000000 --- a/packages/solid-router/tests/hydrate.test.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { - createMemoryHistory, - createRootRoute, - createRoute, - createRouter, - notFound, -} from '../src' -import { hydrate } from '../../router-core/src/ssr/ssr-client' -import type { TsrSsrGlobal } from '../../router-core/src/ssr/ssr-client' -import type { AnyRouteMatch } from '../src' - -describe('hydrate', () => { - let mockWindow: { $_TSR?: TsrSsrGlobal } - let mockRouter: any - let mockHead: any - - beforeEach(() => { - // Reset global window mock - mockWindow = {} - ;(global as any).window = mockWindow - - // Reset mock head function - mockHead = vi.fn() - - const history = createMemoryHistory({ initialEntries: ['/'] }) - - const rootRoute = createRootRoute({}) - - const indexRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/', - component: () => 'Index', - notFoundComponent: () => 'Not Found', - head: mockHead, - }) - - const otherRoute = createRoute({ - getParentRoute: () => indexRoute, - path: '/other', - component: () => 'Other', - }) - - const routeTree = rootRoute.addChildren([ - indexRoute.addChildren([otherRoute]), - ]) - - mockRouter = createRouter({ routeTree, history, isServer: true }) - }) - - afterEach(() => { - vi.resetAllMocks() - delete (global as any).window - }) - - it('should throw error if window.$_TSR is not available', async () => { - await expect(hydrate(mockRouter)).rejects.toThrow( - 'Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!', - ) - }) - - it('should throw error if window.$_TSR.router is not available', async () => { - mockWindow.$_TSR = { - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - // router is missing - } as any - - await expect(hydrate(mockRouter)).rejects.toThrow( - 'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!', - ) - }) - - it('should initialize serialization adapters when provided', async () => { - const mockSerializer = { - key: 'testAdapter', - fromSerializable: vi.fn(), - toSerializable: vi.fn(), - test: vi.fn().mockReturnValue(true), - '~types': { - input: {}, - output: {}, - extends: {}, - }, - } - - mockRouter.options.serializationAdapters = [mockSerializer] - - const mockMatches = [{ id: '/', routeId: '/', index: 0, _nonReactive: {} }] - mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) - mockRouter.state.matches = mockMatches - - const mockBuffer = [vi.fn(), vi.fn()] - mockWindow.$_TSR = { - router: { - manifest: { routes: {} }, - dehydratedData: {}, - lastMatchId: '/', - matches: [], - }, - c: vi.fn(), - p: vi.fn(), - buffer: mockBuffer, - initialized: false, - } - - await hydrate(mockRouter) - - expect(mockWindow.$_TSR.t).toBeInstanceOf(Map) - expect(mockWindow.$_TSR.t?.get('testAdapter')).toBe( - mockSerializer.fromSerializable, - ) - expect(mockBuffer[0]).toHaveBeenCalled() - expect(mockBuffer[1]).toHaveBeenCalled() - expect(mockWindow.$_TSR.initialized).toBe(true) - }) - - it('should handle empty serialization adapters', async () => { - mockRouter.options.serializationAdapters = [] - - mockWindow.$_TSR = { - router: { - manifest: { routes: {} }, - dehydratedData: {}, - lastMatchId: '/', - matches: [], - }, - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } - - await hydrate(mockRouter) - - expect(mockWindow.$_TSR.t).toBeUndefined() - expect(mockWindow.$_TSR.initialized).toBe(true) - }) - - it('should set manifest in router.ssr', async () => { - const testManifest = { routes: {} } - mockWindow.$_TSR = { - router: { - manifest: testManifest, - dehydratedData: {}, - lastMatchId: '/', - matches: [], - }, - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } - - await hydrate(mockRouter) - - expect(mockRouter.ssr).toEqual({ - manifest: testManifest, - }) - }) - - it('should hydrate matches', async () => { - const mockMatches = [ - { - id: '/', - routeId: '/', - index: 0, - ssr: undefined, - _nonReactive: {}, - }, - { - id: '/other', - routeId: '/other', - index: 1, - ssr: undefined, - _nonReactive: {}, - }, - ] - - const dehydratedMatches = [ - { - i: '/', - l: { indexData: 'server-data' }, - s: 'success' as const, - ssr: true, - u: Date.now(), - }, - ] - - mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) - mockRouter.state.matches = mockMatches - - mockWindow.$_TSR = { - router: { - manifest: { routes: {} }, - dehydratedData: {}, - lastMatchId: '/', - matches: dehydratedMatches, - }, - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } - - await hydrate(mockRouter) - - const { id, loaderData, ssr, status } = mockMatches[0] as AnyRouteMatch - expect(id).toBe('/') - expect(loaderData).toEqual({ indexData: 'server-data' }) - expect(status).toBe('success') - expect(ssr).toBe(true) - }) - - it('should handle errors during route context hydration', async () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - mockHead.mockImplementation(() => { - throw notFound() - }) - - const mockMatches = [ - { id: '/', routeId: '/', index: 0, ssr: true, _nonReactive: {} }, - ] - - mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) - mockRouter.state.matches = mockMatches - - mockWindow.$_TSR = { - router: { - manifest: { routes: {} }, - dehydratedData: {}, - lastMatchId: '/', - matches: [ - { - i: '/', - l: { data: 'test' }, - s: 'success', - ssr: true, - u: Date.now(), - }, - ], - }, - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } - - await hydrate(mockRouter) - - const match = mockRouter.state.matches[0] as AnyRouteMatch - expect(match.error).toEqual({ isNotFound: true }) - - expect(consoleSpy).toHaveBeenCalledWith( - 'NotFound error during hydration for routeId: /', - expect.objectContaining({ - isNotFound: true, - }), - ) - - consoleSpy.mockRestore() - }) -}) From aa6ce570cec8c27b322dc8b104462ddd2ed42d4b Mon Sep 17 00:00:00 2001 From: Remi Oduyemi Date: Wed, 19 Nov 2025 19:41:27 +0000 Subject: [PATCH 14/18] fix: update to use package import --- packages/react-router/tests/hydrate.test.ts | 4 +- packages/solid-router/tests/hydrate.test.ts | 265 ++++++++++++++++++++ 2 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 packages/solid-router/tests/hydrate.test.ts diff --git a/packages/react-router/tests/hydrate.test.ts b/packages/react-router/tests/hydrate.test.ts index 5bbeecd8d46..0bcb9dd682d 100644 --- a/packages/react-router/tests/hydrate.test.ts +++ b/packages/react-router/tests/hydrate.test.ts @@ -6,8 +6,8 @@ import { createRouter, notFound, } from '../src' -import { hydrate } from '../../router-core/src/ssr/ssr-client' -import type { TsrSsrGlobal } from '../../router-core/src/ssr/ssr-client' +import { hydrate } from '@tanstack/router-core/ssr/client' +import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client' import type { AnyRouteMatch } from '../src' describe('hydrate', () => { diff --git a/packages/solid-router/tests/hydrate.test.ts b/packages/solid-router/tests/hydrate.test.ts new file mode 100644 index 00000000000..0bcb9dd682d --- /dev/null +++ b/packages/solid-router/tests/hydrate.test.ts @@ -0,0 +1,265 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, + notFound, +} from '../src' +import { hydrate } from '@tanstack/router-core/ssr/client' +import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client' +import type { AnyRouteMatch } from '../src' + +describe('hydrate', () => { + let mockWindow: { $_TSR?: TsrSsrGlobal } + let mockRouter: any + let mockHead: any + + beforeEach(() => { + // Reset global window mock + mockWindow = {} + ;(global as any).window = mockWindow + + // Reset mock head function + mockHead = vi.fn() + + const history = createMemoryHistory({ initialEntries: ['/'] }) + + const rootRoute = createRootRoute({}) + + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () => 'Index', + notFoundComponent: () => 'Not Found', + head: mockHead, + }) + + const otherRoute = createRoute({ + getParentRoute: () => indexRoute, + path: '/other', + component: () => 'Other', + }) + + const routeTree = rootRoute.addChildren([ + indexRoute.addChildren([otherRoute]), + ]) + + mockRouter = createRouter({ routeTree, history, isServer: true }) + }) + + afterEach(() => { + vi.resetAllMocks() + delete (global as any).window + }) + + it('should throw error if window.$_TSR is not available', async () => { + await expect(hydrate(mockRouter)).rejects.toThrow( + 'Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!', + ) + }) + + it('should throw error if window.$_TSR.router is not available', async () => { + mockWindow.$_TSR = { + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + // router is missing + } as any + + await expect(hydrate(mockRouter)).rejects.toThrow( + 'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!', + ) + }) + + it('should initialize serialization adapters when provided', async () => { + const mockSerializer = { + key: 'testAdapter', + fromSerializable: vi.fn(), + toSerializable: vi.fn(), + test: vi.fn().mockReturnValue(true), + '~types': { + input: {}, + output: {}, + extends: {}, + }, + } + + mockRouter.options.serializationAdapters = [mockSerializer] + + const mockMatches = [{ id: '/', routeId: '/', index: 0, _nonReactive: {} }] + mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) + mockRouter.state.matches = mockMatches + + const mockBuffer = [vi.fn(), vi.fn()] + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: [], + }, + c: vi.fn(), + p: vi.fn(), + buffer: mockBuffer, + initialized: false, + } + + await hydrate(mockRouter) + + expect(mockWindow.$_TSR.t).toBeInstanceOf(Map) + expect(mockWindow.$_TSR.t?.get('testAdapter')).toBe( + mockSerializer.fromSerializable, + ) + expect(mockBuffer[0]).toHaveBeenCalled() + expect(mockBuffer[1]).toHaveBeenCalled() + expect(mockWindow.$_TSR.initialized).toBe(true) + }) + + it('should handle empty serialization adapters', async () => { + mockRouter.options.serializationAdapters = [] + + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: [], + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + expect(mockWindow.$_TSR.t).toBeUndefined() + expect(mockWindow.$_TSR.initialized).toBe(true) + }) + + it('should set manifest in router.ssr', async () => { + const testManifest = { routes: {} } + mockWindow.$_TSR = { + router: { + manifest: testManifest, + dehydratedData: {}, + lastMatchId: '/', + matches: [], + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + expect(mockRouter.ssr).toEqual({ + manifest: testManifest, + }) + }) + + it('should hydrate matches', async () => { + const mockMatches = [ + { + id: '/', + routeId: '/', + index: 0, + ssr: undefined, + _nonReactive: {}, + }, + { + id: '/other', + routeId: '/other', + index: 1, + ssr: undefined, + _nonReactive: {}, + }, + ] + + const dehydratedMatches = [ + { + i: '/', + l: { indexData: 'server-data' }, + s: 'success' as const, + ssr: true, + u: Date.now(), + }, + ] + + mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) + mockRouter.state.matches = mockMatches + + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: dehydratedMatches, + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + const { id, loaderData, ssr, status } = mockMatches[0] as AnyRouteMatch + expect(id).toBe('/') + expect(loaderData).toEqual({ indexData: 'server-data' }) + expect(status).toBe('success') + expect(ssr).toBe(true) + }) + + it('should handle errors during route context hydration', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + mockHead.mockImplementation(() => { + throw notFound() + }) + + const mockMatches = [ + { id: '/', routeId: '/', index: 0, ssr: true, _nonReactive: {} }, + ] + + mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) + mockRouter.state.matches = mockMatches + + mockWindow.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + lastMatchId: '/', + matches: [ + { + i: '/', + l: { data: 'test' }, + s: 'success', + ssr: true, + u: Date.now(), + }, + ], + }, + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(mockRouter) + + const match = mockRouter.state.matches[0] as AnyRouteMatch + expect(match.error).toEqual({ isNotFound: true }) + + expect(consoleSpy).toHaveBeenCalledWith( + 'NotFound error during hydration for routeId: /', + expect.objectContaining({ + isNotFound: true, + }), + ) + + consoleSpy.mockRestore() + }) +}) From 7aa3bb5671597c6345354311d57e2801b7a2a7fd Mon Sep 17 00:00:00 2001 From: Remi <31855291+roduyemi@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:46:58 +0000 Subject: [PATCH 15/18] fix: package import order Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/solid-router/tests/hydrate.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/solid-router/tests/hydrate.test.ts b/packages/solid-router/tests/hydrate.test.ts index 0bcb9dd682d..25199f3e54e 100644 --- a/packages/solid-router/tests/hydrate.test.ts +++ b/packages/solid-router/tests/hydrate.test.ts @@ -1,14 +1,14 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { hydrate } from '@tanstack/router-core/ssr/client' +import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client' import { createMemoryHistory, createRootRoute, createRoute, createRouter, notFound, + type AnyRouteMatch, } from '../src' -import { hydrate } from '@tanstack/router-core/ssr/client' -import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client' -import type { AnyRouteMatch } from '../src' describe('hydrate', () => { let mockWindow: { $_TSR?: TsrSsrGlobal } From c6525e64df9bba9609cbf40ef7f30d3e3bc0dc4b Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Wed, 19 Nov 2025 20:49:10 +0100 Subject: [PATCH 16/18] dont prerender notfound route --- e2e/solid-start/basic/vite.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/solid-start/basic/vite.config.ts b/e2e/solid-start/basic/vite.config.ts index 6af116d625b..7e8d607c532 100644 --- a/e2e/solid-start/basic/vite.config.ts +++ b/e2e/solid-start/basic/vite.config.ts @@ -20,6 +20,7 @@ const prerenderConfiguration = { '/redirect', '/i-do-not-exist', '/not-found/via-beforeLoad', + '/not-found/via-head', '/not-found/via-loader', '/search-params/default', '/transition', From 268b423b4130850fc533bb2b3c90565765e214d3 Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Wed, 19 Nov 2025 20:56:07 +0100 Subject: [PATCH 17/18] lint --- packages/react-router/tests/hydrate.test.ts | 2 +- packages/solid-router/tests/hydrate.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-router/tests/hydrate.test.ts b/packages/react-router/tests/hydrate.test.ts index 0bcb9dd682d..b0d91530554 100644 --- a/packages/react-router/tests/hydrate.test.ts +++ b/packages/react-router/tests/hydrate.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { hydrate } from '@tanstack/router-core/ssr/client' import { createMemoryHistory, createRootRoute, @@ -6,7 +7,6 @@ import { createRouter, notFound, } from '../src' -import { hydrate } from '@tanstack/router-core/ssr/client' import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client' import type { AnyRouteMatch } from '../src' diff --git a/packages/solid-router/tests/hydrate.test.ts b/packages/solid-router/tests/hydrate.test.ts index 25199f3e54e..1199684cfbd 100644 --- a/packages/solid-router/tests/hydrate.test.ts +++ b/packages/solid-router/tests/hydrate.test.ts @@ -1,14 +1,14 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { hydrate } from '@tanstack/router-core/ssr/client' -import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client' import { createMemoryHistory, createRootRoute, createRoute, createRouter, - notFound, - type AnyRouteMatch, + notFound } from '../src' +import type {AnyRouteMatch} from '../src'; +import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client' describe('hydrate', () => { let mockWindow: { $_TSR?: TsrSsrGlobal } From dad79febdba715c1bdb1e30c28daa3c34fe41e10 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:57:16 +0000 Subject: [PATCH 18/18] ci: apply automated fixes --- packages/solid-router/tests/hydrate.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/solid-router/tests/hydrate.test.ts b/packages/solid-router/tests/hydrate.test.ts index 1199684cfbd..d71e29d7e98 100644 --- a/packages/solid-router/tests/hydrate.test.ts +++ b/packages/solid-router/tests/hydrate.test.ts @@ -5,9 +5,9 @@ import { createRootRoute, createRoute, createRouter, - notFound + notFound, } from '../src' -import type {AnyRouteMatch} from '../src'; +import type { AnyRouteMatch } from '../src' import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client' describe('hydrate', () => {