Which project does this relate to? Router
Describe the bug
A background (stale-while-revalidate) reload whose loader rejects publishes the failed lane: the committed successful match becomes status: 'error' and the route's errorComponent replaces its component. The match keeps the loaderData its page was rendering from, so nothing was lost — it was replaced. A refresh the user never asked for takes their page away.
Where it appears to happen: runBackground() in load-client reduces the lane (installing the error at the boundary through the same install() path a blocking lane uses), projects it, then calls publishMatches(router, projected[1]) unconditionally — its only bail-outs are a superseded transaction (router._tx !== tx || router._committed !== base) and control outcomes (redirect/cancel). A lane whose loader rejected is published like any other.
Complete minimal reproducer
https://github.com/TylerRick/tanstack-router-background-reload-error-repro
Steps to Reproduce the Bug
pnpm install && pnpm dev, open http://localhost:5596 — the page renders PAGE: FIRST from a successful loader.
- Press fail the next load, then router.invalidate() (background). It sets a flag that makes the loader throw a plain
Error, then calls router.invalidate() with no sync. The router has defaultStaleReloadMode: 'background' and defaultStaleTime: 0.
- The harness waits for the loader to re-run and for fetching to stop (no sleeps), then prints the match snapshot before and after, plus the rendered heading:
before: {"routeId":"/","status":"success","invalid":false,"hasLoaderData":true,"isFetching":false}
after: {"routeId":"/","status":"error","invalid":true,"hasLoaderData":true,"isFetching":false,"error":"loader failed"}
rendered: ERROR COMPONENT: loader failed
Expected behavior
A failed background reload leaves the previously committed successful match and its rendered page in place, rather than replacing them with the error match. What the resulting invalidation and retry state should be is yours to decide.
Screenshots or Videos
n/a
Platform
- Router / Start Version — the same result on each of these three:
@tanstack/solid-router 2.0.0-rc.4 / @tanstack/router-core 1.171.22
@tanstack/solid-router 2.0.0-rc.5 / @tanstack/router-core 1.171.22
@tanstack/solid-router 2.0.0-rc.5 / @tanstack/router-core 1.171.27, via a pnpm override — rc.5 depends on 1.171.22, so the newest router-core has to be forced
- OS: Linux
- Browser: Chromium (Playwright 1.62.1)
- Browser Version: 151.0.0.0
- Bundler: Vite
- Bundler Version: 8.2.2
Additional context
Found in a TanStack Start app that re-runs its loaders from the client on every SSE reconnect, cross-tab message and visibility resume. One lost request during such a refresh replaced a working page with the app-wide error page until a later pass succeeded. The reproducer is deliberately client-only — no Start, no SSR, no server functions, no query client — because a plain loader rejection reaches the same path; an integration reproducer is easy to add if you would like one.
Which project does this relate to? Router
Describe the bug
A background (stale-while-revalidate) reload whose loader rejects publishes the failed lane: the committed successful match becomes
status: 'error'and the route'serrorComponentreplaces itscomponent. The match keeps theloaderDataits page was rendering from, so nothing was lost — it was replaced. A refresh the user never asked for takes their page away.Where it appears to happen:
runBackground()inload-clientreduces the lane (installing the error at the boundary through the sameinstall()path a blocking lane uses), projects it, then callspublishMatches(router, projected[1])unconditionally — its only bail-outs are a superseded transaction (router._tx !== tx || router._committed !== base) and control outcomes (redirect/cancel). A lane whose loader rejected is published like any other.Complete minimal reproducer
https://github.com/TylerRick/tanstack-router-background-reload-error-repro
Steps to Reproduce the Bug
pnpm install && pnpm dev, openhttp://localhost:5596— the page rendersPAGE: FIRSTfrom a successful loader.Error, then callsrouter.invalidate()with nosync. The router hasdefaultStaleReloadMode: 'background'anddefaultStaleTime: 0.Expected behavior
A failed background reload leaves the previously committed successful match and its rendered page in place, rather than replacing them with the error match. What the resulting invalidation and retry state should be is yours to decide.
Screenshots or Videos
n/a
Platform
@tanstack/solid-router2.0.0-rc.4 /@tanstack/router-core1.171.22@tanstack/solid-router2.0.0-rc.5 /@tanstack/router-core1.171.22@tanstack/solid-router2.0.0-rc.5 /@tanstack/router-core1.171.27, via a pnpm override — rc.5 depends on 1.171.22, so the newest router-core has to be forcedAdditional context
Found in a TanStack Start app that re-runs its loaders from the client on every SSE reconnect, cross-tab message and visibility resume. One lost request during such a refresh replaced a working page with the app-wide error page until a later pass succeeded. The reproducer is deliberately client-only — no Start, no SSR, no server functions, no query client — because a plain loader rejection reaches the same path; an integration reproducer is easy to add if you would like one.