Turbo Drive fallback limits for silent request failures and client state
0 reputation · 13 Jul 2025, 15:51 UTC
0 reputation · 13 Jul 2025, 15:51 UTC
Turbo Drive's documented fallback mechanism converts failed Ajax visits into full page loads, but the exact boundary between recoverable and unrecoverable failures remains unclear when requests neither succeed nor explicitly reject. The handbook describes fallback for non-2xx responses and aborted requests, yet the behavior for requests that hang without a response is less defined, since browsers may abort fetches differently across engines.
This uncertainty matters for applications that maintain significant client-side state outside the DOM. Because the fallback performs a traditional navigation, any JavaScript-managed state is discarded unless separately persisted. Teams must therefore decide whether to rely on the automatic fallback as a safety net or to intercept failures earlier, but the practical limits of that safety net are not fully documented.
Does Turbo Drive's fallback trigger for requests that time out without a network error or HTTP error response? Is there a documented way to preserve client-side state across the automatic full-page reload, or is persistence outside the Turbo session the only supported approach? Which Turbo versions guarantee the fallback behavior for aborted requests versus silent timeouts?
34775 reputation · 13 Jul 2025, 17:51 UTC
1. Does fallback trigger for requests that time out without a network error or HTTP error? No. Turbo Drive does not configure a fetch timeout. The Fetch API has no built-in timeout, so a request that hangs without a network-level error or HTTP error response remains pending indefinitely and will not trigger the automatic fallback. Fallback only occurs when the fetch promise rejects — for network errors, CORS failures, or explicit AbortController aborts.
2. Is there a documented way to preserve client-side state across the automatic full-page reload? No. A fallback navigation is a standard browser navigation. Turbo does not serialize or restore in-memory JavaScript state across that reload. Any state held outside the DOM (React/Vue component state, Redux stores, in-memory caches, WebSocket connections) is discarded. Persistence must be implemented outside Turbo — typically via sessionStorage, localStorage, cookies, server-side session, or URL query parameters — with explicit restore logic on page load.
3. Which Turbo versions guarantee fallback for aborted requests versus silent timeouts? Fallback for explicitly aborted requests (via AbortController or browser navigation cancellation) has been stable since Turbo 7.x and continues in Turbo 8.x with the default fetch adapter. Silent timeouts have never triggered fallback in any Turbo version because core Turbo does not add a timeout; any timeout-based fallback requires a custom fetch wrapper.
The fallback mechanism is implemented in the visit driver and catches fetch promise rejections. It does not monitor elapsed time. A request that receives no response bytes and no TCP reset simply leaves the promise unsettled. Browser engines differ in when (or whether) they abort a stalled connection, but Turbo does not impose its own deadline. Therefore the practical boundary between recoverable and unrecoverable failures is fetch rejection, not elapsed time.
Turbo.fetchRequestMethod (or your fetch adapter) so that stalled requests reject with an AbortError, which will then trigger the documented fallback path.sessionStorage (for tab-scoped state) or localStorage (for cross-tab state) rather than relying on a fallback safety net.turbo:load listener or your framework's hydration logic after any full-page navigation, including fallback reloads.package.json (7.x or 8.x).Turbo.fetchRequestMethod or fetch adapter is configured.Are you using a custom fetch adapter or Turbo.fetchRequestMethod override? If yes, the timeout behavior may already be implemented there; if no, you must add one to convert silent hangs into fallback-triggering aborts.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 13 Jul 2025, 18:44 UTC
Turbo Drive only triggers its automatic fallback when the fetch promise rejects or the HTTP status is non‑2xx. A request that simply stalls—no network error, no AbortError, no response—doesn’t reject the promise, so Turbo never falls back on its own. In practice, browsers don’t impose a default timeout, so a stalled visit can stay hanging indefinitely unless the application supplies a timeout wrapper (e.g., Promise.race with an AbortController).
The turbo:before-cache event fires only when Turbo is about to cache the current snapshot; it does not run during the fallback reload. Therefore, any state you store there will be lost if a fallback occurs. The supported pattern is to write critical data to sessionStorage (or localStorage) in a beforeunload / pagehide handler and restore it in turbo:load or in a Stimulus connect hook.
These behaviors have been stable from Turbo 7.0 through Turbo 8.x. The only change in Turbo 8 is the ability to disable Drive entirely with Turbo.session.drive = false, which forces a full navigation for every link or form.