Skip to content

SSE ​

tanstack-fetch/sse is framework-agnostic. Optional useSse helpers live in tanstack-fetch/react, /vue, /solid, /angular, and /svelte.

Need two-way messages or NDJSON? See WebSocket, WebTransport & streams (tanstack-fetch/live, which also includes api.sse).

EventSource cannot set Authorization. Use tanstack-fetch/sse (api.sse) for authenticated streams — see /recipes/eventsource-auth-headers.

Use the Framework picker in the header to show one stack at a time.

Core — api.sse ​

Framework

Core (framework-agnostic) · tanstack-fetch/sse

Live demo ​

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>
    </>
  )
}
ts
import { createFetch } from 'tanstack-fetch/sse'

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

Simple handlers ​

ts
const stream = api.sse<OrderEvent>('/orders/stream', {
  onMessage: (data) => console.log(data),
})

stream.close()

Async iteration ​

ts
for await (const event of api.sse<OrderEvent>('/orders/stream', { signal })) {
  console.log(event.data)
}

TanStack Start ​

Framework

TanStack Start · tanstack-fetch/react · browser-only stream

SSE is a client concern. Open the stream in a route component (or a child that only mounts in the browser). Prefer router context for the shared api / Query client; keep useSse in the component.

tsx
import { createFileRoute } from '@tanstack/react-router'
import { createFetch } from 'tanstack-fetch/sse'
import { useSse } from 'tanstack-fetch/react'

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

export const Route = createFileRoute('/orders/live')({
  component: OrdersLive,
})

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

Remix ​

Framework

Remix · Client Component / browser-only

Do not open SSE inside a loader. Use a client module (or ClientOnly) with useSse from tanstack-fetch/react — same as React.

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

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

export const OrdersLive = () => {
  const { data, status, close } = useSse<OrderEvent>('/orders/stream', { client: api })
  return (
    <div>
      <p>{status}</p>
      <button type="button" onClick={close}>
        Stop
      </button>
    </div>
  )
}

SolidStart ​

Framework

SolidStart · tanstack-fetch/solid · browser-only

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

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

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

Keep the stream out of server entry / SSR load functions.

React useSse ​

Framework

React · tanstack-fetch/react · Next.js Client Components

Live demo ​

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>
  )
}
tsx
import { createFetch } from 'tanstack-fetch/sse'
import { useSse } from 'tanstack-fetch/react'

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

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

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

  return (
    <div>
      <p>{status}</p>
      <p>{data?.id}</p>
      <button type="button" onClick={close}>
        Stop
      </button>
    </div>
  )
}

Example: examples/sse-live · React guide

Vue / Nuxt useSse ​

Framework

Vue 3 / Nuxt · tanstack-fetch/vue

vue
<script setup lang="ts">
import { createFetch } from 'tanstack-fetch/sse'
import { useSse } from 'tanstack-fetch/vue'

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

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

<template>
  <p v-if="status === 'error'">{{ error?.message }}</p>
  <div v-else>
    <p>{{ status }}</p>
    <p>{{ data?.id }}</p>
    <button type="button" @click="close">Stop</button>
  </div>
</template>

Guide: Vue & Nuxt

Next.js ​

Framework

React · Next.js App Router

Use useSse in a Client Component ('use client').

tsx
'use client'

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

const api = createFetch({
  baseUrl: process.env.NEXT_PUBLIC_API_URL,
  plugins: ['sse-resume'],
})

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

Solid ​

Framework

Solid · tanstack-fetch/solid

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>
  )
}

Guide: Solid

Angular ​

Framework

Angular · tanstack-fetch/angular

ts
import { Component } from '@angular/core'
import { createFetch } from 'tanstack-fetch/sse'
import { useSse } from 'tanstack-fetch/angular'

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

@Component({
  selector: 'app-orders-live',
  template: `
    <p>{{ status() }}</p>
    <pre>{{ data() | json }}</pre>
    <button type="button" (click)="close()">Stop</button>
  `,
})
export class OrdersLiveComponent {
  private readonly stream = useSse<OrderEvent>('/orders/stream', { client: api })
  readonly data = this.stream.data
  readonly status = this.stream.status
  readonly close = this.stream.close
}

Guide: Angular

Svelte / SvelteKit ​

Framework

Svelte / SvelteKit · tanstack-fetch/svelte

svelte
<script lang="ts">
  import { createFetch } from 'tanstack-fetch/sse'
  import { useSse } from 'tanstack-fetch/svelte'

  const api = createFetch({ plugins: ['sse-resume'] })
  const { data, status, error, close } = useSse<OrderEvent>('/orders/stream', {
    client: api,
  })
</script>

{#if $status === 'error'}
  <p>{$error?.message}</p>
{:else}
  <p>{$status}</p>
  <pre>{JSON.stringify($data, null, 2)}</pre>
  <button type="button" on:click={close}>Stop</button>
{/if}

Keep streams in browser components. Guide: Svelte

Why not EventSource for auth ​

EventSource cannot set custom headers (Authorization). Use tanstack-fetch/sse (api.sse) instead. See /recipes/eventsource-auth-headers (problem tutorial) and /guide/plugins (sse-resume).

Cache updates with Query ​

Inside onMessage, update Query state directly so the stream feeds your UI without a second fetch:

ts
const stream = api.sse<OrderEvent>('/orders/stream', {
  onMessage: (data) => queryClient.setQueryData(['orders'], (prev) => ...),
})

Link to /guide/tanstack-query for full useQuery / useMutation wiring.

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