Files
2026-03-02 21:16:26 +01:00

134 lines
3.9 KiB
Markdown

# org-virtual-routes: Understand Virtual File Routes
## Priority: LOW
## Explanation
Virtual routes are automatically generated placeholder routes in the route tree when you have a `.lazy.tsx` file without a corresponding main route file. They provide the minimal configuration needed to anchor lazy-loaded components.
## Bad Example
```tsx
// Creating unnecessary boilerplate main route files
// routes/settings.tsx - Just to have a file
export const Route = createFileRoute('/settings')({
// Empty - no loader, no beforeLoad, nothing
})
// routes/settings.lazy.tsx - Actual component
export const Route = createLazyFileRoute('/settings')({
component: SettingsPage,
})
// The main file is unnecessary boilerplate
```
## Good Example: Let Virtual Routes Handle It
```tsx
// Delete routes/settings.tsx entirely!
// routes/settings.lazy.tsx - Only file needed
export const Route = createLazyFileRoute('/settings')({
component: SettingsPage,
pendingComponent: SettingsLoading,
errorComponent: SettingsError,
})
function SettingsPage() {
return <div>Settings Content</div>
}
// TanStack Router auto-generates a virtual route:
// {
// path: '/settings',
// // Minimal config to anchor the lazy file
// }
```
## Good Example: When You DO Need Main Route File
```tsx
// routes/dashboard.tsx - Need this for loader/beforeLoad
export const Route = createFileRoute('/dashboard')({
beforeLoad: async ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login' })
}
},
loader: async ({ context: { queryClient } }) => {
await queryClient.ensureQueryData(dashboardQueries.stats())
},
// Component is in lazy file
})
// routes/dashboard.lazy.tsx
export const Route = createLazyFileRoute('/dashboard')({
component: DashboardPage,
pendingComponent: DashboardSkeleton,
})
// Main file IS needed here because we have loader/beforeLoad
```
## Decision Guide
| Route Has... | Need Main File? | Use Virtual? |
|--------------|-----------------|--------------|
| Only component | No | Yes |
| loader | Yes | No |
| beforeLoad | Yes | No |
| validateSearch | Yes | No |
| loaderDeps | Yes | No |
| Just pendingComponent/errorComponent | No | Yes |
## Good Example: File Structure with Virtual Routes
```
routes/
├── __root.tsx # Always needed
├── index.tsx # Has loader
├── about.lazy.tsx # Virtual route (no main file)
├── contact.lazy.tsx # Virtual route (no main file)
├── dashboard.tsx # Has beforeLoad (auth)
├── dashboard.lazy.tsx # Component
├── posts.tsx # Has loader
├── posts.lazy.tsx # Component
├── posts/
│ ├── $postId.tsx # Has loader
│ └── $postId.lazy.tsx # Component
└── settings/
├── index.lazy.tsx # Virtual route
├── profile.lazy.tsx # Virtual route
└── security.tsx # Has beforeLoad (requires re-auth)
```
## Good Example: Generated Route Tree
```tsx
// routeTree.gen.ts (auto-generated)
import { Route as rootRoute } from './routes/__root'
import { Route as aboutLazyRoute } from './routes/about.lazy' // Virtual parent
export const routeTree = rootRoute.addChildren([
// Virtual route created for about.lazy.tsx
createRoute({
path: '/about',
getParentRoute: () => rootRoute,
}).lazy(() => import('./routes/about.lazy').then(m => m.Route)),
// Regular route with explicit main file
dashboardRoute.addChildren([...]),
])
```
## Context
- Virtual routes reduce boilerplate for simple pages
- Only works with file-based routing
- Auto-generated in `routeTree.gen.ts`
- Main route file needed for any "critical path" config
- Critical: loader, beforeLoad, validateSearch, loaderDeps, context
- Non-critical (can be in lazy): component, pendingComponent, errorComponent
- Check generated route tree to verify virtual routes