Skip to content

ChatClient streaming stalls when the browser tab is hidden #983

Description

@TheBinaryGuy

TanStack AI version

@tanstack/ai-client 0.22.1

Framework/Library version

React 19.2.7

Describe the bug and the steps to reproduce it

When a response is streaming, switching to another browser tab makes chunk processing almost stop. When I return to the tab, it catches up.

I first saw this with useChat in React. The same problem happens with ChatClient directly, so it does not look related to React.

Steps to reproduce

  1. Open the StackBlitz repro.
  2. Click Run real hidden-tab test.
  3. Immediately switch to another tab.
  4. Wait at least 15 seconds, then return.

In the real hidden-tab test, only 16 chunks were processed during 15.9 seconds in the background. Across the full 2,004-chunk run, the largest gap between chunks was 1,001 ms.

Expected

The client should keep consuming stream chunks while the tab is hidden.

Actual

Chunk processing slows to roughly one chunk per second in the hidden tab, then catches up when the tab becomes visible.

Likely cause

In @tanstack/ai-client 0.22.1, ChatClient.consumeSubscription() waits for this after every chunk:

await new Promise((resolve) => setTimeout(resolve, 0))

On current main, commit 3301398 moved this await into processIncomingChunk(). It is still awaited once per chunk, so the behavior is unchanged.

Browsers throttle timers in hidden tabs. Since this timer is awaited inside the stream loop, it also delays requesting the next chunk.

I tested this local change and it fixed the problem:

if (typeof document === 'undefined' || !document.hidden) {
  await new Promise((resolve) => setTimeout(resolve, 0))
}

typeof document is safe here because typeof does not throw when document is unavailable. Node.js, SSR, and Web Workers therefore keep the existing timer yield. Visible browser tabs also keep the existing behavior. The timer is skipped only when a browser document exists and the page is hidden.

There may be a better scheduling or batching fix for the library.

History

Git history shows that the per-chunk timer was introduced in #61 as part of the chunk processing overhaul.

I can open a PR if that would be helpful.

Your Minimal, Reproducible Example - (Sandbox Highly Recommended)

https://stackblitz.com/edit/chatclient-token-streaming-stalling-demo

Screenshots or Videos (Optional)

No response

Do you intend to try to help solve this bug with your own PR?

Yes, I think I know how to fix it and will discuss it in the comments of this issue

Terms & Code of Conduct

  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions