Skip to content

Live playground ​

All widgets import tanstack-fetch from source and use a mock fetch / stream. Use the Framework picker to show only your stack.

Open in StackBlitz ​

ExampleFrameworkStackBlitzDocs
TanStack QueryReactOpenReact
Next.js SSRReact · Next.jsOpenNext SSR
Nuxt SSRVue · Nuxt—Nuxt SSR
UploadCore demoOpenUpload
SSEReact / adaptersOpenSSE
WebSocket · WebTransport · NDJSONCore demoLive on this page ↓Realtime
tRPCReact / StartOpentRPC recipe
Auth statusCoreOpenRefresh

Status matrix (incl. 429) ​

Framework

Core

Live demo · real createFetch

Trigger HTTP statuses

Mock fetch returns the code you click. Handlers fire before FetchError is thrown — including 429 with Retry-After.

Tap a status — real createFetch + mock fetch

Event log

Handlers and throws show up here.

const api = createFetch({
  interceptors: [
    createStatusInterceptor({
      401: () => logout(),
      429: ({ context }) => {
        const waitMs = parseRetryAfter(context.response?.headers)
        // show toast / backoff…
      },
      '4xx': ({ status }) => console.warn('4xx', status),
    }),
  ],
})

Upload ​

Framework

Core

Live demo · Upload

api.upload + multipart fields

Real api.upload() builds FormData and posts through createFetch from tanstack-fetch/sse. Add onUploadProgress in your app for XHR progress.

No file selected

0%

Pick a file and upload — real api.upload() + mock fetch

await api.upload('/files', {
  file,
  fieldName: 'file',
  fields: { folder: 'avatars', public: true },
  onUploadProgress: ({ progress }) => setProgress(progress),
})

queryFn shape ​

Live demo · queryFn shape

Returns data · throws on error

Click Run — this calls createFetch against a mock API

const api = createFetch({ baseUrl, fetch: mockFetch })

// same shape as TanStack Query queryFn
const users = await api.get<User[]>('/users', {})

React / TanStack Query ​

Framework

React

Live demo · React / TanStack Query

useQuery + useMutation shape

Real createFetch calls — same queryFn / mutationFn contract your React app uses.

idle — click useQuery

const api = createFetch({ baseUrl })

// React Query
useQuery({
  queryKey: ['users'],
  queryFn: ({ signal }) => api.get<User[]>('/users', { signal }),
})

useMutation({
  mutationFn: (body) => api.post<User>('/users', { body }),
})

SSE · React useSse ​

Framework

React · tanstack-fetch/react

Live demo · useSse

React useSse + status

Pass {'{ client: api }'} — no FetchProvider required. Status: connecting → connected → disconnected / error.

status: disconnected

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

const api = createFetch({
  baseUrl: import.meta.env.VITE_API_URL,
  plugins: ['sse-resume'],
  getToken: () => localStorage.getItem('access_token'),
})

const OrdersLive = () => {
  // Pass client — FetchProvider is optional
  const { data, status, error, close } = useSse<Tick>('/orders/stream', {
    client: api,
  })

  if (status === 'error') return <p>{error?.message}</p>

  return (
    <div>
      <p>{status}</p>
      <p>{data ? `#${data.n}` : '—'}</p>
      <button type="button" onClick={close}>Disconnect</button>
    </div>
  )
}

Next.js SSR ​

Framework

React · Next.js

Live demo · Next.js SSR

ssr-forward cookie / auth

Real createFetch with source: 'ssr' + ssr-forward — watch headers get attached (then skipped in browser).

Request log

Run server prefetch to see forwarded Cookie / Authorization.

// app/users/page.tsx (Next.js App Router)
import { cookies } from 'next/headers'
import { createFetch } from 'tanstack-fetch'
import { createSsrForwardInterceptor, createTraceInterceptor } from 'tanstack-fetch/plugins'

const createServerApi = async () =>
  createFetch({
    baseUrl: process.env.API_URL!,
    source: 'ssr',
    interceptors: [createSsrForwardInterceptor(), createTraceInterceptor()],
    incoming: async () => ({
      cookie: (await cookies()).toString(),
    }),
  })

await queryClient.query({
  queryKey: ['users'],
  queryFn: async () => (await createServerApi()).get('/users'),
})

Nuxt / Vue ​

Framework

Vue · Nuxt

Live Nuxt SSR walkthrough: Nuxt SSR example · Vue guide.

Solid ​

Framework

Solid · tanstack-fetch/solid

Browser demos for Solid live in the Solid guide and SSE · Solid. StackBlitz apps are React today — copy the Solid snippets from those pages.

Angular ​

Framework

