diff --git a/.changeset/lucky-windows-shake.md b/.changeset/lucky-windows-shake.md new file mode 100644 index 0000000000..bcd1031e3a --- /dev/null +++ b/.changeset/lucky-windows-shake.md @@ -0,0 +1,5 @@ +--- +'@tanstack/solid-router': patch +--- + +Fix hydration mismatch for `ssr: false` routes with a `pendingComponent`. The suspense fallback is now skipped only while hydrating server-rendered markup, so selective SSR routes hydrate cleanly while client-side navigations keep showing their pending component. diff --git a/e2e/solid-start/selective-ssr/src/routeTree.gen.ts b/e2e/solid-start/selective-ssr/src/routeTree.gen.ts index df2b865e51..8473c00b48 100644 --- a/e2e/solid-start/selective-ssr/src/routeTree.gen.ts +++ b/e2e/solid-start/selective-ssr/src/routeTree.gen.ts @@ -9,11 +9,18 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' +import { Route as SsrFalsePendingComponentRouteImport } from './routes/ssr-false-pending-component' import { Route as PostsRouteImport } from './routes/posts' import { Route as DataOnlyPendingComponentRouteImport } from './routes/data-only-pending-component' import { Route as IndexRouteImport } from './routes/index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +const SsrFalsePendingComponentRoute = + SsrFalsePendingComponentRouteImport.update({ + id: '/ssr-false-pending-component', + path: '/ssr-false-pending-component', + getParentRoute: () => rootRouteImport, + } as any) const PostsRoute = PostsRouteImport.update({ id: '/posts', path: '/posts', @@ -40,12 +47,14 @@ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/data-only-pending-component': typeof DataOnlyPendingComponentRoute '/posts': typeof PostsRouteWithChildren + '/ssr-false-pending-component': typeof SsrFalsePendingComponentRoute '/posts/$postId': typeof PostsPostIdRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/data-only-pending-component': typeof DataOnlyPendingComponentRoute '/posts': typeof PostsRouteWithChildren + '/ssr-false-pending-component': typeof SsrFalsePendingComponentRoute '/posts/$postId': typeof PostsPostIdRoute } export interface FileRoutesById { @@ -53,18 +62,30 @@ export interface FileRoutesById { '/': typeof IndexRoute '/data-only-pending-component': typeof DataOnlyPendingComponentRoute '/posts': typeof PostsRouteWithChildren + '/ssr-false-pending-component': typeof SsrFalsePendingComponentRoute '/posts/$postId': typeof PostsPostIdRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/data-only-pending-component' | '/posts' | '/posts/$postId' + fullPaths: + | '/' + | '/data-only-pending-component' + | '/posts' + | '/ssr-false-pending-component' + | '/posts/$postId' fileRoutesByTo: FileRoutesByTo - to: '/' | '/data-only-pending-component' | '/posts' | '/posts/$postId' + to: + | '/' + | '/data-only-pending-component' + | '/posts' + | '/ssr-false-pending-component' + | '/posts/$postId' id: | '__root__' | '/' | '/data-only-pending-component' | '/posts' + | '/ssr-false-pending-component' | '/posts/$postId' fileRoutesById: FileRoutesById } @@ -72,10 +93,18 @@ export interface RootRouteChildren { IndexRoute: typeof IndexRoute DataOnlyPendingComponentRoute: typeof DataOnlyPendingComponentRoute PostsRoute: typeof PostsRouteWithChildren + SsrFalsePendingComponentRoute: typeof SsrFalsePendingComponentRoute } declare module '@tanstack/solid-router' { interface FileRoutesByPath { + '/ssr-false-pending-component': { + id: '/ssr-false-pending-component' + path: '/ssr-false-pending-component' + fullPath: '/ssr-false-pending-component' + preLoaderRoute: typeof SsrFalsePendingComponentRouteImport + parentRoute: typeof rootRouteImport + } '/posts': { id: '/posts' path: '/posts' @@ -121,6 +150,7 @@ const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, DataOnlyPendingComponentRoute: DataOnlyPendingComponentRoute, PostsRoute: PostsRouteWithChildren, + SsrFalsePendingComponentRoute: SsrFalsePendingComponentRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/e2e/solid-start/selective-ssr/src/routes/ssr-false-pending-component.tsx b/e2e/solid-start/selective-ssr/src/routes/ssr-false-pending-component.tsx new file mode 100644 index 0000000000..f97deeb6ed --- /dev/null +++ b/e2e/solid-start/selective-ssr/src/routes/ssr-false-pending-component.tsx @@ -0,0 +1,31 @@ +import { createFileRoute } from '@tanstack/solid-router' +import { z } from 'zod' +import { ssrSchema } from '~/search' + +export const Route = createFileRoute('/ssr-false-pending-component')({ + validateSearch: z.object({ root: ssrSchema }), + ssr: false, + loader: async () => { + await new Promise((resolve) => setTimeout(resolve, 1500)) + return { loadedAt: new Date().toISOString() } + }, + pendingComponent: () => ( +
+ ), + component: SsrFalsePendingComponentRoute, +}) + +function SsrFalsePendingComponentRoute() { + const data = Route.useLoaderData() + + return ( ++ OK - loader finished +
+
+ {JSON.stringify(data(), null, 2)}
+
+