Skip to content

Solid fetch vs tanstack-fetch ​

October 12, 2026 · Solid · TanStack Query · HTTP

Stack

Solid 1.8+ · tanstack-fetch/solid · @tanstack/solid-query

Solid’s fine-grained reactivity loves small primitives. Raw fetch / axios still force you to unwrap bodies and map errors before createQuery.

tanstack-fetch/solid mirrors the React adapters: FetchProvider, useFetch, and useSse that return accessors.

This post is the deep dive: architecture, side-by-side code, graphs, migrate path, SSE, tRPC, and when to keep the default stack client.

TL;DR ​

If you need…Prefer
createQuery without .data unwraptanstack-fetch
SSE accessors (status(), data())useSse
Share client with React monoreposame createFetch
Tiny app, no Querynative fetch is fine
bash
npm install tanstack-fetch solid-js @tanstack/solid-query

Mental model ​

Solid Query’s queryFn is still Promise + AbortSignal. Accessors (query.data) are separate from the HTTP client contract.

Contract that matters for Query:

  1. Success → return the parsed body
  2. HTTP failure → throw (so isError works)
  3. Cancel → pass Query’s signal through

Side-by-side ​

axios + Solid Query (glue) ​

tsx
import axios from 'axios'
import { createQuery } from '@tanstack/solid-query'

const query = createQuery(() => ({
  queryKey: ['users'],
  queryFn: async ({ signal }) => (await axios.get<User[]>('/users', { signal })).data,
}))

tanstack-fetch + Solid Query ​

tsx
import { createFetch } from 'tanstack-fetch'
import { FetchProvider, useFetch } from 'tanstack-fetch/solid'
import { createQuery } from '@tanstack/solid-query'
import { isFetchError } from 'tanstack-fetch'

const api = createFetch({
  baseUrl: import.meta.env.VITE_API_URL,
  getToken: () => localStorage.getItem('access_token'),
})

const App = () => (
  <FetchProvider client={api}>
    <UsersPage />
  </FetchProvider>
)

const UsersPage = () => {
  const client = useFetch()
  const query = createQuery(() => ({
    queryKey: ['users'],
    queryFn: ({ signal }) => client.get<User[]>('/users', { signal }),
  }))

  if (query.isPending) return <p>Loading…</p>
  if (isFetchError(query.error)) return <p>{query.error.status}</p>
  return <ul>{query.data?.map((u) => <li>{u.name}</li>)}</ul>
}

Bootstrap / wiring ​

SolidStart: use absolute URL + ssr-forward on the server like other SSR guides. See tRPC · SolidStart.

Comparison graph: where glue accumulates ​

Rough relative boilerplate (lower is better) — a map of where teams spend time, not a micro-benchmark.

Feature matrix ​

Capabilitytanstack-fetchaxios / fetch
Solid Query fitNativeAdapt
FetchProvider / useFetchYesDIY
useSse accessorsYesDIY
Typed errorsFetchErrorDIY
Refresh pluginYesDIY
Path paramsYesManual
Bundle~4.8KB gzipVaries

Auth, status handlers, refresh ​

ts
import { createFetch } from 'tanstack-fetch'
import { createRefreshTokenInterceptor } from 'tanstack-fetch/plugins'

let expiresAt = 0

const api = createFetch({
  baseUrl: '/api',
  getToken: () => localStorage.getItem('access_token'),
})

api.use(
  'refresh-token',
  createRefreshTokenInterceptor({
    refresh: async () => {
      const body = await api.post<{ accessToken: string; expiresIn: number }>('/auth/refresh', {
        interceptors: { eject: ['refresh-token', 'auth'] },
      })
      localStorage.setItem('access_token', body.accessToken)
      expiresAt = Date.now() + body.expiresIn * 1000
    },
    before: { getExpiresAt: () => expiresAt, skewMs: 60_000 },
    after: { enabled: true },
  }),
)

The plugin keeps one in-flight refresh and retries the failed call — the usual interceptor queue, without the queue code.

Mutations ​

tsx
import { createMutation, useQueryClient } from '@tanstack/solid-query'
import { useFetch } from 'tanstack-fetch/solid'

const CreateUser = () => {
  const api = useFetch()
  const queryClient = useQueryClient()
  const mutation = createMutation(() => ({
    mutationFn: (input: CreateUserInput) => api.post<User>('/users', { body: input }),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
  }))
  return <button onClick={() => mutation.mutate({ name: 'Ada' })}>Create</button>
}

Live data: SSE ​

Browser EventSource cannot set Authorization. tanstack-fetch/sse streams over fetch.

tsx
import { createFetch } from 'tanstack-fetch/sse'
import { useSse } from 'tanstack-fetch/solid'

const api = createFetch({ plugins: ['sse-resume'] })

const OrdersLive = () => {
  const { data, status, error, close } = useSse<OrderEvent>('/orders/stream', { client: api })
  if (status() === 'error') return <p>{error()?.message}</p>
  return (
    <div>
      <p>{status()}</p>
      <pre>{JSON.stringify(data(), null, 2)}</pre>
      <button type="button" onClick={close}>Stop</button>
    </div>
  )
}

Status: connecting | connected | disconnected | error.

tRPC over the same client ​

ts
import { createTRPCClient, httpBatchLink } from '@trpc/client'
import { createTRPCFetch } from 'tanstack-fetch/trpc'
import { createQuery } from '@tanstack/solid-query'
import { api } from './api'
import type { AppRouter } from './server'

const trpc = createTRPCClient<AppRouter>({
  links: [httpBatchLink({ url: '/api/trpc', fetch: createTRPCFetch(api) })],
})

const posts = createQuery(() => ({
  queryKey: ['posts'],
  queryFn: ({ signal }) => trpc.post.list.query(undefined, { signal }),
}))

Migrate ​

bash
npx tanstack-fetch migrate --from axios --framework solid --write
npx tanstack-fetch doctor --dir ./src

Suggested folder layout ​

text
src/
  lib/api.ts
  app.tsx            # FetchProvider + QueryClientProvider
  queries/users.ts
  components/UsersPage.tsx

Decision tree ​

  1. Is TanStack Query your source of truth for server state?
    • Yes → start with tanstack-fetch
    • No → axios/fetch may stay
  2. Do you need SSE with Authorization or resume?
    • Yes → tanstack-fetch/sse + useSse
    • No → either client works for simple REST
  3. Do other frameworks share this backend client?
    • Yes → one createFetch across packages
    • No → still fine; adapters are optional

Checklist ​

  • [ ] FetchProvider or shared api
  • [ ] Forward signal
  • [ ] isFetchError for status
  • [ ] SSE accessors via useSse
  • [ ] Doctor clean

Try it ​

bash
npm install tanstack-fetch solid-js @tanstack/solid-query
npx tanstack-fetch doctor --dir ./src

Series ​

Same deep-dive format for every stack:

MIT Licensed · Created by Mohammad Garmabi · Not an official TanStack package