Angular · tanstack-fetch/angular

See Angular guide and SSE · Angular.

Svelte / SvelteKit ​

Framework

Svelte · tanstack-fetch/svelte

See Svelte guide and SSE · Svelte.

TanStack Start ​

Framework

TanStack Start · Query + Router context

Start + Query patterns: TanStack Query · tRPC recipe.

Remix ​

Framework

Remix

Loader + Query patterns: tRPC · Remix.

SolidStart ​

Framework

SolidStart

See Solid guide and tRPC · SolidStart.

SSE resume (Last-Event-ID) ​

Framework

Core · api.sse · lastEventId

Live demo · SSE resume

Drop the connection. Lose nothing.

Real tanstack-fetch/sse client. The server is simulated in your browser: it reads Last-Event-ID and continues after it. Start the stream, cut the connection mid-way, and watch the ids.

Idle

Received 0 / 24Connections 0Duplicates 0
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
import { createFetch } from 'tanstack-fetch/sse'

const api = createFetch({ baseUrl: '/api' })
let lastEventId: string | undefined

const connect = () =>
  api.sse<Tick>('/events', {
    lastEventId, // sent as the Last-Event-ID header
    onEvent: (event) => {
      lastEventId = event.id ?? lastEventId
      render(event.data)
    },
    // mid-stream drop: reconnect from the last processed id
    onError: () => setTimeout(connect, 1000),
  })

connect()

SSE core stream ​

Framework

Core · api.sse

Live demo · SSE

api.sse over fetch

Real tanstack-fetch/sse against a mock event stream — cookies / Authorization work (unlike EventSource).

Idle — start the stream

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

const api = createFetch({
  plugins: ['sse-resume'],
  getToken: () => localStorage.getItem('access_token'),
})

const OrdersLive = () => {
  const { data, status, error, close } = useSse<Tick>('/orders/stream', {
    client: api,
  })

  if (status === 'error') return <p>{error?.message}</p>
  return (
    <>
      <p>{status} · tick #{data?.n}</p>
      <button onClick={close}>Close</button>
    </>
  )
}

WebSocket ​

Framework

Core · api.ws · tanstack-fetch/live

Live demo · WebSocket

useWs — auth + reconnect

Chat against a mock socket. Send before it opens (queued), then drop the connection: it reconnects and getToken() runs again, so the URL gets a fresh token.

status: disconnected

import { createFetch } from 'tanstack-fetch/live'
import { useWs } from 'tanstack-fetch/react'

const api = createFetch({ baseUrl, getToken })

const ChatRoom = ({ id }: { id: number }) => {
  const { data, status, send, close } = useWs<ChatMessage, { id: number }>('/rooms/:id', {
    client: api,
    params: { id },
    authIn: 'query', // browsers can't send WS headers
    reconnect: { retries: 3, delayMs: 600 },
  })

  return (
    <>
      <p>{status} · {data?.text}</p>
      <button onClick={() => send({ text: 'hi' })}>Send</button>
      <button onClick={close}>Close</button>
    </>
  )
}

WebTransport ​

Framework

Core · api.transport · tanstack-fetch/live

Live demo · WebTransport

useWs({ transport: 'webtransport' })

Game state streams in as unreliable datagrams (newest value wins). Same API as api.ws: send input back with send().

status: disconnected

import { useWs } from 'tanstack-fetch/react'

const Game = () => {
  const { data: state, status, send } = useWs<GameState, { id: number }, Input>('/game/:id', {
    client: api,
    params: { id: 1 },
    authIn: 'query',
    transport: 'webtransport', // HTTP/3 datagrams
  })

  return (
    <Board dot={state} live={status === 'connected'}
      onBoost={() => send({ boost: 1.5 })} />
  )
}

NDJSON stream ​

Framework

Core · api.stream · tanstack-fetch/live

Live demo · NDJSON

api.stream + streamedQuery

A POST with a body and an Authorization header, read line by line. EventSource cannot do either.

status: disconnected

import {
  experimental_streamedQuery as streamedQuery,
  useQuery,
} from '@tanstack/react-query'

const Answer = ({ prompt }: { prompt: string }) => {
  const { data: tokens = [], fetchStatus, refetch } = useQuery({
    queryKey: ['chat', prompt],
    queryFn: streamedQuery({
      streamFn: ({ signal }) =>
        api.stream<Token>('/chat', { body: { prompt }, signal }),
    }),
    enabled: false,
  })

  return (
    <>
      <button onClick={() => refetch()}>Ask</button>
      <p>{tokens.map((t) => t.text).join('')}</p>
      {fetchStatus === 'fetching' && <span>streaming…</span>}
    </>
  )
}

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