Repository navigation
TanStack: Support pathless layout routes (id-only) in story routing - #35465
huang-julien merged 8 commits into
Conversation
…icateRouteTree cloneChild stripped id from every cloned route's options; a pathless route (explicit id, no path) was cloned with neither identity and TanStack registered it as a duplicate __root__. Preserve the explicit id when the merged (post-override) options carry no path — an override that adds a path re-derives identity from the path instead of tripping the id+path invariant.
A route whose final id segment is pathless (`_layout`, `(group)`) defines a
layout/group and contributes nothing to the URL, but createFileRoute
normalized such ids to path '/': a layout with path '/' never matches its
children (stories 404) and collides with a sibling index route
(Duplicate routes found with id: /). Emit id-only routes instead, matching
what @tanstack/router-generator produces. The segment-tail predicate also
covers pathless layouts nested under pathful segments (/posts/_layout),
which previously inherited their parent's path. An explicit options.path
wins over a pathless id.
BREAKING-ish: createFileRoute('/(group)') previously produced path '/'
(which never routed); it now produces an id-only route.
With pathless routes id-only, a story bound to the layout route itself could no longer render: a pathless layout only matches when a child matches, and the mount-path inference read init-backed getters (fullPath/id) that are undefined on cloned, not-yet-init()ed routes. Resolve the story leaf to the layout's index child — reusing an existing path:'/' child (attaching a synthetic sibling would derive the same generated id and crash createRouter) or attaching a synthetic bare one — and infer the mount URL by walking the cloned parent chain's options (mountPathFor), which works before init and skips pathless and bare '/' segments.
Routing-level coverage beyond the pathless fixes: nested pathful layouts with index children, path params through the clone, group/layout/param mixes shaped like routeTree.gen output, and routeOverrides addressed by original id. Droppable if a minimal diff is preferred.
Sandbox regression stories in the pattern of the Outlet stories (storybookjs#35007): a leaf under a pathless layout renders at the layout-free URL with beforeLoad executed, and a story bound directly to the (nested) pathless layout route mounts at the layout's position. Droppable if a minimal diff is preferred.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughTanStack React Router support now preserves pathless route identities, derives mount paths from ancestors, ensures pathless layouts have matchable index children, and adds normalization tests, cloning tests, router tests, and Storybook regression stories. ChangesTanStack pathless route support
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Storybook
participant createStoryRouter
participant duplicateRouteTree
participant TanStackRouter
Storybook->>createStoryRouter: resolve selected route and mount parameters
createStoryRouter->>duplicateRouteTree: clone route tree with pathless identities
duplicateRouteTree-->>createStoryRouter: return cloned route tree
createStoryRouter->>TanStackRouter: mount at inferred ancestor path
TanStackRouter-->>Storybook: match pathless layouts and leaf route
Possibly related issues
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR fixes Storybook’s TanStack Router integration to correctly handle pathless layout/group file routes (e.g. /_authed, /(group), /posts/_layout) by treating them as id-only routes and ensuring story routing/mounting works when such routes appear in the hierarchy or are bound directly in a story.
Changes:
- Update the
createFileRoutemock to emit id-only routes when the final segment is pathless (unlessoptions.pathis explicitly provided). - Adjust route-tree duplication to preserve explicit ids for pathless routes, while keeping existing id-stripping behavior for pathful routes.
- Improve the decorator/router setup to infer mount URLs via ancestor paths and to make pathless layout leaves matchable by reusing/adding an index child; add regression tests and template stories.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| code/frameworks/tanstack-react/src/routing/path-utils.ts | Adds isPathlessFileRouteId helper to detect ids whose final segment is pathless. |
| code/frameworks/tanstack-react/src/export-mocks/react-router.ts | Makes createFileRoute emit id-only routes for pathless final segments; preserves explicit options.path. |
| code/frameworks/tanstack-react/src/export-mocks/react-router.test.ts | Expands tests to cover id-only behavior for pathless ids and path/override precedence. |
| code/frameworks/tanstack-react/src/routing/duplicate-tree.ts | Preserves ids for pathless clones; adds mountPathFor to compute mount URL from pathful ancestors. |
| code/frameworks/tanstack-react/src/routing/decorator.tsx | Uses mountPathFor for path inference; ensures stories bound directly to pathless layouts still match by using/reusing an index child. |
| code/frameworks/tanstack-react/src/routing/duplicate-tree.test.ts | Adds regression + matrix tests for duplication/matching with pathless layouts and overrides. |
| code/frameworks/tanstack-react/src/routing/decorator.test.ts | Adds regression tests for mount path inference and index-child reuse with pathless layouts. |
| code/frameworks/tanstack-react/template/stories/PathlessLayout.stories.tsx | Adds template regression stories validating rendering and beforeLoad under pathless layouts (including direct binding). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…t test Review feedback on storybookjs#35465: keep plain Vitest unit tests free of the Storybook test layer.
|
I've fixed the test imports in my next PR. |
…t test Review feedback on storybookjs#35465: keep plain Vitest unit tests free of the Storybook test layer.
…t test Review feedback on storybookjs#35465: keep plain Vitest unit tests free of the Storybook test layer.
No need to apologize. I don't care if mine or someone else's fix gets merged. Just as long as it gets patched, I'm happy. |
…t test Review feedback on storybookjs#35465: keep plain Vitest unit tests free of the Storybook test layer.
…t test Review feedback on storybookjs#35465: keep plain Vitest unit tests free of the Storybook test layer.
…act-pathless-layout-routes TanStack: Support pathless layout routes (id-only) in story routing (cherry picked from commit 3533366)
Title: TanStack: Support pathless layout routes (id-only) in story routing
What I did
Pathless routes (
_authed/layouts,(group)directories) break story routing intwo places. First, the
createFileRoutemock normalizes pathless ids to a URLpath: pure-pathless ids get
path: '/', which never matches children (stories 404) and collides with sibling index routes, while nested ones(
/posts/_layout) duplicate their parent's path. Second,duplicateRouteTreestrips
idfrom every clone, so a truly pathless route (id, no path) crasheswith
Duplicate routes found with id: __root__.Fix: ids whose final segment is pathless now produce id-only routes (matching
what the router generator emits), and
duplicateRouteTreepreserves explicit idson pathless routes (pathful routes keep the existing id-stripping).
Follow-through fixes so id-only routes work everywhere the framework touches
them: stories bound directly to a pathless layout route still render (the
decorator infers the mount URL by walking the cloned route chain's options,
since pre-init
fullPathgetters are undefined, and mounts through the layout'sexisting index child, or a synthetic one);
routeOverridesmay give a pathlesslayout a real path (id-preservation is decided after overrides); an explicit
options.pathwins over a pathless id.createFileRoute('/(group)')previously gotpath: '/'(which never routed); it is now id-only. Existing unit test updated.path: '/_authed/dashboard') will 404; use the real URL (/dashboard).(layout)group route directly in a story) still fails onnext:path: '/'layouts never match their children and collide with sibling index routes. Commit 3 plus the regression tests/stories cover it; the sameid+pathinvariant fixed for plain options in Tanstack: Supply id OR path when using RouteOptions for route mock #34950 ([Bug]: @storybook/tanstack-react does not support route path #34946) is also guarded at the two remaining sites (overrides adding a path, explicitoptions.pathon a pathless id).Reviewing in chunks
The five commits are ordered so each is independently green and reviewable:
duplicateRouteTreeid preservation (safe standalone; code-based id-only routes exist today)createFileRoutemock (the core fix; depends on 1)Outletstories from [Bug]: @storybook/tanstack-react root layout not working #35007 (droppable)Tests assert real router matching (
router.state.matchesafterrouter.load())across code-based and file-based trees, including
createStoryRouter-level testsfor mount inference.
AI disclosure
Root-cause analysis, fix, and tests developed with Claude (Claude Code); reviewed and
submitted by me.
Manual testing
cd code/frameworks/tanstack-react && yarn vitest run(50 tests in this package's suite; verified green at every commit).PathlessLayouttemplate stories render a leaf under a pathless layout at the layout-free URL (withbeforeLoadexecuted) and a story bound directly to the pathless layout route.path: '/_authed/dashboard'workaround removed. Minimal repro available on request.