Frontend

TanStack Router (Vite)

When you choose TanStack Router as your frontend framework, Grit scaffolds a Vite-powered React SPA with file-based routing, React Query, and Tailwind CSS. Fast builds, small bundles, no Node.js server needed.

Why TanStack Router?

Instant HMR

Vite provides sub-50ms hot module replacement. Changes appear instantly in the browser.

Small bundles

No server runtime overhead. The production output is static HTML + JS that any CDN can serve.

Type-safe routing

TanStack Router provides fully type-safe route params, search params, and loaders.

File-based routes

Routes auto-discovered by @tanstack/router-vite-plugin. No manual route registry needed.

Project structure

TanStack Router apps use src/routes/ for file-based routing instead of Next.js's app/ directory.

apps/web/ (TanStack Router)
apps/web/
├── src/
│ ├── routes/
│ │ ├── __root.tsx # Root layout (Navbar + Footer)
│ │ ├── index.tsx # Home page (/)
│ │ └── blog/
│ │ ├── index.tsx # Blog list (/blog)
│ │ └── $slug.tsx # Blog detail (/blog/:slug)
│ ├── components/
│ │ ├── navbar.tsx
│ │ └── footer.tsx
│ ├── hooks/
│ │ └── use-blogs.ts
│ ├── lib/
│ │ ├── api.ts # Axios client
│ │ └── utils.ts
│ ├── main.tsx # Entry point
│ └── globals.css
├── index.html
├── vite.config.ts # TanStack Router plugin + API proxy
├── tailwind.config.ts
└── package.json

Key differences from Next.js

AspectNext.jsTanStack Router
Routingapp/ directory conventionsrc/routes/ via Vite plugin
Layoutslayout.tsx__root.tsx + _layout.tsx
Build toolNext.js (webpack/turbopack)Vite
SSRBuilt-inSPA only (no SSR)
"use client"Required for client componentsNot needed (everything is client)
Dev servernext dev (:3000)vite dev (:3000)
Output.next/dist/
ParamsuseParams() from next/navigationRoute.useParams()
Navigation<Link> from next/link<Link> from @tanstack/react-router

Route examples

src/routes/__root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router'
import { Navbar } from '@/components/navbar'
import { Footer } from '@/components/footer'
export const Route = createRootRoute({
component: () => (
<div className="min-h-screen bg-background flex flex-col">
<Navbar />
<main className="flex-1">
<Outlet />
</main>
<Footer />
</div>
),
})
src/routes/blog/$slug.tsx
import { createFileRoute, Link } from '@tanstack/react-router'
import { useQuery } from '@tanstack/react-query'
import { api } from '@/lib/api'
export const Route = createFileRoute('/blog/$slug')({
component: BlogDetailPage,
})
function BlogDetailPage() {
const { slug } = Route.useParams()
const { data: blog } = useQuery({
queryKey: ['blog', slug],
queryFn: () => api.get('/api/blogs/' + slug).then(r => r.data.data),
})
return <h1>{blog?.title}</h1>
}

Admin panel with TanStack Router

When you choose TanStack Router, the admin panel also uses it. Auth and dashboard are handled via layout routes with beforeLoad guards.

src/routes/_dashboard.tsx
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
import { AdminLayout } from '@/components/layout/admin-layout'
export const Route = createFileRoute('/_dashboard')({
beforeLoad: () => {
const token = localStorage.getItem('access_token')
if (!token) {
throw redirect({ to: '/login' })
}
},
component: () => (
<AdminLayout>
<Outlet />
</AdminLayout>
),
})