-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
fix: serial head execution #6093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughDefer route head execution until after all loader Promises settle (use Promise.allSettled). Run heads serially with guarded try/catch, surface redirects immediately after loaders settle, and throw notFound errors after head processing. Removed file-based via-head not-found routes and updated related tests and prerender filters. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant LoadMatches as LoadMatches
participant Loaders as LoaderPool
participant HeadExec as HeadExecutor
participant Inspector as ErrorInspector
Client->>LoadMatches: loadMatches(request)
LoadMatches->>Loaders: start all loaders in parallel
par loaders run
Loaders->>Loaders: each loader resolves/rejects
end
note right of Loaders: Promise.allSettled collects results
LoadMatches->>Inspector: inspect settled results for Redirects
alt redirect found
Inspector-->>Client: throw Redirect (exit, skip heads)
else no redirect
LoadMatches->>HeadExec: executeHead(match) serially per match
HeadExec-->>LoadMatches: apply head metadata (try/catch per head)
LoadMatches->>Inspector: inspect settled results for notFound
alt notFound found
Inspector-->>Client: throw first notFound
else
LoadMatches-->>Client: return loaded matches with head applied
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (2)**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{js,ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (3)📚 Learning: 2025-12-21T12:52:35.231ZApplied to files:
📚 Learning: 2025-10-08T08:11:47.088ZApplied to files:
📚 Learning: 2025-10-01T18:30:26.591ZApplied to files:
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
🔇 Additional comments (2)
Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:eslint,test:unit,tes... |
❌ Failed | 9m 59s | View ↗ |
nx run-many --target=build --exclude=examples/*... |
✅ Succeeded | 1m 41s | View ↗ |
☁️ Nx Cloud last updated this comment at 2025-12-23 01:54:21 UTC
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
packages/router-core/src/load-matches.ts (2)
917-933: Consider limiting head execution to successfully loaded matches.The loop iterates over all
inner.matches, but loaders only execute for matches up toinner.firstBadMatchIndex(line 896-898). Matches beyond this index may have incomplete state (no loader data), which could cause unexpected behavior in head functions that depend onloaderData.Consider aligning the head execution range with the loader execution range:
- for (const match of inner.matches) { + const headMax = inner.firstBadMatchIndex ?? inner.matches.length + for (let i = 0; i < headMax; i++) { + const match = inner.matches[i]! const { id: matchId, routeId } = matchAlternatively, if executing heads for all matches is intentional (even failed ones), please add a comment clarifying this design choice.
916-916: Address TODO: decide on head execution failure behavior.The current implementation continues executing other head functions when one fails, which is resilient but may mask issues in development. Consider documenting the chosen behavior or removing the TODO if the current approach is final.
Would you like me to open an issue to track this decision, or is the current continue-on-failure behavior the intended final design?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/router-core/src/load-matches.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript strict mode with extensive type safety for all code
Files:
packages/router-core/src/load-matches.ts
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Implement ESLint rules for router best practices using the ESLint plugin router
Files:
packages/router-core/src/load-matches.ts
🧬 Code graph analysis (1)
packages/router-core/src/load-matches.ts (1)
packages/router-core/src/index.ts (2)
isRedirect(368-368)isNotFound(374-374)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Test
- GitHub Check: Preview
🔇 Additional comments (3)
packages/router-core/src/load-matches.ts (3)
900-905: LGTM -Promise.allSettledusage is appropriate here.Using
Promise.allSettledensures all loaders complete before processing results, which aligns with the goal of deferring head execution until all loaders settle. The failure extraction logic is correct.
907-912: Redirect early-exit logic is correct.Throwing on the first redirect before head execution is the right approach - there's no need to compute head metadata when redirecting.
935-940: NotFound handling after head execution is appropriate.The asymmetry with redirect handling (redirect throws before head, notFound throws after head) is intentional and makes sense - a notFound page can still benefit from head metadata, while a redirect doesn't need any rendering.
| // After head execution, check for notFound errors and throw the first one | ||
| for (const err of failures) { | ||
| if (isNotFound(err)) { | ||
| throw err | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it a little curious that we call executeHead for children of a match that would have thrown a notFound
If we have /a/b/c and /b throws a notFound it its loader, should we really still execute the head of /c? It feels "leaky"
aca0594 to
921730e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
e2e/solid-start/basic/tests/not-found.spec.ts (1)
29-29: Verify consistency: 'head' removed from navigation tests but retained in direct visit tests.This file has the same inconsistency as the React Start test file. The enriched summary indicates that the via-head route file was removed, yet line 61 still includes 'head' in the directVisitTestMatrix. This will cause test failures when the direct visit test attempts to navigate to
/not-found/via-head.Given that this PR addresses head execution issues with notFound() (PR objectives #6085), removing test coverage for navigation scenarios where notFound is thrown from head() appears inconsistent with the PR's goals.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
e2e/react-start/basic/src/routes/not-found/via-head.tsxe2e/react-start/basic/tests/not-found.spec.tse2e/solid-start/basic/src/routes/not-found/via-head.tsxe2e/solid-start/basic/tests/not-found.spec.ts
💤 Files with no reviewable changes (2)
- e2e/solid-start/basic/src/routes/not-found/via-head.tsx
- e2e/react-start/basic/src/routes/not-found/via-head.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript strict mode with extensive type safety for all code
Files:
e2e/solid-start/basic/tests/not-found.spec.tse2e/react-start/basic/tests/not-found.spec.ts
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Implement ESLint rules for router best practices using the ESLint plugin router
Files:
e2e/solid-start/basic/tests/not-found.spec.tse2e/react-start/basic/tests/not-found.spec.ts
🧠 Learnings (3)
📚 Learning: 2025-10-09T12:59:02.129Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/src/styles/app.css:19-21
Timestamp: 2025-10-09T12:59:02.129Z
Learning: In e2e test directories (paths containing `e2e/`), accessibility concerns like outline suppression patterns are less critical since the code is for testing purposes, not production use.
Applied to files:
e2e/solid-start/basic/tests/not-found.spec.tse2e/react-start/basic/tests/not-found.spec.ts
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.
Applied to files:
e2e/solid-start/basic/tests/not-found.spec.tse2e/react-start/basic/tests/not-found.spec.ts
📚 Learning: 2025-10-09T12:59:14.842Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/public/site.webmanifest:2-3
Timestamp: 2025-10-09T12:59:14.842Z
Learning: In e2e test fixtures (files under e2e directories), empty or placeholder values in configuration files like site.webmanifest are acceptable and should not be flagged unless the test specifically validates those fields.
Applied to files:
e2e/solid-start/basic/tests/not-found.spec.tse2e/react-start/basic/tests/not-found.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Preview
- GitHub Check: Test

fixes #6085
fixes #4785
fixes #4889
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.