├── .env.example ├── src ├── database │ ├── schema.ts │ └── db.ts ├── lib │ ├── auth-client.ts │ ├── utils.ts │ └── auth.ts ├── routes │ ├── index.tsx │ ├── api │ │ └── auth │ │ │ └── $.ts │ ├── auth │ │ └── $path.tsx │ ├── account │ │ └── $path.tsx │ └── __root.tsx ├── router.tsx ├── styles │ ├── custom.css │ └── styles.css ├── components │ ├── meta-theme.ts │ ├── providers.tsx │ ├── mode-toggle.tsx │ ├── header.tsx │ └── ui │ │ ├── button.tsx │ │ └── dropdown-menu.tsx ├── routeTree.gen.ts └── logo.svg ├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── tanstack-circle-logo.png ├── manifest.json └── tanstack-word-logo-white.svg ├── .gitignore ├── components.json ├── vite.config.ts ├── tsconfig.json ├── biome.json ├── auth-schema.ts ├── package.json └── README.md /.env.example: -------------------------------------------------------------------------------- 1 | BETTER_AUTH_SECRET="" 2 | DATABASE_URL="" -------------------------------------------------------------------------------- /src/database/schema.ts: -------------------------------------------------------------------------------- 1 | export * from "@/../auth-schema" 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveyplate/better-auth-tanstack-starter/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveyplate/better-auth-tanstack-starter/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveyplate/better-auth-tanstack-starter/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/database/db.ts: -------------------------------------------------------------------------------- 1 | import { drizzle } from "drizzle-orm/node-postgres" 2 | export const db = drizzle(process.env.DATABASE_URL!) 3 | -------------------------------------------------------------------------------- /public/tanstack-circle-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daveyplate/better-auth-tanstack-starter/HEAD/public/tanstack-circle-logo.png -------------------------------------------------------------------------------- /src/lib/auth-client.ts: -------------------------------------------------------------------------------- 1 | import { createAuthClient } from "better-auth/react" 2 | 3 | export const authClient = createAuthClient({}) 4 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | count.txt 7 | .env 8 | .nitro 9 | .tanstack 10 | .wrangler 11 | .output 12 | .vinxi 13 | todos.json 14 | .vscode 15 | .vercel -------------------------------------------------------------------------------- /src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router" 2 | 3 | export const Route = createFileRoute("/")({ component: IndexPage }) 4 | 5 | function IndexPage() { 6 | return ( 7 |
8 |

Hello, world.

