Repository navigation
Protect application routes with shared layout - #47
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #39
TLDR explanation of the problem
When multiple pages need the same functionality—such as redirecting anonymous users to the welcome page—we can place them under a TanStack Router pathless layout route. The parent route wraps its child routes with shared access control and layout behavior. This is called component composition. Any new route added as a child automatically receives the same protection and application layout without changing its URL.
The problem
GoForLift currently has four routes in its main application area:
/dashboard/routines/history/settingsEvery one of these pages needs the same two things:
Putting those responsibilities inside each page would create repetition and make it possible for a future application page to accidentally omit its access check or shared layout.
Route structure
This PR introduces a TanStack Router pathless layout route:
The route files are:
In TanStack Router file-based routing, the underscore prefix means
_apporganizes and wraps its child routes without addingappto the URL. The dot establishes the parent-child relationship.Therefore,
_app.dashboard.tsxstill resolves to/dashboard, not/_app/dashboardor/app/dashboard.How the parent layout works
_app.tsxowns the behavior shared by every application page:<Outlet />is TanStack Router's placeholder for the currently matched child page. At/dashboard, it renders the dashboard content. At/routines, it renders the routines content. The access boundary and application shell remain the common parent around either child.This is component composition: the parent route wraps each child route with shared behavior.
Access behavior
The boundary resolves access in this order:
/.Checking your session…rather than briefly showing protected content.Try againaction rather than incorrectly treating a server failure as logout.TanStack Query continues to cache the current-user response, so navigating among child routes does not inherently require a new API request every time.
Why this design
Testing
Local verification:
Shared test fixtures
A follow-up scan found stable test data repeated across multiple files. This PR now centralizes:
Factories accept
Partial<T>overrides so individual tests show only the variation relevant to their scenario. One-off values and behavior-defining data remain local so tests stay understandable.The web and API public-user factories intentionally remain separate. They share the
PublicUsercontract through@goforlift/contracts, but their scenario defaults belong to different application boundaries and may evolve independently. A shared cross-package fixture would add coupling and package configuration without enough current reuse to justify it.The convention is recorded once in
AGENTS.md: extract stable data repeated across test files, keep scenario-specific values local, and avoid sharing fixtures across unrelated boundaries.