beforeLoad - i guess we need beforeLayoutLoad
Hey folks 👋
I really enjoy how beforeLoad works in TanStack Router — especially how its return values are merged into the match context.
However, I sometimes wish there was something like a beforeLayoutLoad hook.
It would run under the same conditions as loader (meaning parent layout’s beforeLayoutLoad wouldn’t re-run when traveling under the same layout), but it would behave just like beforeLoad — i.e., its return values would merge into the context and be accessible by children.
Here’s my use case:
I’m using matches = useMatches() and building breadcrumbs like this:
We have nested routes like rootScope23/midScopeA/subScope6, and those mid-scope names are computed via beforeLoad.
But visiting sub-scopes under the same mid-scope still re-triggers the parent beforeLoad, even though nothing logically changed at that level.
I’ve read several related threads here on Discord — most suggestions revolve around tanstack-query and ensureQueryData.
While that technically works, it increases complexity and breaks the natural DX:
- When router data is invalidated, query data must also be manually invalidated.
- ensureQueryData can return stale results even when the route invalidation should cause a refetch.
beforeLoad itself has been great — not just for breadcrumbs but for many parent-level data dependencies.
Sure, I could move all of this logic into loader and find another pattern for breadcrumbs, but a lifecycle hook like beforeLayoutLoad would make a lot of sense and simplify things greatly.
Has anyone else run into this situation or found a clean solution?
And does the TSR team have any future plans or thoughts regarding a hook like this?
(I’d be happy to help prototype or PR something if the idea aligns with the roadmap.)
4 Replies
fair-roseOP•4w ago
That’s a great point — I was digging through the TanStack Router (and Query) source as well, thinking about how such a feature could be implemented.
Interestingly, the existing cause parameter on beforeLoad already helps a lot here.
By checking for cause === "enter", we can basically treat that as a layout-level lifecycle — fetching or computing data only when entering that layout, not when navigating between its children.
Here’s a small example:
It’s funny how often people on Reddit and Discord have mentioned beforeLoad running too frequently, yet no one seems to have pointed out that cause can already distinguish this behavior.
Hopefully this thread becomes a useful reference for others running into the same issue later on.
(Still, having a dedicated beforeLayoutLoad could make this pattern clearer and more discoverable.)
dependent-tan•4w ago
can you go into explicit details please, with an example of multiple routes / parents / children etc when beforeLayoutLoad etc would run and when it would not?
we are planning more lifecycle methods, so maybe this will just be covered by those then. lets see
fair-roseOP•4w ago
Sure, let me break this down with a concrete example.
Real-world scenario:
Database Management System
Imagine a database management system with this hierarchy:
Databases → Schemas → Folders → Tables → Columns
Each entity type has its own detail page. The route structure looks like this:
The Problem
When I'm at match7 (structure.tsx) and navigate to:
Another table's structure (another match7)
Same table's index (match6)
Or even a hypothetical match8/9 (e.g., column details)
...the parent beforeLoad hooks should NOT re-run, because:
I'm still under the same layout (match3: SchemaLayout)
The sidebar remains visible and functional
I'm still operating within the schemas/ route context
Why beforeLayoutLoad Would Be Perfect
If beforeLayoutLoad existed, its return value could be stored in something like beforeLayoutLoadContext. Then, during the context merge at the end of beforeLoad, the final context would be:
{...beforeLayoutLoadContext,...beforeLoadContext }
1. Runs synchronously (can be
awaited) before beforeLoad
2. Follows the same execution pattern as loader:
- loader already behaves correctly here—it doesn't re-run when navigating between sub-paths under the same layout
3. But unlike loader:
- It merges its return value into the route context (like beforeLoad does)
Why loader Doesn't Solve This
While loader has the right execution behavior (doesn't re-trigger under the same layout), it has two problems for my use case:
1. No sequential execution guarantee between loaders
2. Doesn't block rendering
In my scenario:
- The table instance object needs to be accessible in column routes
- The database instance object needs to be accessible everywhere under the database path
This forces me to use beforeLoad + context, but then I lose the layout-aware execution behavior.
The cause === "enter" Workaround Falls Short
After exploring the cause === "enter" approach ii mentioned, I discovered an issue:
When navigating directly to a deep sub-route like:
Every parent beforeLoad runs (which is expected), BUT:
All of them report cause="enter"
Even though logically, only the first one truly "entered"—the rest are just "along the path"
A potential improvement could be to return something like "stale-enter" for subsequent parent loads after the initial enter. But even then, this workaround increases complexity compared to a dedicated lifecycle method.
Summary
beforeLayoutLoad would:
-Run only when entering a layout (not when navigating between its children)
-Merge its return value into context (accessible by all children)
-Execute synchronously, before beforeLoad
-Simplify breadcrumb generation, parent data dependencies, and layout-scoped state
I believe this would be an extremely valuable addition to TanStack Router's lifecycle methods. If it aligns with your roadmap, I'd be happy to help prototype or contribute a PR.
@Manuel Schiller i hope i didnt make it so boring
After re-reading my message, I realized something: cause="enter" is actually working correctly — it truly is an "enter" event.
However, I'm confused about cause="stay".
When I'm in the $tableId.tsx route and navigate to another table via the sidebar, it still says cause="stay" — even though the params have changed.
I think what would make everything more consistent is if we could access what changed when params/search change. For example, having access to:
This would make it much clearer when we need to refetch data versus when we can safely skip.
i noticed that :
loader lifecycle is also being retriggered under same layout and this is completely unnecessary .dependent-tan•3w ago
what does this mean?
a full reproducer project would help