Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

ssr:false: redirect thrown in a child beforeLoad during the initial load blanks the app (MatchInner throws `undefined`), reproducer for #7910

Đang mở
#8,783 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
48/100
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
react, typescript
Lĩnh vực
backend, frontend

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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

  1. The ssr: false match is dehydrated as pending. On the client, hydrate() puts it in the active matches, and router.load() runs the guards.
  2. The child throws a redirect. navigate({ replace: true }) starts a new load whose pending matches reuse the same id for the ssr: false layout. They share _nonReactive.
  3. That load finishes the layout match. loadRouteMatch resolves and clears _nonReactive.loadPromise. This happens before onReady commits the pending matches.
  4. React renders the still-active stale snapshot (status: "pending"). MatchInnerImpl does throw getMatchPromise(match, "loadPromise"), which is undefined.
  5. CatchBoundary stores undefined as the error. That is falsy, so it re-renders its children, which throw again. React gives up and unmounts the root.
  6. The router state afterwards is correct (all matches success), but nothing is mounted. router.state.status stays pending forever.

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 loadPromise alive until the pending matches are committed.
  • Separately, CatchBoundary could treat a thrown undefined as an error, so it renders the errorComponent instead of looping.
Ngôn ngữ chính
TypeScript
Star
15.2k
Fork
1.9k
Merge trung bình
1 ngày 8 giờ
Pull request đã merge (30 ngày)
70

Chuẩn bị môi trường

Mở trong Codespaces

Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của TanStack/router

Tất cả issue của TanStack/router

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.