7.0 KiB
7.0 KiB
Routing Guide
TanStack Router implementation with folder-based routing and lazy loading patterns.
TanStack Router Overview
TanStack Router with file-based routing:
- Folder structure defines routes
- Lazy loading for code splitting
- Type-safe routing
- Breadcrumb loaders
Folder-Based Routing
Directory Structure
routes/
__root.tsx # Root layout
index.tsx # Home route (/)
posts/
index.tsx # /posts
create/
index.tsx # /posts/create
$postId.tsx # /posts/:postId (dynamic)
comments/
index.tsx # /comments
Pattern:
index.tsx= Route at that path$param.tsx= Dynamic parameter- Nested folders = Nested routes
Basic Route Pattern
Example from posts/index.tsx
/**
* Posts route component
* Displays the main blog posts list
*/
import { createFileRoute } from '@tanstack/react-router';
import { lazy } from 'react';
// Lazy load the page component
const PostsList = lazy(() =>
import('@/features/posts/components/PostsList').then(
(module) => ({ default: module.PostsList }),
),
);
export const Route = createFileRoute('/posts/')({
component: PostsPage,
// Define breadcrumb data
loader: () => ({
crumb: 'Posts',
}),
});
function PostsPage() {
return (
<PostsList
title='All Posts'
showFilters={true}
/>
);
}
export default PostsPage;
Key Points:
- Lazy load heavy components
createFileRoutewith route pathloaderfor breadcrumb data- Page component renders content
- Export both Route and component
Lazy Loading Routes
Named Export Pattern
import { lazy } from 'react';
// For named exports, use .then() to map to default
const MyPage = lazy(() =>
import('@/features/my-feature/components/MyPage').then(
(module) => ({ default: module.MyPage })
)
);
Default Export Pattern
import { lazy } from 'react';
// For default exports, simpler syntax
const MyPage = lazy(() => import('@/features/my-feature/components/MyPage'));
Why Lazy Load Routes?
- Code splitting - smaller initial bundle
- Faster initial page load
- Load route code only when navigated to
- Better performance
createFileRoute
Basic Configuration
export const Route = createFileRoute('/my-route/')({
component: MyRoutePage,
});
function MyRoutePage() {
return <div>My Route Content</div>;
}
With Breadcrumb Loader
export const Route = createFileRoute('/my-route/')({
component: MyRoutePage,
loader: () => ({
crumb: 'My Route Title',
}),
});
Breadcrumb appears in navigation/app bar automatically.
With Data Loader
export const Route = createFileRoute('/my-route/')({
component: MyRoutePage,
loader: async () => {
// Can prefetch data here
const data = await api.getData();
return { crumb: 'My Route', data };
},
});
With Search Params
export const Route = createFileRoute('/search/')({
component: SearchPage,
validateSearch: (search: Record<string, unknown>) => {
return {
query: (search.query as string) || '',
page: Number(search.page) || 1,
};
},
});
function SearchPage() {
const { query, page } = Route.useSearch();
// Use query and page
}
Dynamic Routes
Parameter Routes
// routes/users/$userId.tsx
export const Route = createFileRoute('/users/$userId')({
component: UserPage,
});
function UserPage() {
const { userId } = Route.useParams();
return <UserProfile userId={userId} />;
}
Multiple Parameters
// routes/posts/$postId/comments/$commentId.tsx
export const Route = createFileRoute('/posts/$postId/comments/$commentId')({
component: CommentPage,
});
function CommentPage() {
const { postId, commentId } = Route.useParams();
return <CommentEditor postId={postId} commentId={commentId} />;
}
Navigation
Programmatic Navigation
import { useNavigate } from '@tanstack/react-router';
export const MyComponent: React.FC = () => {
const navigate = useNavigate();
const handleClick = () => {
navigate({ to: '/posts' });
};
return <Button onClick={handleClick}>View Posts</Button>;
};
With Parameters
const handleNavigate = () => {
navigate({
to: '/users/$userId',
params: { userId: '123' },
});
};
With Search Params
const handleSearch = () => {
navigate({
to: '/search',
search: { query: 'test', page: 1 },
});
};
Route Layout Pattern
Root Layout (__root.tsx)
import { createRootRoute, Outlet } from '@tanstack/react-router';
import { Box } from '@mui/material';
import { CustomAppBar } from '~components/CustomAppBar';
export const Route = createRootRoute({
component: RootLayout,
});
function RootLayout() {
return (
<Box>
<CustomAppBar />
<Box sx={{ p: 2 }}>
<Outlet /> {/* Child routes render here */}
</Box>
</Box>
);
}
Nested Layouts
// routes/dashboard/index.tsx
export const Route = createFileRoute('/dashboard/')({
component: DashboardLayout,
});
function DashboardLayout() {
return (
<Box>
<DashboardSidebar />
<Box sx={{ flex: 1 }}>
<Outlet /> {/* Nested routes */}
</Box>
</Box>
);
}
Complete Route Example
/**
* User profile route
* Path: /users/:userId
*/
import { createFileRoute } from '@tanstack/react-router';
import { lazy } from 'react';
import { SuspenseLoader } from '~components/SuspenseLoader';
// Lazy load heavy component
const UserProfile = lazy(() =>
import('@/features/users/components/UserProfile').then(
(module) => ({ default: module.UserProfile })
)
);
export const Route = createFileRoute('/users/$userId')({
component: UserPage,
loader: () => ({
crumb: 'User Profile',
}),
});
function UserPage() {
const { userId } = Route.useParams();
return (
<SuspenseLoader>
<UserProfile userId={userId} />
</SuspenseLoader>
);
}
export default UserPage;
Summary
Routing Checklist:
- ✅ Folder-based:
routes/my-route/index.tsx - ✅ Lazy load components:
React.lazy(() => import()) - ✅ Use
createFileRoutewith route path - ✅ Add breadcrumb in
loaderfunction - ✅ Wrap in
SuspenseLoaderfor loading states - ✅ Use
Route.useParams()for dynamic params - ✅ Use
useNavigate()for programmatic navigation
See Also:
- component-patterns.md - Lazy loading patterns
- loading-and-error-states.md - SuspenseLoader usage
- complete-examples.md - Full route examples