-
-
Notifications
You must be signed in to change notification settings - Fork 1k
fix(router-core): Pathless Layout Route Renders Empty HTML #4003
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
base: main
Are you sure you want to change the base?
fix(router-core): Pathless Layout Route Renders Empty HTML #4003
Conversation
View your CI Pipeline Execution ↗ for commit 9fa49ff.
☁️ Nx Cloud last updated this comment at |
More templates
@tanstack/arktype-adapter
@tanstack/directive-functions-plugin
@tanstack/eslint-plugin-router
@tanstack/history
@tanstack/react-router
@tanstack/react-router-devtools
@tanstack/react-router-with-query
@tanstack/react-start
@tanstack/react-start-client
@tanstack/react-start-plugin
@tanstack/react-start-server
@tanstack/router-cli
@tanstack/router-core
@tanstack/router-devtools
@tanstack/router-devtools-core
@tanstack/router-generator
@tanstack/router-plugin
@tanstack/router-utils
@tanstack/router-vite-plugin
@tanstack/server-functions-plugin
@tanstack/solid-router
@tanstack/solid-router-devtools
@tanstack/solid-start
@tanstack/solid-start-client
@tanstack/solid-start-plugin
@tanstack/solid-start-server
@tanstack/start-client-core
@tanstack/start-plugin-core
@tanstack/start-server-core
@tanstack/start-server-functions-client
@tanstack/start-server-functions-fetcher
@tanstack/start-server-functions-server
@tanstack/valibot-adapter
@tanstack/virtual-file-routes
@tanstack/zod-adapter
commit: |
can you add a test for this? |
also wondering if this maybe needs a fix in the router generator instead? what's the idea of your fix here? |
Just because only a pathless layout file exists doesn’t mean there’s no route. |
yes I think so. the generator currently generates a "virtual route" in this situation. this is then matched at runtime |
I believe the issue can be resolved by updating the |
maybe, I did not look into it deeply yet. can you add an e2e test into one of the existing e2e test setups for react router for this please? then we can check whether the fix solves it |
Got it. But I think additional changes might be needed.
|
I think we'll also need to update the test snapshots on this one. Looking at the current changes on the reproduction sandbox in the reported issue, using the pr-pkg-new packages, the diff shows that a path is being assigned to a pathless layout route. ![]() |
Been thinking about this more and we'd need some comprehensive sandbox (end-to-end) testing for this. Plus, my point earlier about pathless layout routes being assigned a path isn't correct. |
Then, do you think we should approach this in a different way rather than relying on |
Could you review the last commit? |
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.
@leesb971204 its partially resolved.
With your changes, these scenarios now work.
src/routes/
app/
_appLayout.tsx
_appLayout.dashboard.tsx
_appLayout.something.tsx
src/routes/
app/
_appLayout.tsx
_appLayout.dashboard.tsx
_appLayout.something.tsx
_foo.tsx
_foo.index.tsx
However, these two scenarios break the route tree.
- For this scenario, the
routeTree
file gets put into a state where it tries to create an import from'./routes/app'
which of-course does not exist on the file-system.
src/routes/
app/
_appLayout.tsx
_appLayout.dashboard.tsx
_appLayout.something.tsx
_foo.tsx
_foo.something.tsx
- For this scenario, the
routeTree
file adds an additional/app
into the routes. So, instead of it being/app/dashboard
, it instead becomes/app/app/dashboard
.
src/routes/
app/
route.tsx
_appLayout.tsx
_appLayout.dashboard.tsx
_appLayout.something.tsx
_foo.tsx
_foo.index.tsx
I wish I had better feedback to give, as to how you can approach this, but this is something @schiller-manuel and I have discussed earlier at length with the current system being 'best-effort' compromise that we came to.
The shortcut, to get your original sandbox working, would be to throw a redirect
in src/routes/app/index.tsx
to a different route or to set a notFoundComponent
and immediately throw a notFound
.
src/routes/
app/
+ index.tsx
_appLayout.tsx
_appLayout.dashboard.tsx
_appLayout.something.tsx
Thanks for the review. |
It is kind of two-fold. Router works in a tree-like structure, which is important since pathless layout routes (which do not have a 'path') requires it having to know what its parent is, for everything to work at runtime. As such, in this scenario, it creates a route in the tree which doesn't have a component and/or any logic attached to it. Its purely there, just for the pathless layout route to use for it refer to it as its parent. So, I say this problem is two-fold, since we could either tackle this problem by.
If you are open to tackling option 1, that'd be great! If not, no worries. |
I’ve implemented the first approach — it now redirects to |
cc @schiller-manuel on please making some allowances to be able to add this into the generator reworks later on. |
I am currently rewriting the generator at a larger scale. please bear with me, I hope to get this done in the next few days. then we can revisit this topic |
Signed-off-by: leesb971204 <[email protected]>
c495f09
to
35d88fb
Compare
// Detect layout routes that have children but lack an explicit index ("/") child | ||
// and mark them to redirect to the root path. | ||
acc.routeNodes.forEach((node) => { | ||
if (node._fsRouteType !== 'layout') { | ||
return | ||
} | ||
|
||
if (!node.isVirtualParentRoute) { | ||
return | ||
} | ||
|
||
if (node.children && node.children.length > 0) { | ||
if (node.children.some((child) => child.cleanedPath === '/')) { | ||
return // already has index child → skip | ||
} | ||
} | ||
|
||
node.isVirtualRedirectIndex = true | ||
node.redirectTo = '/' | ||
}) |
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.
We iterate through acc.routeNodes to find virtual layout routes that do not have an index
route as a children, and redirect them to '/'
.
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.
With the following directory structure, accessing /foo/bar
currently results in a redirect to '/'
.
If this behavior is incorrect and /foo/bar
should be accessible, additional changes are needed.
Please share your thoughts.
src/routes/
foo/
_fooLayout.tsx
bar/
index.tsx
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.
surely this is a valid case where /foo/bar should be accesible
don't see where the pathless layout comes into play here?
I am wondering what the actual issue is that we need to solve. it seems the problem is that at runtime router is not aware that those routes should not be used for matching. if my understanding is correct, shouldn't we rather fix this when processing the route tree in the router? |
Are you suggesting that redirecting pathless layout routes to |
yes. I think ideally the route matching would just fail and the not found handling would kick in. |
In that case, it sounds more appropriate to handle those specific paths as |
971741a
to
35d88fb
Compare
…e index path Signed-off-by: leesb971204 <[email protected]>
Signed-off-by: leesb971204 <[email protected]>
packages/router-core/src/router.ts
Outdated
// If there are child routes, check if any of them have the same path as the current route | ||
if (route.children && route.children.length > 0) { | ||
if (!route.children.find((child) => child.path === '/')) { | ||
return false | ||
} | ||
} | ||
|
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.
If a route does not have a parent index route among its children, it is excluded from matching.
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.
what's "parent index route" when it comes to children?
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.
what's "parent index route" when it comes to children?
This refers to a route with an index
path among the children under a parent route.
src/routes/
foo/
_fooLayout.tsx
index.tsx 👈
bar/
index.tsx
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.
what if you have foo.tsx or route.tsx inside /foo ?
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.
what if you have foo.tsx or route.tsx inside /foo ?
I’m not sure if this can be fully solved within the router core alone.
As you mentioned, we need to correctly match both cases:
– when a route file with the same name exists in another directory (foo.tsx)
– when a route.tsx
file exists inside the directory
But it seems difficult to handle both cases purely in the router core.
I initially tried to determine this based on the presence of options.component
,
but this approach fails in cases where the component is lazy.
Should we consider adding a specific flag in the generator to help the router core handle it properly or using update
?
we need to add tests for this, ideally in router core. |
…nerator logic Signed-off-by: leesb971204 <[email protected]>
@@ -583,6 +600,7 @@ export class Generator { | |||
`id: '${node.path}'`, | |||
!node.isNonPath ? `path: '${node.cleanedPath}'` : undefined, | |||
`getParentRoute: () => ${findParent(node, exportName)}`, | |||
node.isVirtualLayout ? `isVirtualLayout: true` : undefined, |
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.
A value is set via update()
in the router core
to determine whether a route should be matched.
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.
@schiller-manuel
I wanted to implement this in a way that keeps the logic hidden from the user’s code, but I couldn’t come up with a better solution. 😕
I’d appreciate any further suggestions you might have.
If you need a test case, I’d be happy to write one.
fixes #3843