understanding layout routes
I'm having a hard time understanding layout routes. Maybe my use-case is a bit non-standard, but what I would like to achieve is the following scenario. We have a lot of routes per "workspace" that live under
/workspaces/$id, for example:
etc. There are also routes outside of workspaces/$id, but those that are under it should have a shared layout (mainly headerBar + sideBar + some data fetching + making sure the $id is a valid uuid etc.)
I thought about the following, file-based structure:
this "works", but it's not really a layout, or is it ? The route.tsx is where I would parseParams and render header + sideBar, because it will be rendered for all pages below it. One problem is that I then can additionally create urls that go to="/workspaces/$id" and it's seen as valid by the router, which I wouldn't really like because it is not a page that actually exists. So I thought about adding an index.tsx route that redirects to the help page, and that works fine at runtime, but now the router allows the following links to be created on type level:
I'm not really sure why we have $id link twice in ther now - once with slash and once without ๐
. Since we are using trailingSlash: 'always', the created links are really the same.
I think the root cause is that after creating route.tsx, we get a valid link towards '/workspaces/$id', which shouldn't be the case because route.tsx isn't really a page...65 Replies
like-goldOPโข2y ago
another thing I have tried is really using layouts, with the following structure:
so instead of
route.tsx, there is _layout.tsx and then in the directory of the layout, there are the sub routes. This pretty much has the same problem - I can link to '/workspaces/$id' even though I think this route shouldn't exist, and there is another issue: Inside _layout.tsx, I can't parse params because the params I'm receiving are of type Record<never, string> instead of Record<'id', string> as they were before with route.tsx
I can create a reproduction for this if you think this is just a typings issue - I was just wondering which approach would be the recommended one in general since they both kind of achieve the same thing - having a route.tsx file or a _layout.tsx file ... thank you ๐manual-pinkโข2y ago
a repro for the second one would be great
generally speaking, a
route.tsx will cause the route to exist and thus be a valid link target
in your second approach that uses a real (as in underscore prefix ) layout, /workspaces/$id should not exist as a route. maybe there is a bug in the generator (virtual route being created?)like-goldOPโข2y ago
Yes I see a virtual route being created in the routeTree generated file. I'll do a reproduction tomorrow
like-goldOPโข2y ago
here's the reproduction for the
_layout structure, and it shows both issues:
https://stackblitz.com/edit/tanstack-router-23xrx1?file=src%2Froutes%2Fworkspaces%2F%24id%2F_layout.tsx
1. in /workspaces/$id/_layout, params passed into parseParams are of type Record<never, string> rather than Record<'id', string>
2. you can see in __root.tsx that I can create links towards both /workspaces/$id and /workspaces/$id/, even though they don't exist. Navigating to one of those links in the browser shows both routes as active, and they just render an empty page.Dominik Dorfmeister
StackBlitz
Router Basic File Based Example (forked) - StackBlitz
Run official live example code for Router Basic File Based, created by Tanstack on StackBlitz
like-goldOPโข2y ago
@Manuel Schiller FYI โฌ๏ธ
let me know if I should file an issue for this ๐
manual-pinkโข2y ago
I found the reason why you would get
never when trying to parse the params in the _layout route.
while we could "fix" this, I think we should fundamentally discuss the structure you want to achieve.
@Tanner Linsley we are missing the "opposite" of a layout route:
a route that contributes to the path but cannot be matched on its ownabsent-sapphireโข2y ago
Can you elaborate or explain the use case?
When doing code base routing, isnโt this simply having a prefix or a suffix or multiple path segments in your path?
And if so, I can see how it would be extremely difficult for us to design this in file-based routing,
manual-pinkโข2y ago
see the use case presented by @TkDodo ๐ฎ
he wants to group a set of routes under /workspaces/$id, but /workspaces/$id itself should not be a valid route
absent-sapphireโข2y ago
You mean an index route on that?
The root is valid, just not that exact match?
Seems like a redirect would solve this
Either detect the lack of a sub route in the layout and redirect. Or add an index route and redirect from there.
Technically, you could create a bunch of routes in code routing with
path: '/workspaces/$id/whatever', which would only match if it was complete down to the whateverlike-goldOPโข2y ago
Yeah I have an index route and redirect from there, it's
parseParams that doesn't work then on type level, and I get two different valid routes: /workspaces/$id and /workspaces/$id/. The one with trailing slash comes from the index route, but why is the other one there?absent-sapphireโข2y ago
Technically speaking, what you're looking for is some kind of marker that will tell the internal matching system to skip over it as an index match and keep going
The other one being there sounds like a type bug
At one point I had some type logic in place that would add trailing slashes to everything for path matching
like-goldOPโข2y ago
Yeah @Manuel Schiller also said it likely shouldn't be there
absent-sapphireโข2y ago
However, it's still relevant for routes/matches based on ID
e.g., You want to target the the loader data for that layout route
So navigation... it shouldn't be there
But for things like data, context, acquiring routes/matches by ID, it does
like-goldOPโข2y ago
Oh yeah totally, it's only weird for navigation
I also don't fully understand if the
route.tsx approach is better or the layout one. I feel that with route.tsx, I get what I want with less effort / fewer filesabsent-sapphireโข2y ago
Example?
manual-pinkโข2y ago
you would not need the index route right? route.tsx should suffice for the redirecting
like-goldOPโข2y ago
The two route trees I've shown in the initial post
I can't redirect in route.tsx because it's also rendered for sub-routes I thought...
absent-sapphireโข2y ago
Either works
Route.tsx is not an index route. Itโs just the same as making a route with the same name as as the directory above it
manual-pinkโข2y ago
you should be able to redirect conditionally
absent-sapphireโข2y ago
Correct
It all just depends on how you want to approach it.
manual-pinkโข2y ago
that's what I meant as " the opposite of layout routes"
absent-sapphireโข2y ago
Yeah, adding that marker wouldnโt be hard
Honestly though, it should already work that way
Like, unless you have an index route, it shouldnโt consider the branch as a destination to match
Types, yes we need to check those. But runtime? I think thatโs already how it works.
manual-pinkโข2y ago
for types to work, we would need to check if a route is a leaf?
to be a valid target
absent-sapphireโข2y ago
Yes.
For navigation
But there are places where branches are valid as well.
manual-pinkโข2y ago
where?
absent-sapphireโข2y ago
When you write a link with a from thatโs originating from a layout route, technically from: /branch is valid
getRouteApi() can use branches
useSearch from a branch is technically possible if you donโt know what leaf youโre on but what access to the shared search
manual-pinkโข2y ago
but to?
absent-sapphireโข2y ago
It's really just
to: ... that needs to be locked down I think
Again, at one point this was implemented
But as we needed to fix more important bugs, I think some of this nuance was lost
and no type tests ๐คฆโโ๏ธ
To would only allow and autocomplet to leafs, but from can come from any valid branch/leafmanual-pinkโข2y ago
sounds like a job for @Chris Horobin ๐
absent-sapphireโข2y ago
Indeed ๐
@Chris Horobin You up for the challenge? (not to say it would even be remotely difficult for you)
ambitious-aquaโข2y ago
I've been summoned. I'm reading back through the messages. From what I understand we basically should only consider leaves as destinations. But
from can consider branches? Because you might want to be more loose about that I could be coming from any of these leaves under a branch? But a destination has to be concretely a leaf?
There's a lot of nuance here so we definitely need type tests to cover this
Please let me know if I misunderstood this ๐
manual-pinkโข2y ago
perfect summary
ambitious-aquaโข2y ago
yeah. I will think about an efficient way to do the typing of this. From a type level point of view, it might only know if it is a leaf when there are no children and that might be the most efficient anyway. Parsing the actual paths to work this out might not be a good idea
Like, each route knows if it has children or not. A path does not know without either looking up the route by
RouteByPath or looking for a prefix in a huge union (this option is a bad idea imo)
Maybe how I would do it is make an extra type on top of RoutePaths called RouteLeaves which extracts routes that are leaves. Sound good?manual-pinkโข2y ago
maybe already think about a type that could be generated by the generator as well as calculated by TS for code based routing
ambitious-aquaโข2y ago
This is probably a separate topic I'm currently investigating. Theres definitely some very easy things like
path, id, fullPath that can be added to the generated types to make TS faster (as string manipulation for each route is sow to infer these). But its a bit harder to think of a different route tree structure for file based routing to be more efficient. Lots to think about
My only concern about this is currently CheckPath. It checks if it is a valid route by path. While autocomplete will not suggest a branch for to, a branch will still be valid because the route exists... Basically it like a branch is valid only if from is specified and not to?manual-pinkโข2y ago
so we would need to different checks? or check paths should be parameterized?
ambitious-aquaโข2y ago
probably an additional check but i need to think so its not messy
ambitious-aquaโข2y ago
GitHub
fix: do not allow leaf nodes as
to suggestions by chorobin ยท Pull...Introduce RouteLeaves type to only autocomplete leaves
We have change CheckPath aswell (yuck, I can't think of a better way)
Add some type tests to cover only leaves
ambitious-aquaโข2y ago
Ignore the title on the image, I corrected it ๐
manual-pinkโข2y ago
image?
did you check the performance impact (if any)?
ambitious-aquaโข2y ago
yep, its a bit worse but I'm not sure how to not cause a slight regression because we have to distribute over the union to only get the leaves.
manual-pinkโข2y ago
released as of v1.29.1
manual-pinkโข2y ago
@TkDodo ๐ฎ I modified your example to show how these links are now not valid anymore:
https://stackblitz.com/edit/tanstack-router-cvjex8?file=package.json,src%2Froutes%2F__root.tsx
StackBlitz
Router Basic File Based Example (forked) - StackBlitz
Run official live example code for Router Basic File Based, created by Tanstack on StackBlitz
manual-pinkโข2y ago

manual-pinkโข2y ago
@Tanner Linsley the conditional redirect in route.tsx is a bit ugly... or maybe I am missing something.
https://stackblitz.com/edit/tanstack-router-cvjex8?file=package.json,src%2Froutes%2Fworkspaces%2F%24id%2Froute.tsx,node_modules%2F%40tanstack%2Freact-router%2Fdist%2Fesm%2Froute.d.ts,src%2Fmain.tsx
we don't have access to the "parsed" location in
beforeLoad (by that I mean /workspaces/$id instead of /workspaces/c403d5c9-3a2d-4d57-a813-8fd5d2b6fb89), so this is what I came up with:
StackBlitz
Router Basic File Based Example (forked) - StackBlitz
Run official live example code for Router Basic File Based, created by Tanstack on StackBlitz
like-goldOPโข2y ago
Amazing, thank you
like-goldOPโข2y ago
@Manuel Schiller there's something odd now. If
route.tsx renders a component, I will only see that component on help, while before, I would get both rendered. See here: https://stackblitz.com/edit/tanstack-router-w4r4dc?file=src%2Froutes%2Fworkspaces%2F%24id%2Froute.tsx,src%2Froutes%2Fworkspaces%2F%24id%2Fhelp.tsx
If you navigate to help, you will only see "hello route.tsx" which comes fromroute.tsx. I would expect to see both "hello route.tsx" and "Hello /workspaces/$id/_layout/help!"StackBlitz
Router Basic File Based Example (forked) - StackBlitz
Run official live example code for Router Basic File Based, created by Tanstack on StackBlitz
like-goldOPโข2y ago
at least that's how it worked before ๐
manual-pinkโข2y ago
you need to add an
<Outlet> to route.tsx
otherwise the child matches will not be rendered
see https://tanstack.com/router/latest/docs/framework/react/guide/outletslike-goldOPโข2y ago
๐คฆโโ๏ธ
Thanks
ambitious-aquaโข2y ago
I'd like to fix trailing slashes aswell as I dont currently like the way it works. But I don't know if there is some nuance here with
from. I guess with from there is an actual difference between a branch and an index route?
Just had an idea. Can we safely say each routes fullPath always has a trailing slash if it is a leaf?like-goldOPโข2y ago
not sure. we recently introduced that trailingSlashes are customizable on router level:
ambitious-aquaโข2y ago
We could apply this on the type level too probably. Like, always add a trailing slash to the leaf if
alwayslike-goldOPโข2y ago
that would be great
ambitious-aquaโข2y ago
I will do a thread later about my thoughts on these three options and how we can support it on the type level as this might be the best way
deep-jadeโข17mo ago
was this behaviour changed again? in the stackblitz link that @TkDodo ๐ฎ posted (https://stackblitz.com/edit/tanstack-router-w4r4dc?file=src%2Froutes%2Fworkspaces%2F%24id%2Froute.tsx,src%2Froutes%2Fworkspaces%2F%24id%2Fhelp.tsx) the code completion does not show the
/workspaces/$id/ route. But with the most recent version, it's being visible in the list again.
I just updated the dependency versions and the vite plugin according to the docs to get it working again.
here is the updated code : https://stackblitz.com/edit/tanstack-router-dcmzoh?file=src%2Froutes%2Fworkspaces%2F%24id%2Froute.tsxStackBlitz
Router Basic File Based Example (forked) - StackBlitz
Run official live example code for Router Basic File Based, created by Tanstack on StackBlitz
StackBlitz
Router Basic File Based Example (forked) - StackBlitz
Run official live example code for Router Basic File Based, created by Tanstack on StackBlitz

deep-jadeโข17mo ago
I stumbled upon this because I have a similar case where I'd like to have the following hierarchy:
- /org
- /org/$slug
- /org/$slug/...
and all routes under
/org/$slug should share the same layout. So initially I thought I'd use a layout file like /org/_$slug but that didn't work and the I switched to /org/$slug/route.tsx with an Outlet, which works, but now org/$slug/ shows up as a potential link target.
Are there any other reasons why one would use a underscore prefixed file as a layout than not adding that one to the list of potential paths? I can totally live with the fact that I have the link in my code completion if there is no real difference but code completion.like-goldOPโข17mo ago
hmm @Chris Horobin I think in the shown sandbox, there shouldn't be
/workspaces/$id/ available as a route to navigate to, because this route doesn't exist (there is no index route).
so I'm on 1.45.4 and I'm also getting auto completion for links towards /workspaces/$id/ even though we only have a route.tsxthere (no on index route)ambitious-aquaโข16mo ago
I believe this is correct. The types for
to try to mimic the runtime behaviour.
Provided you have trailing slashes set to always
If you have an index route, org/$slug/ resolves to the index route as expected.
If you have no index route, org/$slug/ resolves to route.tsx
It's because it's not possible to navigate to route.tsx if a index route exists and trailing slashes are enforcedlike-goldOPโข16mo ago
but imo it shouldn't be possible to navigate to a
route.tsx route alone, right?
I mean, route.tsx is used as a shared layout - it's not a route that "really exists"ambitious-aquaโข16mo ago
Yeah, I think there were reasons for this @Manuel Schiller do you remember exactly the situation navigating to
route.tsx is needed?manual-pinkโข16mo ago
hm I think it only makes sense in case no index exists
ambitious-aquaโข16mo ago
Yeah, which is what's currently happening, no?
/org/$slug/ navigates to an index route if one exists, otherwise it navigates to the route.tsx?
I think there was some reason to do with modals but I can't remember where the conversation was ๐คlike-goldOPโข16mo ago
hm, the way I see it,
route.tsx shouldn't exist for to (only for from), and if you want a real route to navigate towards, you should add an index.tsx route. But there could be some use-case I'm missing of coursedeep-jadeโข16mo ago
I just modified my routes in a way that instead of using a
route.tsx I have the following structure with a "real" pathless route (I thought)
routes/org/$slug
routes/org/$slug/_apps.tsx <- layout, should not be possible to redirect to
routes/org/$slug/_apps/app-a.tsxroutes/org/$slug/_apps/app-b.tsx
But I still see /org/$slug in the to of a redirect, which doesn't make sense in terms of a pathless route, does it?