9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/router.tsx: -------------------------------------------------------------------------------- 1 | import { createRouter } from "@tanstack/react-router" 2 | 3 | // Import the generated route tree 4 | import { routeTree } from "./routeTree.gen" 5 | 6 | // Create a new router instance 7 | export const getRouter = () => { 8 | return createRouter({ 9 | routeTree, 10 | scrollRestoration: true, 11 | defaultPreloadStaleTime: 0 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/auth.ts: -------------------------------------------------------------------------------- 1 | import { betterAuth } from "better-auth" 2 | import { drizzleAdapter } from "better-auth/adapters/drizzle" 3 | 4 | import { db } from "@/database/db" 5 | import * as schema from "@/database/schema" 6 | 7 | export const auth = betterAuth({ 8 | database: drizzleAdapter(db, { 9 | provider: "pg", 10 | usePlural: true, 11 | schema 12 | }), 13 | emailAndPassword: { 14 | enabled: true 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /src/routes/api/auth/$.ts: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router" 2 | import { auth } from "@/lib/auth" 3 | 4 | export const Route = createFileRoute("/api/auth/$")({ 5 | server: { 6 | handlers: { 7 | GET: ({ request }) => { 8 | return auth.handler(request) 9 | }, 10 | POST: ({ request }) => { 11 | return auth.handler(request) 12 | } 13 | } 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /src/routes/auth/$path.tsx: -------------------------------------------------------------------------------- 1 | import { AuthView } from "@daveyplate/better-auth-ui" 2 | import { createFileRoute } from "@tanstack/react-router" 3 | 4 | export const Route = createFileRoute("/auth/$path")({ 5 | component: RouteComponent 6 | }) 7 | 8 | function RouteComponent() { 9 | const { path } = Route.useParams() 10 | 11 | return ( 12 |
13 | 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/styles.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /src/routes/account/$path.tsx: -------------------------------------------------------------------------------- 1 | import { AccountView } from "@daveyplate/better-auth-ui" 2 | import { createFileRoute } from "@tanstack/react-router" 3 | 4 | export const Route = createFileRoute("/account/$path")({ 5 | component: RouteComponent 6 | }) 7 | 8 | function RouteComponent() { 9 | const { path } = Route.useParams() 10 | 11 | return ( 12 |
13 | 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "TanStack App", 3 | "name": "Create TanStack App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | // import { cloudflare } from "@cloudflare/vite-plugin" 2 | import tailwindcss from "@tailwindcss/vite" 3 | import { nitroV2Plugin } from "@tanstack/nitro-v2-vite-plugin" 4 | import { tanstackStart } from "@tanstack/react-start/plugin/vite" 5 | import viteReact from "@vitejs/plugin-react" 6 | import { defineConfig } from "vite" 7 | import devtoolsJson from "vite-plugin-devtools-json" 8 | import viteTsConfigPaths from "vite-tsconfig-paths" 9 | 10 | const config = defineConfig({ 11 | plugins: [ 12 | viteTsConfigPaths({ 13 | projects: ["./tsconfig.json"] 14 | }), 15 | tailwindcss(), 16 | tanstackStart(), 17 | nitroV2Plugin({ preset: "vercel" }), 18 | viteReact(), 19 | devtoolsJson() 20 | ] 21 | }) 22 | 23 | export default config 24 | -------------------------------------------------------------------------------- /src/styles/custom.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss-safe-area"; 2 | 3 | @import "@daveyplate/better-auth-ui/css"; 4 | 5 | @layer base { 6 | button:not(:disabled), 7 | [role="button"]:not(:disabled) { 8 | cursor: pointer; 9 | } 10 | } 11 | 12 | [role="menuitem"]:not(:disabled) { 13 | cursor: pointer; 14 | } 15 | 16 | :root { 17 | --warning: hsl(38 92% 50%); 18 | --warning-foreground: hsl(48 96% 89%); 19 | } 20 | 21 | .dark { 22 | --warning: hsl(48 96% 89%); 23 | --warning-foreground: hsl(38 92% 50%); 24 | } 25 | 26 | @theme inline { 27 | --color-warning: var(--warning); 28 | --color-warning-foreground: var(--warning-foreground); 29 | } 30 | 31 | /** iOS Dynamic System Font Scaling */ 32 | /* @supports (-webkit-touch-callout:none) { 33 | html { 34 | font: -apple-system-body; 35 | } 36 | } */ 37 | -------------------------------------------------------------------------------- /src/components/meta-theme.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | 3 | export function MetaTheme() { 4 | useEffect(() => { 5 | const updateThemeColor = () => { 6 | const bgColor = window.getComputedStyle( 7 | document.body 8 | ).backgroundColor 9 | 10 | const metaThemeColor = document.querySelector( 11 | "meta[name=theme-color]" 12 | ) 13 | 14 | metaThemeColor?.setAttribute("content", bgColor) 15 | } 16 | 17 | const observer = new MutationObserver(updateThemeColor) 18 | 19 | observer.observe(document.documentElement, { 20 | attributes: true, 21 | attributeFilter: ["class"] 22 | }) 23 | 24 | return () => observer.disconnect() 25 | }, []) 26 | 27 | return null 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["**/*.ts", "**/*.tsx"], 3 | "compilerOptions": { 4 | "target": "ES2022", 5 | "jsx": "react-jsx", 6 | "module": "ESNext", 7 | "lib": ["ES2022", "DOM", "DOM.Iterable"], 8 | "types": ["vite/client"], 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "verbatimModuleSyntax": false, 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "skipLibCheck": true, 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "noUncheckedSideEffectImports": true, 23 | "baseUrl": ".", 24 | "paths": { 25 | "@/*": ["./src/*"] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/2.2.7/schema.json", 3 | "vcs": { 4 | "enabled": true, 5 | "clientKind": "git", 6 | "useIgnoreFile": true 7 | }, 8 | "files": { 9 | "ignoreUnknown": true, 10 | "includes": ["**", "!**/src/routeTree.gen.ts", "!**/*.css"] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "space", 15 | "indentWidth": 4 16 | }, 17 | "assist": { "actions": { "source": { "organizeImports": "on" } } }, 18 | "linter": { 19 | "enabled": true, 20 | "rules": { 21 | "recommended": true, 22 | "style": { 23 | "noNonNullAssertion": "off" 24 | } 25 | } 26 | }, 27 | "javascript": { 28 | "formatter": { 29 | "quoteStyle": "double", 30 | "semicolons": "asNeeded", 31 | "trailingCommas": "none" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/providers.tsx: -------------------------------------------------------------------------------- 1 | import { AuthUIProvider } from "@daveyplate/better-auth-ui" 2 | import { Link, useRouter } from "@tanstack/react-router" 3 | import { ThemeProvider } from "next-themes" 4 | 5 | import { authClient } from "@/lib/auth-client" 6 | import { MetaTheme } from "./meta-theme" 7 | 8 | export function Providers({ children }: { children: React.ReactNode }) { 9 | const { navigate } = useRouter() 10 | 11 | return ( 12 | 18 | navigate({ href })} 21 | replace={(href) => navigate({ href, replace: true })} 22 | Link={({ href, ...props }) => } 23 | > 24 | {children} 25 | 26 | 27 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/components/mode-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react" 4 | import { useTheme } from "next-themes" 5 | 6 | import { Button } from "./ui/button" 7 | import { 8 | DropdownMenu, 9 | DropdownMenuContent, 10 | DropdownMenuItem, 11 | DropdownMenuTrigger 12 | } from "./ui/dropdown-menu" 13 | 14 | export function ModeToggle() { 15 | const { setTheme } = useTheme() 16 | 17 | return ( 18 | 19 | 20 | 29 | 30 | e.preventDefault()} 33 | > 34 | setTheme("light")}> 35 | 36 | Light 37 | 38 | setTheme("dark")}> 39 | 40 | Dark 41 | 42 | setTheme("system")}> 43 | 44 | System 45 | 46 | 47 | 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /src/routes/__root.tsx: -------------------------------------------------------------------------------- 1 | import { TanStackDevtools } from "@tanstack/react-devtools" 2 | import { createRootRoute, HeadContent, Scripts } from "@tanstack/react-router" 3 | import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools" 4 | import { Header } from "@/components/header" 5 | import { Providers } from "@/components/providers" 6 | import appCss from "../styles/styles.css?url" 7 | 8 | export const Route = createRootRoute({ 9 | head: () => ({ 10 | meta: [ 11 | { title: "Better Auth Starter" }, 12 | { charSet: "utf-8" }, 13 | { 14 | name: "viewport", 15 | content: "width=device-width, initial-scale=1" 16 | }, 17 | { 18 | name: "theme-color", 19 | content: "var(--bg-background)" 20 | } 21 | ], 22 | links: [ 23 | { 24 | rel: "stylesheet", 25 | href: appCss 26 | } 27 | ] 28 | }), 29 | 30 | shellComponent: RootDocument 31 | }) 32 | 33 | function RootDocument({ children }: { children: React.ReactNode }) { 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 44 | {children} 45 | 46 | 47 | 55 | } 56 | ]} 57 | /> 58 | 59 | 60 | 61 | 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /src/components/header.tsx: -------------------------------------------------------------------------------- 1 | import { GitHubIcon, UserButton } from "@daveyplate/better-auth-ui" 2 | import { Link } from "@tanstack/react-router" 3 | import { ModeToggle } from "./mode-toggle" 4 | import { Button } from "./ui/button" 5 | 6 | export function Header() { 7 | return ( 8 |
9 | 10 | 19 | Better Auth UI Logo 20 | 26 | 27 | BETTER-AUTH. STARTER 28 | 29 | 30 |
31 | 36 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |
50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /auth-schema.ts: -------------------------------------------------------------------------------- 1 | import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core" 2 | 3 | export const users = pgTable("users", { 4 | id: text("id").primaryKey(), 5 | name: text("name").notNull(), 6 | email: text("email").notNull().unique(), 7 | emailVerified: boolean("email_verified").notNull(), 8 | image: text("image"), 9 | createdAt: timestamp("created_at").notNull(), 10 | updatedAt: timestamp("updated_at").notNull() 11 | }) 12 | 13 | export const sessions = pgTable("sessions", { 14 | id: text("id").primaryKey(), 15 | expiresAt: timestamp("expires_at").notNull(), 16 | token: text("token").notNull().unique(), 17 | createdAt: timestamp("created_at").notNull(), 18 | updatedAt: timestamp("updated_at").notNull(), 19 | ipAddress: text("ip_address"), 20 | userAgent: text("user_agent"), 21 | userId: text("user_id") 22 | .notNull() 23 | .references(() => users.id, { onDelete: "cascade" }) 24 | }) 25 | 26 | export const accounts = pgTable("accounts", { 27 | id: text("id").primaryKey(), 28 | accountId: text("account_id").notNull(), 29 | providerId: text("provider_id").notNull(), 30 | userId: text("user_id") 31 | .notNull() 32 | .references(() => users.id, { onDelete: "cascade" }), 33 | accessToken: text("access_token"), 34 | refreshToken: text("refresh_token"), 35 | idToken: text("id_token"), 36 | accessTokenExpiresAt: timestamp("access_token_expires_at"), 37 | refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), 38 | scope: text("scope"), 39 | password: text("password"), 40 | createdAt: timestamp("created_at").notNull(), 41 | updatedAt: timestamp("updated_at").notNull() 42 | }) 43 | 44 | export const verifications = pgTable("verifications", { 45 | id: text("id").primaryKey(), 46 | identifier: text("identifier").notNull(), 47 | value: text("value").notNull(), 48 | expiresAt: timestamp("expires_at").notNull(), 49 | createdAt: timestamp("created_at"), 50 | updatedAt: timestamp("updated_at") 51 | }) 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tanstack-start-hybrid", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vite dev --port 3000", 7 | "build": "vite build", 8 | "serve": "vite preview", 9 | "test": "vitest run", 10 | "format": "biome format", 11 | "lint": "biome lint", 12 | "check": "biome check" 13 | }, 14 | "dependencies": { 15 | "@daveyplate/better-auth-ui": "^3.3.0", 16 | "@radix-ui/react-dropdown-menu": "^2.1.16", 17 | "@radix-ui/react-slot": "^1.2.4", 18 | "@tailwindcss/vite": "^4.1.18", 19 | "@tanstack/nitro-v2-vite-plugin": "^1.141.0", 20 | "@tanstack/react-devtools": "^0.8.4", 21 | "@tanstack/react-router": "^1.141.2", 22 | "@tanstack/react-router-devtools": "^1.141.2", 23 | "@tanstack/react-router-ssr-query": "^1.141.2", 24 | "@tanstack/react-start": "^1.141.3", 25 | "@tanstack/router-plugin": "^1.141.2", 26 | "better-auth": "^1.4.7", 27 | "class-variance-authority": "^0.7.1", 28 | "clsx": "^2.1.1", 29 | "dotenv": "^17.2.3", 30 | "drizzle-orm": "^0.45.1", 31 | "lucide-react": "^0.561.0", 32 | "next-themes": "^0.4.6", 33 | "pg": "^8.16.3", 34 | "react": "^19.2.3", 35 | "react-dom": "^19.2.3", 36 | "tailwind-merge": "^3.4.0", 37 | "tailwindcss": "^4.1.18", 38 | "tailwindcss-safe-area": "^1.2.0", 39 | "vite-tsconfig-paths": "^6.0.1" 40 | }, 41 | "devDependencies": { 42 | "@biomejs/biome": "2.3.8", 43 | "@testing-library/dom": "^10.4.1", 44 | "@testing-library/react": "^16.3.0", 45 | "@types/node": "^25.0.2", 46 | "@types/pg": "^8.16.0", 47 | "@types/react": "^19.2.7", 48 | "@types/react-dom": "^19.2.3", 49 | "@vitejs/plugin-react": "^5.1.2", 50 | "drizzle-kit": "^0.31.8", 51 | "jsdom": "^27.3.0", 52 | "tsx": "^4.21.0", 53 | "tw-animate-css": "^1.4.0", 54 | "typescript": "^5.9.3", 55 | "vite": "^7.2.7", 56 | "vite-plugin-devtools-json": "^1.0.0", 57 | "vitest": "^4.0.15", 58 | "web-vitals": "^5.1.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "@radix-ui/react-slot" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | import type * as React from "react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 16 | outline: 17 | "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 18 | secondary: 19 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", 21 | link: "text-primary underline-offset-4 hover:underline" 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2 has-[>svg]:px-3", 25 | sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 26 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 27 | icon: "size-9", 28 | "icon-sm": "size-8", 29 | "icon-lg": "size-10" 30 | } 31 | }, 32 | defaultVariants: { 33 | variant: "default", 34 | size: "default" 35 | } 36 | } 37 | ) 38 | 39 | function Button({ 40 | className, 41 | variant, 42 | size, 43 | asChild = false, 44 | ...props 45 | }: React.ComponentProps<"button"> & 46 | VariantProps & { 47 | asChild?: boolean 48 | }) { 49 | const Comp = asChild ? Slot : "button" 50 | 51 | return ( 52 | 57 | ) 58 | } 59 | 60 | export { Button, buttonVariants } 61 | -------------------------------------------------------------------------------- /src/routeTree.gen.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // @ts-nocheck 4 | 5 | // noinspection JSUnusedGlobalSymbols 6 | 7 | // This file was automatically generated by TanStack Router. 8 | // You should NOT make any changes in this file as it will be overwritten. 9 | // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. 10 | 11 | import { Route as rootRouteImport } from './routes/__root' 12 | import { Route as IndexRouteImport } from './routes/index' 13 | import { Route as AuthPathRouteImport } from './routes/auth/$path' 14 | import { Route as AccountPathRouteImport } from './routes/account/$path' 15 | import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$' 16 | 17 | const IndexRoute = IndexRouteImport.update({ 18 | id: '/', 19 | path: '/', 20 | getParentRoute: () => rootRouteImport, 21 | } as any) 22 | const AuthPathRoute = AuthPathRouteImport.update({ 23 | id: '/auth/$path', 24 | path: '/auth/$path', 25 | getParentRoute: () => rootRouteImport, 26 | } as any) 27 | const AccountPathRoute = AccountPathRouteImport.update({ 28 | id: '/account/$path', 29 | path: '/account/$path', 30 | getParentRoute: () => rootRouteImport, 31 | } as any) 32 | const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({ 33 | id: '/api/auth/$', 34 | path: '/api/auth/$', 35 | getParentRoute: () => rootRouteImport, 36 | } as any) 37 | 38 | export interface FileRoutesByFullPath { 39 | '/': typeof IndexRoute 40 | '/account/$path': typeof AccountPathRoute 41 | '/auth/$path': typeof AuthPathRoute 42 | '/api/auth/$': typeof ApiAuthSplatRoute 43 | } 44 | export interface FileRoutesByTo { 45 | '/': typeof IndexRoute 46 | '/account/$path': typeof AccountPathRoute 47 | '/auth/$path': typeof AuthPathRoute 48 | '/api/auth/$': typeof ApiAuthSplatRoute 49 | } 50 | export interface FileRoutesById { 51 | __root__: typeof rootRouteImport 52 | '/': typeof IndexRoute 53 | '/account/$path': typeof AccountPathRoute 54 | '/auth/$path': typeof AuthPathRoute 55 | '/api/auth/$': typeof ApiAuthSplatRoute 56 | } 57 | export interface FileRouteTypes { 58 | fileRoutesByFullPath: FileRoutesByFullPath 59 | fullPaths: '/' | '/account/$path' | '/auth/$path' | '/api/auth/$' 60 | fileRoutesByTo: FileRoutesByTo 61 | to: '/' | '/account/$path' | '/auth/$path' | '/api/auth/$' 62 | id: '__root__' | '/' | '/account/$path' | '/auth/$path' | '/api/auth/$' 63 | fileRoutesById: FileRoutesById 64 | } 65 | export interface RootRouteChildren { 66 | IndexRoute: typeof IndexRoute 67 | AccountPathRoute: typeof AccountPathRoute 68 | AuthPathRoute: typeof AuthPathRoute 69 | ApiAuthSplatRoute: typeof ApiAuthSplatRoute 70 | } 71 | 72 | declare module '@tanstack/react-router' { 73 | interface FileRoutesByPath { 74 | '/': { 75 | id: '/' 76 | path: '/' 77 | fullPath: '/' 78 | preLoaderRoute: typeof IndexRouteImport 79 | parentRoute: typeof rootRouteImport 80 | } 81 | '/auth/$path': { 82 | id: '/auth/$path' 83 | path: '/auth/$path' 84 | fullPath: '/auth/$path' 85 | preLoaderRoute: typeof AuthPathRouteImport 86 | parentRoute: typeof rootRouteImport 87 | } 88 | '/account/$path': { 89 | id: '/account/$path' 90 | path: '/account/$path' 91 | fullPath: '/account/$path' 92 | preLoaderRoute: typeof AccountPathRouteImport 93 | parentRoute: typeof rootRouteImport 94 | } 95 | '/api/auth/$': { 96 | id: '/api/auth/$' 97 | path: '/api/auth/$' 98 | fullPath: '/api/auth/$' 99 | preLoaderRoute: typeof ApiAuthSplatRouteImport 100 | parentRoute: typeof rootRouteImport 101 | } 102 | } 103 | } 104 | 105 | const rootRouteChildren: RootRouteChildren = { 106 | IndexRoute: IndexRoute, 107 | AccountPathRoute: AccountPathRoute, 108 | AuthPathRoute: AuthPathRoute, 109 | ApiAuthSplatRoute: ApiAuthSplatRoute, 110 | } 111 | export const routeTree = rootRouteImport 112 | ._addFileChildren(rootRouteChildren) 113 | ._addFileTypes() 114 | 115 | import type { getRouter } from './router.tsx' 116 | import type { createStart } from '@tanstack/react-start' 117 | declare module '@tanstack/react-start' { 118 | interface Register { 119 | ssr: true 120 | router: Awaited> 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/styles/styles.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | @import "tw-animate-css"; 3 | 4 | @import "./custom.css"; 5 | 6 | @custom-variant dark (&:is(.dark *)); 7 | 8 | @theme inline { 9 | --radius-sm: calc(var(--radius) - 4px); 10 | --radius-md: calc(var(--radius) - 2px); 11 | --radius-lg: var(--radius); 12 | --radius-xl: calc(var(--radius) + 4px); 13 | --color-background: var(--background); 14 | --color-foreground: var(--foreground); 15 | --color-card: var(--card); 16 | --color-card-foreground: var(--card-foreground); 17 | --color-popover: var(--popover); 18 | --color-popover-foreground: var(--popover-foreground); 19 | --color-primary: var(--primary); 20 | --color-primary-foreground: var(--primary-foreground); 21 | --color-secondary: var(--secondary); 22 | --color-secondary-foreground: var(--secondary-foreground); 23 | --color-muted: var(--muted); 24 | --color-muted-foreground: var(--muted-foreground); 25 | --color-accent: var(--accent); 26 | --color-accent-foreground: var(--accent-foreground); 27 | --color-destructive: var(--destructive); 28 | --color-border: var(--border); 29 | --color-input: var(--input); 30 | --color-ring: var(--ring); 31 | --color-chart-1: var(--chart-1); 32 | --color-chart-2: var(--chart-2); 33 | --color-chart-3: var(--chart-3); 34 | --color-chart-4: var(--chart-4); 35 | --color-chart-5: var(--chart-5); 36 | --color-sidebar: var(--sidebar); 37 | --color-sidebar-foreground: var(--sidebar-foreground); 38 | --color-sidebar-primary: var(--sidebar-primary); 39 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 40 | --color-sidebar-accent: var(--sidebar-accent); 41 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 42 | --color-sidebar-border: var(--sidebar-border); 43 | --color-sidebar-ring: var(--sidebar-ring); 44 | } 45 | 46 | :root { 47 | --radius: 0.625rem; 48 | --background: oklch(1 0 0); 49 | --foreground: oklch(0.145 0 0); 50 | --card: oklch(1 0 0); 51 | --card-foreground: oklch(0.145 0 0); 52 | --popover: oklch(1 0 0); 53 | --popover-foreground: oklch(0.145 0 0); 54 | --primary: oklch(0.205 0 0); 55 | --primary-foreground: oklch(0.985 0 0); 56 | --secondary: oklch(0.97 0 0); 57 | --secondary-foreground: oklch(0.205 0 0); 58 | --muted: oklch(0.97 0 0); 59 | --muted-foreground: oklch(0.556 0 0); 60 | --accent: oklch(0.97 0 0); 61 | --accent-foreground: oklch(0.205 0 0); 62 | --destructive: oklch(0.577 0.245 27.325); 63 | --border: oklch(0.922 0 0); 64 | --input: oklch(0.922 0 0); 65 | --ring: oklch(0.708 0 0); 66 | --chart-1: oklch(0.646 0.222 41.116); 67 | --chart-2: oklch(0.6 0.118 184.704); 68 | --chart-3: oklch(0.398 0.07 227.392); 69 | --chart-4: oklch(0.828 0.189 84.429); 70 | --chart-5: oklch(0.769 0.188 70.08); 71 | --sidebar: oklch(0.985 0 0); 72 | --sidebar-foreground: oklch(0.145 0 0); 73 | --sidebar-primary: oklch(0.205 0 0); 74 | --sidebar-primary-foreground: oklch(0.985 0 0); 75 | --sidebar-accent: oklch(0.97 0 0); 76 | --sidebar-accent-foreground: oklch(0.205 0 0); 77 | --sidebar-border: oklch(0.922 0 0); 78 | --sidebar-ring: oklch(0.708 0 0); 79 | } 80 | 81 | .dark { 82 | --background: oklch(0.145 0 0); 83 | --foreground: oklch(0.985 0 0); 84 | --card: oklch(0.205 0 0); 85 | --card-foreground: oklch(0.985 0 0); 86 | --popover: oklch(0.205 0 0); 87 | --popover-foreground: oklch(0.985 0 0); 88 | --primary: oklch(0.922 0 0); 89 | --primary-foreground: oklch(0.205 0 0); 90 | --secondary: oklch(0.269 0 0); 91 | --secondary-foreground: oklch(0.985 0 0); 92 | --muted: oklch(0.269 0 0); 93 | --muted-foreground: oklch(0.708 0 0); 94 | --accent: oklch(0.269 0 0); 95 | --accent-foreground: oklch(0.985 0 0); 96 | --destructive: oklch(0.704 0.191 22.216); 97 | --border: oklch(1 0 0 / 10%); 98 | --input: oklch(1 0 0 / 15%); 99 | --ring: oklch(0.556 0 0); 100 | --chart-1: oklch(0.488 0.243 264.376); 101 | --chart-2: oklch(0.696 0.17 162.48); 102 | --chart-3: oklch(0.769 0.188 70.08); 103 | --chart-4: oklch(0.627 0.265 303.9); 104 | --chart-5: oklch(0.645 0.246 16.439); 105 | --sidebar: oklch(0.205 0 0); 106 | --sidebar-foreground: oklch(0.985 0 0); 107 | --sidebar-primary: oklch(0.488 0.243 264.376); 108 | --sidebar-primary-foreground: oklch(0.985 0 0); 109 | --sidebar-accent: oklch(0.269 0 0); 110 | --sidebar-accent-foreground: oklch(0.985 0 0); 111 | --sidebar-border: oklch(1 0 0 / 10%); 112 | --sidebar-ring: oklch(0.556 0 0); 113 | } 114 | 115 | @layer base { 116 | * { 117 | @apply border-border outline-ring/50; 118 | } 119 | body { 120 | @apply bg-background text-foreground; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Welcome to your new TanStack app! 2 | 3 | # Getting Started 4 | 5 | To run this application: 6 | 7 | ```bash 8 | pnpm install 9 | pnpm start 10 | ``` 11 | 12 | # Building For Production 13 | 14 | To build this application for production: 15 | 16 | ```bash 17 | pnpm build 18 | ``` 19 | 20 | ## Testing 21 | 22 | This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with: 23 | 24 | ```bash 25 | pnpm test 26 | ``` 27 | 28 | ## Styling 29 | 30 | This project uses [Tailwind CSS](https://tailwindcss.com/) for styling. 31 | 32 | 33 | ## Linting & Formatting 34 | 35 | This project uses [Biome](https://biomejs.dev/) for linting and formatting. The following scripts are available: 36 | 37 | 38 | ```bash 39 | pnpm lint 40 | pnpm format 41 | pnpm check 42 | ``` 43 | 44 | 45 | 46 | ## Routing 47 | This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`. 48 | 49 | ### Adding A Route 50 | 51 | To add a new route to your application just add another a new file in the `./src/routes` directory. 52 | 53 | TanStack will automatically generate the content of the route file for you. 54 | 55 | Now that you have two routes you can use a `Link` component to navigate between them. 56 | 57 | ### Adding Links 58 | 59 | To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`. 60 | 61 | ```tsx 62 | import { Link } from "@tanstack/react-router"; 63 | ``` 64 | 65 | Then anywhere in your JSX you can use it like so: 66 | 67 | ```tsx 68 | About 69 | ``` 70 | 71 | This will create a link that will navigate to the `/about` route. 72 | 73 | More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent). 74 | 75 | ### Using A Layout 76 | 77 | In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you use the `` component. 78 | 79 | Here is an example layout that includes a header: 80 | 81 | ```tsx 82 | import { Outlet, createRootRoute } from '@tanstack/react-router' 83 | import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' 84 | 85 | import { Link } from "@tanstack/react-router"; 86 | 87 | export const Route = createRootRoute({ 88 | component: () => ( 89 | <> 90 |
91 | 95 |
96 | 97 | 98 | 99 | ), 100 | }) 101 | ``` 102 | 103 | The `` component is not required so you can remove it if you don't want it in your layout. 104 | 105 | More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts). 106 | 107 | 108 | ## Data Fetching 109 | 110 | There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered. 111 | 112 | For example: 113 | 114 | ```tsx 115 | const peopleRoute = createRoute({ 116 | getParentRoute: () => rootRoute, 117 | path: "/people", 118 | loader: async () => { 119 | const response = await fetch("https://swapi.dev/api/people"); 120 | return response.json() as Promise<{ 121 | results: { 122 | name: string; 123 | }[]; 124 | }>; 125 | }, 126 | component: () => { 127 | const data = peopleRoute.useLoaderData(); 128 | return ( 129 |
    130 | {data.results.map((person) => ( 131 |
  • {person.name}
  • 132 | ))} 133 |
134 | ); 135 | }, 136 | }); 137 | ``` 138 | 139 | Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters). 140 | 141 | ### React-Query 142 | 143 | React-Query is an excellent addition or alternative to route loading and integrating it into you application is a breeze. 144 | 145 | First add your dependencies: 146 | 147 | ```bash 148 | pnpm add @tanstack/react-query @tanstack/react-query-devtools 149 | ``` 150 | 151 | Next we'll need to create a query client and provider. We recommend putting those in `main.tsx`. 152 | 153 | ```tsx 154 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 155 | 156 | // ... 157 | 158 | const queryClient = new QueryClient(); 159 | 160 | // ... 161 | 162 | if (!rootElement.innerHTML) { 163 | const root = ReactDOM.createRoot(rootElement); 164 | 165 | root.render( 166 | 167 | 168 | 169 | ); 170 | } 171 | ``` 172 | 173 | You can also add TanStack Query Devtools to the root route (optional). 174 | 175 | ```tsx 176 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 177 | 178 | const rootRoute = createRootRoute({ 179 | component: () => ( 180 | <> 181 | 182 | 183 | 184 | 185 | ), 186 | }); 187 | ``` 188 | 189 | Now you can use `useQuery` to fetch your data. 190 | 191 | ```tsx 192 | import { useQuery } from "@tanstack/react-query"; 193 | 194 | import "./App.css"; 195 | 196 | function App() { 197 | const { data } = useQuery({ 198 | queryKey: ["people"], 199 | queryFn: () => 200 | fetch("https://swapi.dev/api/people") 201 | .then((res) => res.json()) 202 | .then((data) => data.results as { name: string }[]), 203 | initialData: [], 204 | }); 205 | 206 | return ( 207 |
208 |
    209 | {data.map((person) => ( 210 |
  • {person.name}
  • 211 | ))} 212 |
213 |
214 | ); 215 | } 216 | 217 | export default App; 218 | ``` 219 | 220 | You can find out everything you need to know on how to use React-Query in the [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview). 221 | 222 | ## State Management 223 | 224 | Another common requirement for React applications is state management. There are many options for state management in React. TanStack Store provides a great starting point for your project. 225 | 226 | First you need to add TanStack Store as a dependency: 227 | 228 | ```bash 229 | pnpm add @tanstack/store 230 | ``` 231 | 232 | Now let's create a simple counter in the `src/App.tsx` file as a demonstration. 233 | 234 | ```tsx 235 | import { useStore } from "@tanstack/react-store"; 236 | import { Store } from "@tanstack/store"; 237 | import "./App.css"; 238 | 239 | const countStore = new Store(0); 240 | 241 | function App() { 242 | const count = useStore(countStore); 243 | return ( 244 |
245 | 248 |
249 | ); 250 | } 251 | 252 | export default App; 253 | ``` 254 | 255 | One of the many nice features of TanStack Store is the ability to derive state from other state. That derived state will update when the base state updates. 256 | 257 | Let's check this out by doubling the count using derived state. 258 | 259 | ```tsx 260 | import { useStore } from "@tanstack/react-store"; 261 | import { Store, Derived } from "@tanstack/store"; 262 | import "./App.css"; 263 | 264 | const countStore = new Store(0); 265 | 266 | const doubledStore = new Derived({ 267 | fn: () => countStore.state * 2, 268 | deps: [countStore], 269 | }); 270 | doubledStore.mount(); 271 | 272 | function App() { 273 | const count = useStore(countStore); 274 | const doubledCount = useStore(doubledStore); 275 | 276 | return ( 277 |
278 | 281 |
Doubled - {doubledCount}
282 |
283 | ); 284 | } 285 | 286 | export default App; 287 | ``` 288 | 289 | We use the `Derived` class to create a new store that is derived from another store. The `Derived` class has a `mount` method that will start the derived store updating. 290 | 291 | Once we've created the derived store we can use it in the `App` component just like we would any other store using the `useStore` hook. 292 | 293 | You can find out everything you need to know on how to use TanStack Store in the [TanStack Store documentation](https://tanstack.com/store/latest). 294 | 295 | # Demo files 296 | 297 | Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed. 298 | 299 | # Learn More 300 | 301 | You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com). 302 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 2 | import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" 3 | import type * as React from "react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | function DropdownMenu({ 8 | ...props 9 | }: React.ComponentProps) { 10 | return 11 | } 12 | 13 | function DropdownMenuPortal({ 14 | ...props 15 | }: React.ComponentProps) { 16 | return ( 17 | 21 | ) 22 | } 23 | 24 | function DropdownMenuTrigger({ 25 | ...props 26 | }: React.ComponentProps) { 27 | return ( 28 | 32 | ) 33 | } 34 | 35 | function DropdownMenuContent({ 36 | className, 37 | sideOffset = 4, 38 | ...props 39 | }: React.ComponentProps) { 40 | return ( 41 | 42 | 51 | 52 | ) 53 | } 54 | 55 | function DropdownMenuGroup({ 56 | ...props 57 | }: React.ComponentProps) { 58 | return ( 59 | 63 | ) 64 | } 65 | 66 | function DropdownMenuItem({ 67 | className, 68 | inset, 69 | variant = "default", 70 | ...props 71 | }: React.ComponentProps & { 72 | inset?: boolean 73 | variant?: "default" | "destructive" 74 | }) { 75 | return ( 76 | 86 | ) 87 | } 88 | 89 | function DropdownMenuCheckboxItem({ 90 | className, 91 | children, 92 | checked, 93 | ...props 94 | }: React.ComponentProps) { 95 | return ( 96 | 105 | 106 | 107 | 108 | 109 | 110 | {children} 111 | 112 | ) 113 | } 114 | 115 | function DropdownMenuRadioGroup({ 116 | ...props 117 | }: React.ComponentProps) { 118 | return ( 119 | 123 | ) 124 | } 125 | 126 | function DropdownMenuRadioItem({ 127 | className, 128 | children, 129 | ...props 130 | }: React.ComponentProps) { 131 | return ( 132 | 140 | 141 | 142 | 143 | 144 | 145 | {children} 146 | 147 | ) 148 | } 149 | 150 | function DropdownMenuLabel({ 151 | className, 152 | inset, 153 | ...props 154 | }: React.ComponentProps & { 155 | inset?: boolean 156 | }) { 157 | return ( 158 | 167 | ) 168 | } 169 | 170 | function DropdownMenuSeparator({ 171 | className, 172 | ...props 173 | }: React.ComponentProps) { 174 | return ( 175 | 180 | ) 181 | } 182 | 183 | function DropdownMenuShortcut({ 184 | className, 185 | ...props 186 | }: React.ComponentProps<"span">) { 187 | return ( 188 | 196 | ) 197 | } 198 | 199 | function DropdownMenuSub({ 200 | ...props 201 | }: React.ComponentProps) { 202 | return ( 203 | 204 | ) 205 | } 206 | 207 | function DropdownMenuSubTrigger({ 208 | className, 209 | inset, 210 | children, 211 | ...props 212 | }: React.ComponentProps & { 213 | inset?: boolean 214 | }) { 215 | return ( 216 | 225 | {children} 226 | 227 | 228 | ) 229 | } 230 | 231 | function DropdownMenuSubContent({ 232 | className, 233 | ...props 234 | }: React.ComponentProps) { 235 | return ( 236 | 244 | ) 245 | } 246 | 247 | export { 248 | DropdownMenu, 249 | DropdownMenuPortal, 250 | DropdownMenuTrigger, 251 | DropdownMenuContent, 252 | DropdownMenuGroup, 253 | DropdownMenuLabel, 254 | DropdownMenuItem, 255 | DropdownMenuCheckboxItem, 256 | DropdownMenuRadioGroup, 257 | DropdownMenuRadioItem, 258 | DropdownMenuSeparator, 259 | DropdownMenuShortcut, 260 | DropdownMenuSub, 261 | DropdownMenuSubTrigger, 262 | DropdownMenuSubContent 263 | } 264 | -------------------------------------------------------------------------------- /public/tanstack-word-logo-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | logo 4 | 12 | --------------------------------------------------------------------------------