Repository: TanStack/router (2025-08-28T13:30:16Z)

Issue:
Wrap and InnerWrap not properly wrapping router components (@tanstack/solid-start)
### Which project does this relate to?

Start

### Describe the bug

The `Wrap` and `InnerWrap` components (from `createTanstackRouter`) aren't, well, wrapping the app. In my particular case, this means the `QueryClientProvider` context is not able to be accessed via `useQueryClient`, which is how I noticed it. You can see in the below screenshot that the two components get injected as siblings to the main component tree, rather than a part of. Potentially an SSR/hydration issue?

A simple workaround is to just move `QueryClientProvider` to a route component and pass `queryClient` there.

### Your Example Website or App

https://codesandbox.io/p/devbox/serverless-platform-3fyfd3?file=%2Fsrc%2Froutes%2F__root.tsx%3A11%2C1&workspaceId=ws_Kd4jkuu9beA8EHP9C8SzFs

### Steps to Reproduce the Bug or Issue

Setup a router like the following:

```tsx
// src/router.tsx
import { createTanstackRouter } from '@tanstack/solid-router';
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';

export function createRouter() {
  const queryClient = new QueryClient();

  const router = createTanstackRouter({
    routeTree,
    context: { queryClient },
    defaultPreload: "intent",
    defaultPreloadStaleTime: 0,
    dehydrate: () => ({
      queryClientState: dehydrate(queryClient),
    }),
    hydrate: (dehydrated) => {
      hydrate(queryClient, dehydrated.queryClientState);
    },
    // Issue is here. Also applies to `InnerWrap`
    Wrap: (props) => (
      <QueryClientProvider client={queryClient}>
        {props.children}
      </QueryClientProvider>
    ),
  });

  return router;
}

// src/routes/index.tsx
export const Route = createFileRoute("/")({
  component: RouteComponent,
});

function RouteComponent() {
  // Results in runtime error:
  // `No QueryClient set, use QueryClientProvider to set one`
  const queryClient = useQueryClient();
  ///          ^ undefined
  
  // Rest of component...
}
```

### Expected behavior

`Wrap` and/or `InnerWrap` should wrap the router's child components.

### Screenshots or Videos

You can see that the `Wrap` and `InnerWrap` components exist but aren't in the correct position in the component hierarchy.

