diff --git a/.changeset/tangy-falcons-hang.md b/.changeset/tangy-falcons-hang.md
new file mode 100644
index 0000000000..d747b9e9ea
--- /dev/null
+++ b/.changeset/tangy-falcons-hang.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/solid-router': patch
+---
+
+Fix hydration mismatch for ssr='data-only' with pendingComponent
diff --git a/e2e/e2e-utils/src/fixture.ts b/e2e/e2e-utils/src/fixture.ts
index aab4a943b1..59bc140c10 100644
--- a/e2e/e2e-utils/src/fixture.ts
+++ b/e2e/e2e-utils/src/fixture.ts
@@ -1,4 +1,5 @@
 import { test as base, expect } from '@playwright/test'
+import type { Page } from '@playwright/test'
 
 export interface TestFixtureOptions {
   /**
@@ -46,3 +47,25 @@ export const test = base.extend<TestFixtureOptions>({
     expect(errorMessages).toEqual([])
   },
 })
+
+export function collectBrowserErrors(page: Page): Array<string> {
+  const browserErrors: Array<string> = []
+
+  page.on('pageerror', (error) => {
+    browserErrors.push(error.message)
+  })
+
+  page.on('console', (message) => {
+    if (message.type() === 'error' || message.type() === 'warning') {
+      browserErrors.push(message.text())
+    }
+  })
+
+  page.on('requestfailed', (request) => {
+    browserErrors.push(
+      `${request.url()} ${request.failure()?.errorText ?? 'failed'}`,
+    )
+  })
+
+  return browserErrors
+}
diff --git a/e2e/e2e-utils/src/index.ts b/e2e/e2e-utils/src/index.ts
index f1c489c871..ec6affdb56 100644
--- a/e2e/e2e-utils/src/index.ts
+++ b/e2e/e2e-utils/src/index.ts
@@ -5,4 +5,4 @@ export { resolveRuntimeSuffix } from './resolve-runtime-suffix'
 export { e2eStartDummyServer, e2eStopDummyServer } from './e2eSetupTeardown'
 export { preOptimizeDevServer, waitForServer } from './devServerWarmup'
 export type { Post } from './posts'
-export { test } from './fixture'
+export { collectBrowserErrors, test } from './fixture'
diff --git a/e2e/solid-start/selective-ssr/src/routeTree.gen.ts b/e2e/solid-start/selective-ssr/src/routeTree.gen.ts
index 423535df71..df2b865e51 100644
--- a/e2e/solid-start/selective-ssr/src/routeTree.gen.ts
+++ b/e2e/solid-start/selective-ssr/src/routeTree.gen.ts
@@ -10,6 +10,7 @@
 
 import { Route as rootRouteImport } from './routes/__root'
 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'
 
@@ -18,6 +19,12 @@ const PostsRoute = PostsRouteImport.update({
   path: '/posts',
   getParentRoute: () => rootRouteImport,
 } as any)
+const DataOnlyPendingComponentRoute =
+  DataOnlyPendingComponentRouteImport.update({
+    id: '/data-only-pending-component',
+    path: '/data-only-pending-component',
+    getParentRoute: () => rootRouteImport,
+  } as any)
 const IndexRoute = IndexRouteImport.update({
   id: '/',
   path: '/',
@@ -31,30 +38,39 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({
 
 export interface FileRoutesByFullPath {
   '/': typeof IndexRoute
+  '/data-only-pending-component': typeof DataOnlyPendingComponentRoute
   '/posts': typeof PostsRouteWithChildren
   '/posts/$postId': typeof PostsPostIdRoute
 }
 export interface FileRoutesByTo {
   '/': typeof IndexRoute
+  '/data-only-pending-component': typeof DataOnlyPendingComponentRoute
   '/posts': typeof PostsRouteWithChildren
   '/posts/$postId': typeof PostsPostIdRoute
 }
 export interface FileRoutesById {
   __root__: typeof rootRouteImport
   '/': typeof IndexRoute
+  '/data-only-pending-component': typeof DataOnlyPendingComponentRoute
   '/posts': typeof PostsRouteWithChildren
   '/posts/$postId': typeof PostsPostIdRoute
 }
 export interface FileRouteTypes {
   fileRoutesByFullPath: FileRoutesByFullPath
-  fullPaths: '/' | '/posts' | '/posts/$postId'
+  fullPaths: '/' | '/data-only-pending-component' | '/posts' | '/posts/$postId'
   fileRoutesByTo: FileRoutesByTo
-  to: '/' | '/posts' | '/posts/$postId'
-  id: '__root__' | '/' | '/posts' | '/posts/$postId'
+  to: '/' | '/data-only-pending-component' | '/posts' | '/posts/$postId'
+  id:
+    | '__root__'
+    | '/'
+    | '/data-only-pending-component'
+    | '/posts'
+    | '/posts/$postId'
   fileRoutesById: FileRoutesById
 }
 export interface RootRouteChildren {
   IndexRoute: typeof IndexRoute
+  DataOnlyPendingComponentRoute: typeof DataOnlyPendingComponentRoute
   PostsRoute: typeof PostsRouteWithChildren
 }
 
@@ -67,6 +83,13 @@ declare module '@tanstack/solid-router' {
       preLoaderRoute: typeof PostsRouteImport
       parentRoute: typeof rootRouteImport
     }
+    '/data-only-pending-component': {
+      id: '/data-only-pending-component'
+      path: '/data-only-pending-component'
+      fullPath: '/data-only-pending-component'
+      preLoaderRoute: typeof DataOnlyPendingComponentRouteImport
+      parentRoute: typeof rootRouteImport
+    }
     '/': {
       id: '/'
       path: '/'
@@ -96,6 +119,7 @@ const PostsRouteWithChildren = PostsRoute._addFileChildren(PostsRouteChildren)
 
 const rootRouteChildren: RootRouteChildren = {
   IndexRoute: IndexRoute,
+  DataOnlyPendingComponentRoute: DataOnlyPendingComponentRoute,
   PostsRoute: PostsRouteWithChildren,
 }
 export const routeTree = rootRouteImport
diff --git a/e2e/solid-start/selective-ssr/src/routes/data-only-pending-component.tsx b/e2e/solid-start/selective-ssr/src/routes/data-only-pending-component.tsx
new file mode 100644
index 0000000000..7368b1c8f8
--- /dev/null
+++ b/e2e/solid-start/selective-ssr/src/routes/data-only-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('/data-only-pending-component')({
+  validateSearch: z.object({ root: ssrSchema }),
+  ssr: 'data-only',
+  loader: async () => {
+    await new Promise((resolve) => setTimeout(resolve, 1500))
+    return { loadedAt: new Date().toISOString() }
+  },
+  pendingComponent: () => (
+    <div data-testid="data-only-pending-component-pending" />
+  ),
+  component: DataOnlyPendingComponentRoute,
+})
+
+function DataOnlyPendingComponentRoute() {
+  const data = Route.useLoaderData()
+
+  return (
+    <div data-testid="data-only-pending-component-ready">
+      <p data-testid="data-only-pending-component-ready-label">
+        OK - loader finished
+      </p>
+      <pre data-testid="data-only-pending-component-ready-data">
+        {JSON.stringify(data(), null, 2)}
+      </pre>
+    </div>
+  )
+}
diff --git a/e2e/solid-start/selective-ssr/tests/pending-component-hydration.spec.ts b/e2e/solid-start/selective-ssr/tests/pending-component-hydration.spec.ts
new file mode 100644
index 0000000000..5d1055a570
--- /dev/null
+++ b/e2e/solid-start/selective-ssr/tests/pending-component-hydration.spec.ts
@@ -0,0 +1,30 @@
+import { expect } from '@playwright/test'
+import { collectBrowserErrors, test } from '@tanstack/router-e2e-utils'
+
+test.describe('pending component hydration', () => {
+  test('data-only route hydrates from pending element to loaded state on first load', async ({
+    page,
+  }) => {
+    const browserErrors = collectBrowserErrors(page)
+
+    await page.goto('/data-only-pending-component')
+
+    await expect(page).toHaveURL(/data-only-pending-component/)
+
+    await expect(
+      page.getByTestId('data-only-pending-component-pending'),
+    ).toBeAttached()
+
+    await expect(
+      page.getByTestId('data-only-pending-component-ready-label'),
+    ).toHaveText('OK - loader finished', { timeout: 10_000 })
+
+    expect(browserErrors).toEqual([])
+    expect(browserErrors).not.toContainEqual(
+      expect.stringContaining('template is not a function'),
+    )
+    expect(browserErrors).not.toContainEqual(
+      expect.stringContaining('Hydration Mismatch'),
+    )
+  })
+})
diff --git a/packages/solid-router/src/Match.tsx b/packages/solid-router/src/Match.tsx
index 294d64e0ca..c6daf2d2fe 100644
--- a/packages/solid-router/src/Match.tsx
+++ b/packages/solid-router/src/Match.tsx
@@ -86,6 +86,11 @@ export const Match = (props: { matchId: string }) => {
           currentMatchState().ssr === false ||
           currentMatchState().ssr === 'data-only'
 
+        const shouldSkipSuspenseFallback =
+          (isServer ?? router.isServer)
+            ? resolvedNoSsr
+            : currentMatchState().ssr === 'data-only'
+
         const ResolvedSuspenseBoundary = () => Solid.Suspense
 
         const ResolvedCatchBoundary = () =>
@@ -105,8 +110,9 @@ export const Match = (props: { matchId: string }) => {
               <Dynamic
                 component={ResolvedSuspenseBoundary()}
                 fallback={
-                  // Don't show fallback on server when using no-ssr mode to avoid hydration mismatch
-                  (isServer ?? router.isServer) && resolvedNoSsr ? undefined : (
+                  // Data-only SSR renders the inner fallback on the server, so
+                  // avoid adding an extra suspense fallback on the client.
+                  shouldSkipSuspenseFallback ? undefined : (
                     <Dynamic component={resolvePendingComponent()} />
                   )
                 }
