ssr:false: redirect thrown in a child beforeLoad during the initial load blanks the app (MatchInner throws `undefined`), reproducer for #7910
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 48/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- react, typescript
調査の方向性
Start by tracing hydration and the redirected load through MatchInnerImpl, getMatchPromise, CatchBoundary, and loadRouteMatch; the issue gives a reproducible timing scenario but no test file or code locations. Reproduce with the supplied ssr: false parent and child beforeLoad redirect, adding router.invalidate() during the redirected load. Done means the initial page no longer blanks and the redirect reaches the destination reliably.
索引モデルが issue の本文から書いたものです。
説明
Versions:
- @tanstack/react-router 1.170.18
- @tanstack/router-core 1.171.15
- @tanstack/react-start 1.167.x (start-client-core 1.170.14)
- React 19.2
- Vite 8.1, dev mode
Describe the bug
A full page load of a URL under an ssr: false layout, where a child route's async beforeLoad throws redirect(), can leave the page blank. The console shows Uncaught undefined and "An error occurred in the component".
Navigating to the same URL client-side works.
This is the issue reported in #7910, which was closed for lack of a reproducer. Here is one with no .preload() forwarding.
Minimal reproduction
// routes/zzrepro.tsx
export const Route = createFileRoute("/zzrepro")({
ssr: false,
beforeLoad: async () => { await wait(200); return { user: { id: "x" } } },
component: () => <Outlet />,
})
// routes/zzrepro.a.tsx
export const Route = createFileRoute("/zzrepro/a")({
beforeLoad: async () => { await wait(200); throw redirect({ to: "/zzrepro/b" }) },
component: () => <div>A</div>,
})
// routes/zzrepro.b.tsx
export const Route = createFileRoute("/zzrepro/b")({
beforeLoad: async () => { await wait(200) },
component: () => <div>B rendered OK</div>,
})
In our app, the redirect alone was enough to blank the page.
In this synthetic version it reproduces deterministically if router.invalidate() is also called while the redirected load is in flight, about 500–900 ms after page load.
For example, Supabase's onAuthStateChange emits SIGNED_IN on every page load. A root-level listener that calls router.invalidate() on it hits this window.
Analysis
- The
ssr: falsematch is dehydrated aspending. On the client,hydrate()puts it in the active matches, androuter.load()runs the guards. - The child throws a redirect.
navigate({ replace: true })starts a new load whose pending matches reuse the same id for thessr: falselayout. They share_nonReactive. - That load finishes the layout match.
loadRouteMatchresolves and clears_nonReactive.loadPromise. This happens beforeonReadycommits the pending matches. - React renders the still-active stale snapshot (
status: "pending").MatchInnerImpldoesthrow getMatchPromise(match, "loadPromise"), which isundefined. CatchBoundarystoresundefinedas the error. That is falsy, so it re-renders its children, which throw again. React gives up and unmounts the root.- The router state afterwards is correct (all matches
success), but nothing is mounted.router.state.statusstayspendingforever.
Workaround we ship (patch-package)
In MatchInnerImpl's getMatchPromise:
return router.getMatch(match.id)?._nonReactive[key]
?? match._nonReactive[key]
?? router.latestLoadPromise
?? new Promise((resolve) => setTimeout(resolve, 0))
Suggested fixes
- Never throw a non-thenable from the pending branches.
- Or keep
loadPromisealive until the pending matches are committed. - Separately,
CatchBoundarycould treat a thrownundefinedas an error, so it renders theerrorComponentinstead of looping.
- 主要言語
- TypeScript
- スター
- 15.2k
- フォーク
- 1.9k
- 平均マージ
- 1日 13時間
- マージ済み PR(30日)
- 76
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
TanStack/router のほかの issue
-
router.startViewTransition leaves the ready/finished promises unhandled, so a skipped transition throws an unhandled AbortError対応中かも @unjica が 3 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
TanStack/router#8776 · コメント 3 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Router devtools crash when search params contain a BigInt (JSON.stringify in history tracking)対応中かも @A-Raphie が 7 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 1 日以内に返信
-
docs: `createLink` has no API reference page; 3 how-to guides link to a missing `#createlink` anchor対応中かも @A-Raphie が 7 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信
-
Inline scripts are duplicated after hydration when the CSP nonce is sent in a header対応中かも @gabriel-le が 28 日前に担当しました。 オープン
難易度 1/5 1時間未満 初心者へのやさしさ 90/100
メンテナーはふだん 1 日以内に返信
-
start-plugin-core: sitemap lastmod and pages.json lastBuilt are stamped with the build's wall clock (no SOURCE_DATE_EPOCH support)対応中かも @treadnot が 29 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
メンテナーはふだん 1 日以内に返信
TanStack/router の issue をすべて見る
似ている issue
-
難易度 1/5 1時間未満 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
juice-shop/juice-shop#3706 ·
メンテナーはふだん 1 日以内に返信
-
sbx up from a subfolder silently pushes the whole work tree対応中かも @pdettori が今日担当しました。 オープンenhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
outline/outline#14348 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug needs-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信