Skip to content

Blank screen for the whole navigation when a route's pendingComponent renders <Outlet /> (changed in 1.170.19) #8042

Description

@lizatiliguzov

Which project does this relate to?

Router

Describe the bug

If a parent route's pendingComponent renders <Outlet />, and the child route below it is still unresolved when the pending component appears, the router subtree renders nothing for the whole navigation. Nothing is thrown, nothing is logged, and no error boundary is hit — the app is blank until the navigation completes.

  • @tanstack/react-router@1.170.18 (@tanstack/router-core@1.171.15) — last good
  • @tanstack/react-router@1.170.19 (@tanstack/router-core@1.171.16) — first bad
  • @tanstack/react-router@1.170.25 (@tanstack/router-core@1.171.21) — still present

I found that boundary by installing each release in turn and re-running the reproduction. I have not tried to work out which change is responsible.

To be precise about what regressed: on 1.170.18 the same route config also goes blank, but only until the child's own load settles. From 1.170.19 the blank lasts until the entire navigation settles. I measured this by varying the parent and child loader
durations — on 1.170.18 the blank tracks the child and ignores the parent; on 1.170.25 it tracks the whole navigation and ignores the child (Comparison C below).

I could not find anything in the docs stating whether <Outlet /> inside a pendingComponent is supported. The Outlet guide says it "can be rendered anywhere within a route's component tree" and renders null when there is no matching child, and the Outlet API page lists a child's pendingComponent among the things an Outlet renders — but no page addresses an Outletinside a pending component, and I couldn't
find an example that does it. If it is meant to be unsupported, it seems worth saying so and warning in development, because the failure is completely silent.

Complete minimal reproducer

https://github.com/lizatiliguzov/tanstack-minimal-reproduction

Steps to Reproduce the Bug

npm install
npm test

One assertion — while the parent route is pending, its pendingComponent should be on screen — plus three controls. It fails on 1.170.25 and passes on 1.170.18:

 × a pendingComponent WITH <Outlet /> is visible while the parent route is pending
→ expected the pendingComponent to be rendered, but the router subtree rendered nothing
visible text under <RouterProvider> : ""
sibling <div> outside it : "probe rendered outside <RouterProvider>" <- React itself is still rendering
router match statuses : __root__=success, /layout=success, /layout/parent/$id=pending, /layout/parent/$id/child=pending
waited : 5002ms, with the parent route's loader still unresolved
npm run use:working && npm test # 1.170.18 / core 1.171.15 -> 4 passed
npm run use:broken && npm test # 1.170.25 / core 1.171.21 -> 1 failed, 3 passed

The assertion walks the DOM for visible text rather than reading textContent — React retains the old page and hides it with style="display: none !important", so a textContent assertion passes on the broken version and gives a false green.

In a browser: npm run dev, open /, click the one link. The page is blank for ~3 s (the parent route's loader), then loads. The box in the bottom-right is a sibling of <RouterProvider>, so it stays visible while the router subtree renders nothing; the console prints a timeline of every change to that subtree.

Route tree:

__root component: <Outlet />
└── layout component: chrome + <Outlet />
├── / index page with the link
└── /parent/$id loader: 3000ms
pendingMs: 0
pendingComponent: heading + <Outlet />
component: heading + <Outlet />
└── child loader: 50ms

No defaultPendingComponent on the router. pendingMs: 0 is load-bearing: the precondition is that the child is still unresolved when the pending component appears. With the default pendingMs of 1000 a 50 ms child resolves first and there is no blank; a child still loading at that point blanks the same way (measured with a 2000 ms child loader and no pendingMs override: blank 1012 → 3012 ms).

Comparison A — version. Blank duration, measured from the click:

versionblank
1.170.18 / core 1.171.15~510 ms, then the pending component is visible for the rest of the load
1.170.19 / core 1.171.16~3010 ms — the entire navigation
1.170.25 / core 1.171.21~3010 ms — the entire navigation

Comparison B — the <Outlet />. Same version (1.170.25):

pending componentresult
with <Outlet /> (npm run dev)blank for the entire navigation
without <Outlet /> (npm run dev:no-outlet)pending component visible immediately and for the whole load

Comparison C — what bounds the blank. Unlike A and B this one is not scripted: I
measured it by editing the parentLoader / childLoader defaults in src/app.tsx (they
are options on the route factory, not env knobs). The cell is how long the app stayed blank:

1.170.181.170.25
parent 3000, child 5511 ms3011 ms
parent 3000, child 800812 ms3015 ms
parent 3000, child 25002511 ms3011 ms
parent 6000, child 50511 ms6017 ms
parent 1000, child 50511 ms1016 ms
parent 500, child 25002511 ms2510 ms

Expected behavior

While the parent route is pending, I expected its pendingComponent to be visible — as it is on 1.170.18, and as it is on 1.170.25 the moment the <Outlet /> is removed. Instead nothing renders at all for the whole navigation.

If rendering <Outlet /> inside a pendingComponent is not supported, I would expect the Outlet to render null there — which is what it already does when there is no matching child — or at minimum a development warning, rather than silently blanking the app.

Screenshots or Videos

No response

Platform

  • Router / Start Version: @tanstack/react-router 1.170.25 (@tanstack/router-core 1.171.21); last good 1.170.18 (core 1.171.15), first bad 1.170.19 (core 1.171.16)
  • OS: macOS (Darwin 25.5.0)
  • Browser: Chromium 151 (Playwright); the headless test runs in jsdom 30
  • Browser Version: 151
  • Bundler: vite
  • Bundler Version: 7.3.6

React 19.2.8, Node 24.15.0, vitest 4.1.10. No router plugin, no file-based routing, no TanStack Start — plain code-based routes.

Additional context

Full matrix. Client-side navigation from / to /parent/1/child, parent loader 3000 ms:

router<Outlet /> in pendingchild routeStrictModeresult
1.170.18noown loaderonpending UI at ~5 ms, no blank
1.170.18yesnone (resolves synchronously)onpending UI + child at ~6 ms, no blank
1.170.18yesown loaderonblank ~510 ms, then pending UI
1.170.19yesown loaderonblank ~3010 ms
1.170.25noown loaderonpending UI at ~6 ms, no blank
1.170.25yesnone (resolves synchronously)onpending UI + child at ~6 ms, no blank
1.170.25yesown loaderonblank ~3010 ms
1.170.25yesown loaderoffblank ~3010 ms
1.170.25yescode-split componentonblank ~3010 ms
1.170.25yes, wrapped in a local <Suspense>own loaderonpending UI + local fallback, no blank
1.170.25yes, plus a router defaultPendingComponentown loaderonpending UI + default pending in the child slot, no blank

StrictMode is not involved. A fresh full page load directly on /parent/1/child behaves the same. No console errors, no React warnings, no unhandled rejections during the blank.

The ~510 ms floor on 1.170.18 tracks defaultPendingMinMs (500 by default): set defaultPendingMinMs: 1200 and the floor moves to 1216 ms.

The last two matrix rows are both usable workarounds: wrapping the <Outlet /> in the pending component in a local <Suspense>, or setting a router-level defaultPendingComponent, removes the blank entirely. With the second, the child slot shows the default pending component for the rest of the load.

Existing issues I looked at.#7986 and #7958 are "pending UI shows too often" rather than a blank, and 1.170.22 lists a fix for them — this still reproduces on 1.170.25. #4759 and #7961 are about a blank frame before the pending component appears on initial load, not a blank that lasts the whole navigation. I may have missed something, so apologies if this is a duplicate.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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