| name | virtual-file-routes |
| description | Programmatic route tree building as an alternative to filesystem conventions: rootRoute, index, route, layout, physical, defineVirtualSubtreeConfig. Use with TanStack Router plugin's virtualRouteConfig option. |
| metadata | {"type":"core","library":"tanstack-router","library_version":"1.162.0"} |
| sources | ["TanStack/router:packages/virtual-file-routes/src","TanStack/router:docs/router/routing/virtual-file-routes.md"] |
Virtual File Routes (@tanstack/virtual-file-routes)
Build route trees programmatically instead of relying on filesystem conventions. Useful when you want explicit control over route structure, need to mix virtual and physical routes, or want to define route subtrees within file-based routing directories.
Use this skill only when the project config contains virtualRouteConfig, a virtual route config file, or __virtual.ts. Ordinary file-route changes belong to Router Core and the Router Plugin.
CRITICAL: Types are FULLY INFERRED. Never cast, never annotate inferred values.
Install
npm install @tanstack/virtual-file-routes
API Reference
rootRoute(file, children?)
Creates the root of a virtual route tree.
import { rootRoute, index, route } from '@tanstack/virtual-file-routes'
const routes = rootRoute('root.tsx', [
index('index.tsx'),
route('/about', 'about.tsx'),
])
index(file)
Creates an index route — the default rendered when the parent path matches exactly.
import { index } from '@tanstack/virtual-file-routes'
index('home.tsx')
route(path, ...)
Creates a route node. Three call signatures:
import { route, index } from '@tanstack/virtual-file-routes'
route('/about', 'about.tsx')
route('/dashboard', 'dashboard.tsx', [
index('dashboard-index.tsx'),
route('/settings', 'settings.tsx'),
])
route('/api', [route('/users', 'users.tsx'), route('/posts', 'posts.tsx')])
layout(file, children) or layout(id, file, children)
Creates a pathless layout route — wraps children without adding a URL segment.
import { layout, route, index } from '@tanstack/virtual-file-routes'
layout('authLayout.tsx', [
route('/dashboard', 'dashboard.tsx'),
route('/settings', 'settings.tsx'),
])
layout('admin-layout', 'adminLayout.tsx', [route('/admin', 'admin.tsx')])
physical(pathPrefix, directory) or physical(directory)
Mounts a directory of file-based routes at a URL prefix. Uses TanStack Router's standard file-based routing conventions within that directory.
import { physical } from '@tanstack/virtual-file-routes'
physical('/posts', 'posts')
physical('features')
defineVirtualSubtreeConfig(config)
Type helper for __virtual.ts files inside file-based routing directories. Identity function that provides type inference.
import {
defineVirtualSubtreeConfig,
index,
route,
} from '@tanstack/virtual-file-routes'
export default defineVirtualSubtreeConfig([
index('home.tsx'),
route('$id', 'details.tsx'),
])
Integration with Router Plugin
Pass the virtual route config to the TanStack Router plugin:
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
import { routes } from './routes'
export default defineConfig({
plugins: [
tanstackRouter({
target: 'react',
virtualRouteConfig: routes,
}),
],
})
Or reference a file path:
tanstackRouter({
target: 'react',
virtualRouteConfig: './routes.ts',
})
Full Example
import {
rootRoute,
route,
index,
layout,
physical,
} from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
index('index.tsx'),
layout('authLayout.tsx', [
route('/dashboard', 'app/dashboard.tsx', [
index('app/dashboard-index.tsx'),
route('/invoices', 'app/dashboard-invoices.tsx', [
index('app/invoices-index.tsx'),
route('$id', 'app/invoice-detail.tsx'),
]),
]),
]),
physical('/posts', 'posts'),
])
Common Mistakes
1. HIGH: Forgetting that file paths are relative to routesDirectory
File paths in rootRoute, index, route, and layout are relative to the routesDirectory configured in the router plugin (default: ./src/routes). Do not use absolute paths or paths relative to the project root.
route('/about', '/src/routes/about.tsx')
route('/about', 'about.tsx')
2. MEDIUM: Using physical() without matching directory structure
The directory passed to physical() must exist inside routesDirectory and follow TanStack Router's file-based routing conventions.
physical('/blog', 'src/blog')
physical('/blog', 'blog')
3. MEDIUM: Confusing layout() with route()
layout() creates a pathless wrapper — it does NOT add a URL segment. Use route() for URL segments.
layout('dashboardLayout.tsx', [route('/dashboard', 'dashboard.tsx')])
Refactor Audit
After changing a virtual tree:
- Confirm there is exactly one
rootRoute and that every referenced file exists relative to routesDirectory.
- Check each
route, layout, and physical node for duplicate effective paths or IDs.
- Confirm each
physical() directory is mounted once at the intended path prefix and does not overlap a virtual child.
- Regenerate
routeTree.gen.ts and inspect parentage, full paths, and imports. Do not edit generated output.
- Update links, redirects, params, and
from narrowing, then run generator tests, type tests, and a production build.