TL;DR Vercel's resumable-stream library covers one scenario: a full page reload while generation is in progress. Tab switches, mobile backgrounding, and device switches all drop the SSE connection with no way to resume. 40-60% of streaming sessions involve at least one tab switch - none recover automatically. resumable-stream also has a documented incompatibility with stop(). The fix is a session layer where continuity is a property of the channel, not the connection.
resumable-stream ships as part of Vercel's AI SDK infrastructure. It is well-named for what it does: resume a stream after a full page reload. For production applications with more complex continuity requirements, it covers one scenario well and is explicit about the rest - and understanding exactly where it applies is the first step to building streaming that holds up.
What resumable-stream does
resumable-stream is a Redis-backed library that buffers token output server-side. When a client reconnects after a full page reload, it reads from the Redis buffer and replays tokens from where it left off. Generation continues uninterrupted on the server. The client catches up.
This works for the specific case it was designed for: a user reloading the page during a long generation. Vercel published the library specifically to address this scenario. For page reloads, it does what it says.
What resumable-stream doesn't cover
It handles the page-reload case reliably. There are several scenarios it doesn't cover, and the documentation is explicit about them.
Tab switches. When a user switches tabs, the browser may suspend or deprioritize the tab. The SSE connection drops. When the user switches back, there is no resume - the stream is gone. resumable-stream does not handle tab switches. This is explicit in the documentation.
Mobile backgrounding. iOS and Android aggressively terminate background network connections. An AI response in progress when a user switches apps is lost. There is no recovery path.
Device switches. A user who starts a conversation on a laptop and picks up on their phone gets a new session. The in-progress generation is not accessible from the second device. SSE is scoped to a single HTTP request from a single client.
Multiple tabs. A second browser tab opened during an active generation starts an independent session. It has no access to the existing stream.
Network changes. Moving from Wi-Fi to mobile data, or any network transition that drops the connection, loses the stream. There is no reconnection protocol at the SSE level.
The research is direct on the scale of this: 40-60% of streaming sessions involve at least one tab switch or backgrounding event. None of those sessions recover automatically with resumable-stream.
The abort incompatibility
resumable-stream has a second limitation you'll hit if you're using it: it is not compatible with stop().
When a user cancels a generation, resumable-stream treats the abort as a disconnect and attempts to resume. This creates a conflict: the user stopped intentionally; the library tries to continue. Vercel acknowledged this in GitHub issue #8390: "You are correct. Right now, resume and stop are not compatible."
The issue remains open. If you're using resumable-stream, you have to choose between resumability and reliable cancellation - the library cannot provide both.
Why SSE makes this structural
These aren't gaps that a newer version of resumable-stream can close. They follow from how SSE works.
SSE is an HTTP connection from one client to one server. The connection carries no session identity at the protocol level - it is the session. When the connection drops, the session ends. A new connection is a new session.
The Redis buffer in resumable-stream gives the server a way to store output between connections. But it doesn't give the client a way to reconnect to a specific session. The reconnection depends on the client presenting the right session ID to the right server endpoint. That works on reload, where the page logic handles it. On a tab switch or device change, there is no automatic reconnect mechanism - so it fails.
This is why the maintainer pointed to WebSockets as the real solution in issue #6502. WebSockets are persistent and bidirectional. A client that disconnects can reconnect explicitly, request history from a specific offset, and resume. SSE cannot do this by design.
What session continuity actually requires
The property you need is: session continuity as a feature of the session, not the connection. Any client with the right session identifier - same tab, different tab, different device, after a mobile background - should be able to join and catch up.
This requires three things that resumable-stream doesn't provide:
Offset-based history. Every token needs a position. A reconnecting client presents its last known offset; the server replays from there. Without offsets, the client has to receive everything from the start or nothing.
Session fan-out. Multiple clients subscribing to the same session should all receive the same stream. SSE cannot do this - each client gets its own connection and its own session.
Reconnect semantics. The session layer needs to distinguish a reconnecting client from a new client. On reconnect, it replays missed tokens. On new session, it starts fresh.
A channel-based transport provides all three natively. Session continuity is a property of the channel, not the connection. Any client that connects to the same channel - same tab after a switch, different device, monitoring dashboard - gets current state and replays from the offset it last received.
Vercel's ChatTransport interface in AI SDK 5 is the integration point. A channel-based transport plugs in at this point. useChat hooks, application logic, and UI rendering stay exactly as they are:
const { messages } = useChat({
transport: new ChannelBasedTransport({ sessionId })
});The transport manages reconnection, offset tracking, and fan-out. Your application doesn't need to know which device or tab is connected.
What to look for in a transport
When evaluating transports for session continuity, the relevant capabilities:
Offset-based replay. The transport should track message positions and support catch-up from a specific offset on reconnect - not full replay from the start, which re-triggers the agent.
Multi-device fan-out. One session, multiple subscribers. Connected clients get live streaming; reconnecting clients get automatic catch-up.
Abort compatibility. The transport should support explicit cancel signals as typed messages, separate from connection close. This is the incompatibility resumable-stream cannot resolve.
Ably AI Transport integrates with the Vercel AI SDK to add durable sessions, multi-device sync, and bidirectional control to your chat application. Visit the Ably AI Transport overview, read the documentation, or sign up free
Ready to build? Get started with Vercel AI SDK.
Research basis: analysis of 300+ GitHub issues in vercel/ai repository; 31 Vercel Community Forum threads (65% unresolved); 35 Stack Overflow questions (40% unanswered). GitHub issues cited: #11865 (tab switch, stream resumption), #8390 (resume and stop incompatible - acknowledged, unresolved), #6502 (resumable stream can't be stopped), #8477 (resume + onFinish crashes), #6974, #11512 (Expo/React Native). Maintainer quote from Lars Grammel, Vercel. Vercel acknowledgment from Nico Albanese, Vercel (#8390).
Recommended Articles
Why Vercel AI SDK stop() doesn't cancel the stream
stop() in Vercel AI SDK closes the connection, not the generation. The abort signal is a separate HTTP request and may never reach the generating process.
Vercel AI SDK ChatTransport: implementing a custom WebSocket transport
ChatTransport in Vercel AI SDK 5 lets you replace the default HTTP transport with WebSockets. Application code, agents, and UI stay unchanged.
Durable sessions for Vercel AI SDK applications
Vercel AI SDK's SSE transport breaks in production: proxy buffering, no reconnect, serverless limits. ChatTransport makes it swappable. Options compared.