![Image](https://github.com/user-attachments/assets/6d2da571-766c-4f4c-9f3b-61a35ac9d13a)
*the main portion of the component tree is folded under `CatchBoundary`*

### Platform

- OS: macOS
- Browser: Chrome
- Version: https://pkg.pr.new/TanStack/router/@tanstack/solid-router@3696


### Additional context

### Workaround
Very simple workaround, just move the `QueryClientProvider` into the a route component ie. `RootComponent` and pass the `queryClient` in there, either via import or via router context.

```tsx
// src/routes/__root.tsx

// via import
// import { queryClient } from '../router'

// Typical root route setup
// ...

function RootComponent() {
  // or via context
  const queryClient = Route.useRouteContext({ select: (c) => c.queryClient });

  return (
    <>
        <QueryClientProvider client={queryClient()}>
          <Outlet />
        </QueryClientProvider>
    </>
  )
}
```


Discussion:
Ran into the same issue. Created PR with a fix: #5033



Patch:
diff --git a/packages/solid-router/src/Matches.tsx b/packages/solid-router/src/Matches.tsx
index cf83c6b5060..f9fa1d34f7a 100644
--- a/packages/solid-router/src/Matches.tsx
+++ b/packages/solid-router/src/Matches.tsx
@@ -38,27 +38,27 @@ declare module '@tanstack/router-core' {
 export function Matches() {
   const router = useRouter()
 
-  const pendingElement = router.options.defaultPendingComponent ? (
-    <router.options.defaultPendingComponent />
-  ) : null
-
   // Do not render a root Suspense during SSR or hydrating from SSR
   const ResolvedSuspense =
     router.isServer || (typeof document !== 'undefined' && router.ssr)
       ? SafeFragment
       : Solid.Suspense
 
-  const inner = (
-    <ResolvedSuspense fallback={pendingElement}>
-      {!router.isServer && <Transitioner />}
-      <MatchesInner />
-    </ResolvedSuspense>
-  )
+  const OptionalWrapper = router.options.InnerWrap || SafeFragment
 
-  return router.options.InnerWrap ? (
-    <router.options.InnerWrap>{inner}</router.options.InnerWrap>
-  ) : (
-    inner
+  return (
+    <OptionalWrapper>
+      <ResolvedSuspense
+        fallback={
+          router.options.defaultPendingComponent ? (
+            <router.options.defaultPendingComponent />
+          ) : null
+        }
+      >
+        {!router.isServer && <Transitioner />}
+        <MatchesInner />
+      </ResolvedSuspense>
+    </OptionalWrapper>
   )
 }
 
@@ -74,8 +74,10 @@ function MatchesInner() {
     select: (s) => s.loadedAt,
   })
 
-  const matchComponent = () =>
-    matchId() ? <Match matchId={matchId()!} /> : null
+  const matchComponent = () => {
+    const id = matchId()
+    return id ? <Match matchId={id} /> : null
+  }
 
   return (
     <matchContext.Provider value={matchId}>
diff --git a/packages/solid-router/src/RouterProvider.tsx b/packages/solid-router/src/RouterProvider.tsx
index 1f579a0a4f3..7fee86bcf79 100644
--- a/packages/solid-router/src/RouterProvider.tsx
+++ b/packages/solid-router/src/RouterProvider.tsx
@@ -1,5 +1,6 @@
 import { Matches } from './Matches'
 import { getRouterContext } from './routerContext'
+import { SafeFragment } from './SafeFragment'
 import type * as Solid from 'solid-js'
 import type {
   AnyRouter,
@@ -29,17 +30,15 @@ export function RouterContextProvider<
 
   const routerContext = getRouterContext()
 
-  const provider = (
-    <routerContext.Provider value={router as AnyRouter}>
-      {children()}
-    </routerContext.Provider>
-  )
-
-  if (router.options.Wrap) {
-    return <router.options.Wrap>{provider}</router.options.Wrap>
-  }
+  const OptionalWrapper = router.options.Wrap || SafeFragment
 
-  return provider
+  return (
+    <OptionalWrapper>
+      <routerContext.Provider value={router as AnyRouter}>
+        {children()}
+      </routerContext.Provider>
+    </OptionalWrapper>
+  )
 }
 
 export function RouterProvider<


Test patch:
diff --git a/packages/solid-router/tests/Matches.test.tsx b/packages/solid-router/tests/Matches.test.tsx
index b7242fed007..6f0f865a1bd 100644
--- a/packages/solid-router/tests/Matches.test.tsx
+++ b/packages/solid-router/tests/Matches.test.tsx
@@ -1,15 +1,18 @@
 import { expect, test } from 'vitest'
 import { fireEvent, render, screen } from '@solidjs/testing-library'
+import { createContext, useContext } from 'solid-js'
 import {
   Link,
   Outlet,
   RouterProvider,
+  createMemoryHistory,
   createRootRoute,
   createRoute,
   createRouter,
   isMatch,
   useMatches,
 } from '../src'
+import { sleep } from './utils'
 
 const rootRoute = createRootRoute()
 
@@ -122,3 +125,99 @@ test('when filtering useMatches by loaderData', async () => {
 
   expect(await screen.findByText('Incorrect Matches -')).toBeInTheDocument()
 })
+
+test('Matches provides InnerWrap context to route components', async () => {
+  const rootRoute = createRootRoute({
+    component: () => {
+      const contextValue = useContext(ctx)
+      expect(contextValue, 'Context is not provided').not.toBeUndefined()
+
+      return <div>{contextValue}</div>
+    },
+  })
+
+  const routeTree = rootRoute.addChildren([])
+  const router = createRouter({
+    routeTree,
+  })
+
+  const ctx = createContext<string>()
+
+  const app = render(() => (
+    <RouterProvider
+      router={router}
+      InnerWrap={(props) => {
+        return (
+          <ctx.Provider value={'context-for-children'}>
+            {props.children}
+          </ctx.Provider>
+        )
+      }}
+    />
+  ))
+
+  const indexElem = await app.findByText('context-for-children')
+  expect(indexElem).toBeInTheDocument()
+})
+
+test('Matches provides InnerWrap context to defaultPendingComponent', async () => {
+  const rootRoute = createRootRoute({})
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: () => {
+      return (
+        <div>
+          <Link to="/home">link to home</Link>
+        </div>
+      )
+    },
+  })
+
+  const homeRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/home',
+    loader: () => sleep(300),
+    component: () => <div>Home page</div>,
+  })
+
+  const routeTree = rootRoute.addChildren([homeRoute, indexRoute])
+  const router = createRouter({
+    routeTree,
+    history: createMemoryHistory({
+      initialEntries: ['/'],
+    }),
+  })
+
+  const ctx = createContext<string>()
+
+  const app = render(() => (
+    <RouterProvider
+      router={router}
+      defaultPendingMs={200}
+      defaultPendingComponent={() => {
+        const contextValue = useContext(ctx)
+        expect(contextValue, 'Context is not provided').not.toBeUndefined()
+
+        return <div>{contextValue}</div>
+      }}
+      InnerWrap={(props) => {
+        return (
+          <ctx.Provider value={'context-for-default-pending'}>
+            {props.children}
+          </ctx.Provider>
+        )
+      }}
+    />
+  ))
+
+  const linkToHome = await app.findByRole('link', {
+    name: 'link to home',
+  })
+  expect(linkToHome).toBeInTheDocument()
+
+  fireEvent.click(linkToHome)
+
+  const indexElem = await app.findByText('context-for-default-pending')
+  expect(indexElem).toBeInTheDocument()
+})
diff --git a/packages/solid-router/tests/RouterProvider.test.tsx b/packages/solid-router/tests/RouterProvider.test.tsx
new file mode 100644
index 00000000000..2bc33955151
--- /dev/null
+++ b/packages/solid-router/tests/RouterProvider.test.tsx
@@ -0,0 +1,37 @@
+import { describe, expect, it } from 'vitest'
+import { render } from '@solidjs/testing-library'
+import { createContext, useContext } from 'solid-js'
+import { createRootRoute, createRouter } from '../src'
+import { RouterProvider } from '../src/RouterProvider'
+
+describe('RouterProvider', () => {
+  it('should provide context through RouterProvider Wrap', async () => {
+    const ctx = createContext<string>()
+
+    const rootRoute = createRootRoute({
+      component: () => {
+        const contextValue = useContext(ctx)
+        expect(contextValue, 'Context is not provided').not.toBeUndefined()
+
+        return <div>{contextValue}</div>
+      },
+    })
+
+    const routeTree = rootRoute.addChildren([])
+    const router = createRouter({
+      routeTree,
+    })
+
+    const app = render(() => (
+      <RouterProvider
+        router={router}
+        Wrap={(props) => {
+          return <ctx.Provider value={'findMe'}>{props.children}</ctx.Provider>
+        }}
+      />
+    ))
+
+    const indexElem = await app.findByText('findMe')
+    expect(indexElem).toBeInTheDocument()
+  })
+})