| 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. |
| type | core |
| library | tanstack-router |
| library_version | 1.161.4 |
| 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.
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')])