Frontend
TanStack Router
Router type-safe moderno para React com nested routing e code splitting
O que é TanStack Router?
TanStack Router é um roteador type-safe para React que oferece nested routing, code splitting automático, search params tipados e excelente performance. Desenvolvido pela mesma equipe do TanStack Query.
Por que utilizamos TanStack Router na IngenioLab?
- Type Safety completa: Rotas, params e search tipados
- Nested routing: Layouts aninhados naturalmente
- Code splitting automático: Lazy loading integrado
- Performance: Otimizações automáticas
- DevTools: Debugging avançado
- File-based routing: Organização baseada em arquivos
Instalação
npm install @tanstack/react-routernpm install -D @tanstack/router-vite-plugin @tanstack/router-devtools
Configuração Base
1. Vite Plugin:
// vite.config.tsimport { defineConfig } from 'vite'import react from '@vitejs/plugin-react'import { TanStackRouterVite } from '@tanstack/router-vite-plugin'export default defineConfig({plugins: [react(),TanStackRouterVite()]})
2. Estrutura de rotas:
src/routes/├── __root.tsx # Layout raiz├── index.tsx # Página inicial (/)├── about.tsx # /about├── users/│ ├── index.tsx # /users│ └── $userId.tsx # /users/$userId└── dashboard/├── _layout.tsx # Layout para /dashboard/*├── index.tsx # /dashboard└── settings.tsx # /dashboard/settings
3. Root Route:
// src/routes/__root.tsximport { createRootRoute, Link, Outlet } from '@tanstack/react-router'import { TanStackRouterDevtools } from '@tanstack/router-devtools'export const Route = createRootRoute({component: () => (<><div className="p-2 flex gap-2"><Link to="/" className="[&.active]:font-bold">Home</Link><Link to="/about" className="[&.active]:font-bold">About</Link><Link to="/users" className="[&.active]:font-bold">Users</Link></div><hr /><Outlet /><TanStackRouterDevtools /></>)})
4. Configuração principal:
// src/main.tsximport { StrictMode } from 'react'import ReactDOM from 'react-dom/client'import { RouterProvider, createRouter } from '@tanstack/react-router'// Import the generated route treeimport { routeTree } from './routeTree.gen'// Create a new router instanceconst router = createRouter({ routeTree })// Register the router instance for type safetydeclare module '@tanstack/react-router' {interface Register {router: typeof router}}// Render the appconst rootElement = document.getElementById('root')!if (!rootElement.innerHTML) {const root = ReactDOM.createRoot(rootElement)root.render(<StrictMode><RouterProvider router={router} /></StrictMode>)}
Rotas Básicas
1. Página simples:
// src/routes/about.tsximport { createFileRoute } from '@tanstack/react-router'export const Route = createFileRoute('/about')({component: About})function About() {return (<div className="p-2"><h3>About</h3><p>Sobre a IngenioLab</p></div>)}
2. Rota com parâmetros:
// src/routes/users/$userId.tsximport { createFileRoute } from '@tanstack/react-router'import { useUser } from '@/hooks/useUsers'export const Route = createFileRoute('/users/$userId')({component: UserDetail})function UserDetail() {const { userId } = Route.useParams()const { data: user, isLoading } = useUser(userId)if (isLoading) return <div>Loading...</div>return (<div className="p-2"><h3>User: {user?.name}</h3><p>Email: {user?.email}</p></div>)}
3. Rota com Search Params:
// src/routes/users/index.tsximport { createFileRoute } from '@tanstack/react-router'import { z } from 'zod'const usersSearchSchema = z.object({page: z.number().catch(1),search: z.string().optional(),role: z.enum(['admin', 'user']).optional()})export const Route = createFileRoute('/users/')({component: Users,validateSearch: usersSearchSchema})function Users() {const { page, search, role } = Route.useSearch()const navigate = Route.useNavigate()const updateSearch = (newSearch: Partial<typeof search>) => {navigate({search: (prev) => ({ ...prev, ...newSearch })})}return (<div><inputvalue={search || ''}onChange={(e) => updateSearch({ search: e.target.value })}placeholder="Search users..."/><selectvalue={role || ''}onChange={(e) => updateSearch({ role: e.target.value as any })}><option value="">All roles</option><option value="admin">Admin</option><option value="user">User</option></select>{/* Lista de usuários baseada nos filtros */}</div>)}
Layouts Aninhados
1. Layout Dashboard:
// src/routes/dashboard/_layout.tsximport { createFileRoute, Link, Outlet } from '@tanstack/react-router'export const Route = createFileRoute('/dashboard/_layout')({component: DashboardLayout})function DashboardLayout() {return (<div className="dashboard-layout"><aside className="sidebar"><nav><Link to="/dashboard" className="[&.active]:font-bold">Overview</Link><Link to="/dashboard/settings" className="[&.active]:font-bold">Settings</Link></nav></aside><main className="main-content"><Outlet /></main></div>)}
2. Páginas do Dashboard:
// src/routes/dashboard/index.tsximport { createFileRoute } from '@tanstack/react-router'export const Route = createFileRoute('/dashboard/')({component: Dashboard})function Dashboard() {return (<div><h1>Dashboard</h1><p>Overview do sistema</p></div>)}
Data Loading
1. Loader com TanStack Query:
// src/routes/users/$userId.tsximport { createFileRoute, notFound } from '@tanstack/react-router'import { queryOptions } from '@tanstack/react-query'import { userService } from '@/services/userService'const userQueryOptions = (userId: string) =>queryOptions({queryKey: ['users', userId],queryFn: () => userService.getById(userId)})export const Route = createFileRoute('/users/$userId')({component: UserDetail,loader: ({ context: { queryClient }, params: { userId } }) =>queryClient.ensureQueryData(userQueryOptions(userId)),errorComponent: ({ error }) => <div>Error: {error.message}</div>,notFoundComponent: () => <div>User not found</div>})function UserDetail() {const { userId } = Route.useParams()const user = Route.useLoaderData()return (<div><h1>{user.name}</h1><p>{user.email}</p></div>)}
2. Configurar Query Client:
// src/main.tsximport { QueryClient, QueryClientProvider } from '@tanstack/react-query'import { createRouter, RouterProvider } from '@tanstack/react-router'import { routeTree } from './routeTree.gen'const queryClient = new QueryClient()const router = createRouter({routeTree,context: {queryClient}})declare module '@tanstack/react-router' {interface Register {router: typeof router}}function App() {return (<QueryClientProvider client={queryClient}><RouterProvider router={router} /></QueryClientProvider>)}
Autenticação e Guards
1. Auth Context:
// src/auth.tsximport { createContext, useContext } from 'react'interface AuthContext {user: User | nulllogin: (email: string, password: string) => Promise<void>logout: () => voidisAuthenticated: boolean}const authContext = createContext<AuthContext | null>(null)export function useAuth() {const context = useContext(authContext)if (!context) throw new Error('useAuth must be used within AuthProvider')return context}
2. Route Guards:
// src/routes/_authenticated.tsximport { createFileRoute, redirect } from '@tanstack/react-router'import { useAuth } from '@/auth'export const Route = createFileRoute('/_authenticated')({component: AuthenticatedLayout,beforeLoad: ({ context, location }) => {if (!context.auth.isAuthenticated) {throw redirect({to: '/login',search: {redirect: location.href}})}}})function AuthenticatedLayout() {return <Outlet />}
3. Rotas protegidas:
// src/routes/_authenticated/dashboard.tsximport { createFileRoute } from '@tanstack/react-router'export const Route = createFileRoute('/_authenticated/dashboard')({component: Dashboard})function Dashboard() {const { user } = useAuth()return (<div><h1>Welcome, {user?.name}</h1></div>)}
Navigation
1. Programmatic Navigation:
// src/components/UserActions.tsximport { useRouter, useNavigate } from '@tanstack/react-router'export const UserActions = ({ userId }: { userId: string }) => {const router = useRouter()const navigate = useNavigate()const goToUser = () => {navigate({to: '/users/$userId',params: { userId }})}const goToEdit = () => {navigate({to: '/users/$userId/edit',params: { userId },search: { tab: 'profile' }})}const goBack = () => {router.history.back()}return (<div><button onClick={goToUser}>View User</button><button onClick={goToEdit}>Edit User</button><button onClick={goBack}>Go Back</button></div>)}
2. Link Component:
// src/components/Navigation.tsximport { Link } from '@tanstack/react-router'export const Navigation = () => {return (<nav><Linkto="/users/$userId"params={{ userId: '123' }}activeOptions={{ exact: true }}className="[&.active]:font-bold">User Detail</Link><Linkto="/users"search={{ page: 1, role: 'admin' }}className="[&.active]:font-bold">Admin Users</Link></nav>)}
Code Splitting
1. Lazy Loading automático:
// src/routes/heavy-page.lazy.tsximport { createLazyFileRoute } from '@tanstack/react-router'export const Route = createLazyFileRoute('/heavy-page')({component: HeavyPage})function HeavyPage() {return (<div><h1>Heavy Page</h1>{/* Componente pesado que será carregado sob demanda */}</div>)}
2. Loading States:
// src/routes/__root.tsxexport const Route = createRootRoute({component: RootComponent,pendingComponent: () => <div>Loading...</div>,errorComponent: ({ error }) => (<div>Something went wrong: {error.message}</div>)})
Formulários e Actions
1. Form Actions:
// src/routes/users/create.tsximport { createFileRoute, redirect } from '@tanstack/react-router'import { userService } from '@/services/userService'export const Route = createFileRoute('/users/create')({component: CreateUser,action: async ({ request }) => {const formData = await request.formData()const userData = Object.fromEntries(formData)const newUser = await userService.create(userData)throw redirect({to: '/users/$userId',params: { userId: newUser.id }})}})function CreateUser() {return (<form method="post"><input name="name" placeholder="Name" required /><input name="email" type="email" placeholder="Email" required /><button type="submit">Create User</button></form>)}
Error Handling
1. Error Boundaries por rota:
// src/routes/users.tsximport { createFileRoute, ErrorComponent } from '@tanstack/react-router'export const Route = createFileRoute('/users')({component: Users,errorComponent: ({ error, reset }) => (<div><h2>Error loading users</h2><p>{error.message}</p><button onClick={reset}>Try again</button></div>)})
2. Not Found personalizado:
// src/routes/users/$userId.tsxexport const Route = createFileRoute('/users/$userId')({component: UserDetail,notFoundComponent: () => (<div><h2>User not found</h2><Link to="/users">Back to users</Link></div>)})
TypeScript Integration
1. Tipos gerados automaticamente:
// Os tipos são gerados automaticamente pelo plugin// Não precisa definir manualmente// Uso com type safety completaconst navigate = useNavigate()navigate({to: '/users/$userId', // Autocomplete e verificaçãoparams: { userId: '123' }, // Tipadosearch: { page: 1 } // Tipado baseado no schema})
2. Custom Router Types:
// src/router.types.tsdeclare module '@tanstack/react-router' {interface Register {router: typeof router}interface StaticDataRouteOption {title?: stringdescription?: string}}
DevTools e Debug
1. Router DevTools:
// src/routes/__root.tsximport { TanStackRouterDevtools } from '@tanstack/router-devtools'export const Route = createRootRoute({component: () => (<><Outlet />{process.env.NODE_ENV === 'development' && (<TanStackRouterDevtools />)}</>)})