Routing Architecture Guidelines
🚨 CRITICAL: Unified Route Pattern
NEVER create separate /system/ routes. All routes must follow the unified /$orgSlug/* pattern.
❌ FORBIDDEN Patterns
// ❌ NEVER DO THIS - Separate system route tree
src/routes/system/
src/routes/system/organizations/
src/routes/system/users/
src/routes/system/dashboard/
// ❌ NEVER DO THIS - Hardcoded system routes
<Link to="/system/users">
navigate({ to: "/system/dashboard" })
✅ CORRECT Patterns
Route Structure
src/routes/$orgSlug/
├── index.tsx // Main dashboard (system OR org)
├── organizations.tsx // Organizations management
├── users.tsx // Users management
├── users/
│ ├── invite.tsx // User invitations (system feature)
│ └── audit.tsx // User audit (system feature)
├── funds.tsx
├── funds/setup.tsx // Fund setup (system feature)
├── fund-snapshots.tsx // Fund snapshots (system feature)
├── audit-logs.tsx // Audit logs (system feature)
├── market-data-sync.tsx // Market data sync (system feature)
└── settings/
├── integrations.tsx // System integrations
└── market-data.tsx // Market data config
System Context Detection
// ✅ CORRECT - Detect system context in routes
function MyRouteComponent() {
const { orgSlug } = Route.useParams();
const isSystem = orgSlug === "system";
if (isSystem) {
// Show system-specific content
return <SystemDashboard />;
}
// Show regular organization content
return <OrganizationDashboard />;
}
Navigation Links
// ✅ CORRECT - Always use unified pattern
<Link to="/$orgSlug/users">
<Link to="/$orgSlug/users/invite">
<Link to="/$orgSlug/organizations">
// ✅ CORRECT - Use navigate with unified pattern
const navigate = useNavigate();
void navigate({ to: "/$orgSlug/funds/setup" });
Sidebar Configuration
// ✅ CORRECT - System sections use unified pattern
const SYSTEM_SECTIONS: SidebarSection[] = [
{
title: "System Management",
icon: Settings,
links: [
{ title: "Users", to: "/$orgSlug/users" },
{ title: "User Invitations", to: "/$orgSlug/users/invite" },
{ title: "Audit Logs", to: "/$orgSlug/audit-logs" },
],
},
];
Context Detection Pattern
Route Component Pattern
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/$orgSlug/users')({
component: UsersPage,
})
function UsersPage() {
const { orgSlug } = Route.useParams();
const isSystem = orgSlug === "system";
const trpc = useTRPC();
// Use same tRPC procedure for both contexts
const usersQuery = trpc.org.users.list.queryOptions();
const { data: users } = useQuery(usersQuery);
return (
<div>
<h1>
{isSystem ? "System Users" : "Organization Users"}
</h1>
{isSystem && (
<div className="bg-blue-50 p-4 mb-4">
System-wide user management across all organizations
</div>
)}
<UsersTable users={users} isSystem={isSystem} />
</div>
);
}
Component Props Pattern
// ✅ CORRECT - Pass context to child components
<UsersTable users={users} isSystem={isSystem} />
// Child component handles different behavior
function UsersTable({ users, isSystem }: { users: User[], isSystem: boolean }) {
if (isSystem) {
// Show system-specific columns/features
return <SystemUsersTable users={users} />;
}
return <OrganizationUsersTable users={users} />;
}
tRPC Procedure Usage
Same tRPC procedures work for both contexts:
// ✅ CORRECT - Same procedure, different data based on context
const usersQuery = trpc.org.users.list.queryOptions();
// Backend returns different data based on ctx.user.isSystem
// System users get all users across orgs
// Regular users get users from their org only
When to Use System Features
System-only features (shown when orgSlug === "system"):
- Cross-organization management
- System-wide user invitations
- Audit logs across all organizations
- System settings and integrations
- Market data synchronization
- Fund setup wizards
Regular features (shown for any org):
- Organization-specific data
- Fund management within org
- Regular user management
- Organization settings
Migration Checklist
When refactoring from old /system/ pattern:
- ✅ Move
src/routes/system/*→src/routes/$orgSlug/* - ✅ Update all
<Link to="/system/*">→<Link to="/$orgSlug/*"> - ✅ Update all
navigate({ to: "/system/*" })→navigate({ to: "/$orgSlug/*" }) - ✅ Add
const { orgSlug } = Route.useParams(); const isSystem = orgSlug === "system"; - ✅ Add conditional rendering:
if (isSystem) { /* system content */ } - ✅ Update sidebar to use
SYSTEM_SECTIONSwhenisSystemOrgis true
Testing the Pattern
// Test that system routes work
import { render, screen } from '@testing-library/react'
import { createMemoryRouter } from '@tanstack/react-router'
test('system dashboard renders system content', async () => {
const router = createMemoryRouter([
{
path: '/$orgSlug',
component: () => {
const { orgSlug } = Route.useParams()
const isSystem = orgSlug === 'system'
return (
<div>
{isSystem ? <div>System Dashboard</div> : <div>Org Dashboard</div>}
</div>
)
}
}
], {
initialEntries: ['/system']
})
render(<RouterProvider router={router} />)
expect(screen.getByText('System Dashboard')).toBeInTheDocument()
})
Benefits of Unified Pattern
- Single Source of Truth: One route tree, one file structure
- Type Safety: TanStack Router handles type checking
- Consistent URLs: Same pattern for all contexts
- Easy Testing: One pattern to test
- Better UX: Clean, shareable URLs
- Simpler Navigation: No route conflicts or duplication
⚠️ Code Review Checklist
When reviewing PRs, check for:
- ❌ Any
/system/hardcoded routes - ❌ Separate
src/routes/system/directories - ❌ Missing system context detection
- ✅ All routes use
/$orgSlug/*pattern - ✅ System context detected via
orgSlug === "system" - ✅ Conditional rendering based on system context
- ✅ Sidebar shows correct sections for system context