Add Page#

This project uses TanStack Start file-based routing: any .tsx under src/routes/ becomes a route, and the file path determines the URL.

Create a Page#

Add a file under src/routes/_app/:

tsx// src/routes/_app/todos.tsx
import { useEffect, useState } from "react"
import { createFileRoute } from "@tanstack/react-router"
import { http } from "@/lib/http"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"

interface Todo { id: string; title: string }

function TodosPage() {
  const [todos, setTodos] = useState<Todo[]>([])
  const [title, setTitle] = useState("")

  const fetchTodos = async () => {
    const res = await http.get<Todo[]>("/v1/todos/list")
    if (res.success && res.data) setTodos(res.data)
  }

  const addTodo = async () => {
    if (!title) return
    const res = await http.post("/v1/todos/create", { title })
    if (res.success) {
      setTitle("")
      void fetchTodos()
    }
  }

  useEffect(() => { void fetchTodos() }, [])

  return (
    <div className="space-y-4">
      <h1 className="text-2xl font-bold">Todos</h1>
      <div className="flex gap-2 max-w-md">
        <Input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="New todo" />
        <Button onClick={addTodo}>Add</Button>
      </div>
      <div className="space-y-2">
        {todos.map((t) => (
          <div key={t.id} className="p-3 bg-card rounded border">{t.title}</div>
        ))}
      </div>
    </div>
  )
}

export const Route = createFileRoute("/_app/todos")({ component: TodosPage })

Vite regenerates the route tree (routeTree.gen.ts) on save — no manual registration.

⚠️ The path in createFileRoute("/_app/todos") must match the file path; otherwise the build fails.

Route Groups#

DirectoryURLPurpose
_app/.../dashboard, /profile, ...Authenticated dashboard with sidebar
_app/owner/.../owner/users, ...Owner-only admin pages
en/..., zh/.../en/about, /zh/docs, ...Public marketing site
v1/.../v1/users/me, ...API Server Routes
Top-level signin.tsx, signup.tsx/signin, /signupPublic pages

Auth Protection#

src/routes/_app/route.tsx wraps the whole group in <RequireAuth>, so any page under _app/ is automatically auth-gated — no per-page boilerplate. Unauthenticated users are redirected to /signin.

tsx// src/routes/_app/route.tsx (excerpt)
<RequireAuth>
  <SidebarProvider>
    <AppSidebar />
    <SidebarInset>
      <Topbar />
      <Outlet />   {/* ← child pages render here */}
    </SidebarInset>
  </SidebarProvider>
</RequireAuth>

Admin Pages#

Place admin pages under src/routes/_app/owner/ (e.g. createFileRoute("/_app/owner/users")). The sidebar filters menu items by user.role === "owner" so regular users don't see the Owner entries.