├── src ├── components │ ├── demo │ │ ├── index.ts │ │ └── middleware-demo.tsx │ ├── navigation │ │ ├── index.ts │ │ └── navigation-bar.tsx │ ├── theme │ │ ├── index.ts │ │ ├── theme-provider.tsx │ │ └── theme-toggle.tsx │ ├── landing │ │ ├── index.ts │ │ ├── hero-section.tsx │ │ ├── features-section.tsx │ │ └── footer.tsx │ ├── ui │ │ ├── collapsible.tsx │ │ ├── badge.tsx │ │ ├── alert.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── sheet.tsx │ │ └── dropdown-menu.tsx │ ├── not-found.tsx │ └── default-catch-boundary.tsx ├── lib │ └── utils.ts ├── integrations │ └── tanstack-query │ │ ├── devtools.tsx │ │ └── root-provider.tsx ├── core │ ├── middleware │ │ └── example-middleware.ts │ └── functions │ │ └── example-functions.ts ├── server.ts ├── start.tsx ├── routes │ ├── index.tsx │ └── __root.tsx ├── router.tsx ├── utils │ └── seo.ts ├── routeTree.gen.ts ├── styles.css └── logo.svg ├── .wrangler └── deploy │ └── config.json ├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png └── manifest.json ├── .mcp.json ├── .gitignore ├── .vscode └── settings.json ├── wrangler.jsonc ├── .cta.json ├── components.json ├── tsconfig.json ├── vite.config.ts ├── package.json ├── CLAUDE.md ├── .claude └── agents │ ├── shadcn-ui-builder.md │ └── tanstack-server-functions.md └── README.md /src/components/demo/index.ts: -------------------------------------------------------------------------------- 1 | export { MiddlewareDemo } from './middleware-demo' -------------------------------------------------------------------------------- /src/components/navigation/index.ts: -------------------------------------------------------------------------------- 1 | export { NavigationBar } from './navigation-bar' -------------------------------------------------------------------------------- /.wrangler/deploy/config.json: -------------------------------------------------------------------------------- 1 | {"configPath":"../../dist/server/wrangler.json","auxiliaryWorkers":[]} -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/backpine/tanstack-start-on-cloudflare/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/backpine/tanstack-start-on-cloudflare/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/backpine/tanstack-start-on-cloudflare/HEAD/public/logo512.png -------------------------------------------------------------------------------- /.mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "shadcn": { 4 | "command": "npx", 5 | "args": ["shadcn@latest", "mcp"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/theme/index.ts: -------------------------------------------------------------------------------- 1 | export { ThemeProvider, useTheme } from "./theme-provider"; 2 | export { ThemeToggle, ThemeToggleSimple } from "./theme-toggle"; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | count.txt 7 | .env 8 | .nitro 9 | .tanstack 10 | .output 11 | .vinxi 12 | todos.json 13 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/components/landing/index.ts: -------------------------------------------------------------------------------- 1 | export { HeroSection } from './hero-section' 2 | export { FeaturesSection } from './features-section' 3 | export { CtaSection } from './cta-section' 4 | export { Footer } from './footer' -------------------------------------------------------------------------------- /src/integrations/tanstack-query/devtools.tsx: -------------------------------------------------------------------------------- 1 | import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools' 2 | 3 | export default { 4 | name: 'Tanstack Query', 5 | render: , 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.watcherExclude": { 3 | "**/routeTree.gen.ts": true 4 | }, 5 | "search.exclude": { 6 | "**/routeTree.gen.ts": true 7 | }, 8 | "files.readonlyInclude": { 9 | "**/routeTree.gen.ts": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /wrangler.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/wrangler/config-schema.json", 3 | "name": "tanstack-start-app", 4 | "compatibility_date": "2025-09-02", 5 | "compatibility_flags": ["nodejs_compat"], 6 | "main": "./src/server.ts", 7 | "vars": { 8 | "MY_VAR": "Hello from Cloudflare", 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /.cta.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": ".", 3 | "mode": "file-router", 4 | "typescript": true, 5 | "tailwind": true, 6 | "packageManager": "pnpm", 7 | "git": true, 8 | "version": 1, 9 | "framework": "react-cra", 10 | "chosenAddOns": [ 11 | "start", 12 | "shadcn", 13 | "tanstack-query" 14 | ] 15 | } -------------------------------------------------------------------------------- /src/core/middleware/example-middleware.ts: -------------------------------------------------------------------------------- 1 | import { createMiddleware } from "@tanstack/react-start"; 2 | 3 | export const exampleMiddlewareWithContext = createMiddleware({ 4 | type: "function", 5 | }).server(async ({ next }) => { 6 | console.log("Executing exampleMiddlewareWithContext"); 7 | return await next({ 8 | context: { 9 | data: "Some Data From Middleware", 10 | }, 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | // DO NOT DELETE THIS FILE!!! 2 | // This file is a good smoke test to make sure the custom server entry is working 3 | import handler from "@tanstack/react-start/server-entry"; 4 | 5 | console.log("[server-entry]: using custom server entry in 'src/server.ts'"); 6 | 7 | export default { 8 | fetch(request: Request) { 9 | return handler.fetch(request, { 10 | context: { 11 | fromFetch: true, 12 | }, 13 | }); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /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": "zinc", 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 | } -------------------------------------------------------------------------------- /src/integrations/tanstack-query/root-provider.tsx: -------------------------------------------------------------------------------- 1 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query' 2 | 3 | export function getContext() { 4 | const queryClient = new QueryClient() 5 | return { 6 | queryClient, 7 | } 8 | } 9 | 10 | export function Provider({ 11 | children, 12 | queryClient, 13 | }: { 14 | children: React.ReactNode 15 | queryClient: QueryClient 16 | }) { 17 | return ( 18 | {children} 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/start.tsx: -------------------------------------------------------------------------------- 1 | import { createStart } from "@tanstack/react-start"; 2 | 3 | declare module "@tanstack/react-start" { 4 | interface Register { 5 | server: { 6 | requestContext: { 7 | fromFetch: boolean; 8 | }; 9 | }; 10 | } 11 | } 12 | 13 | export const startInstance = createStart(() => { 14 | return { 15 | defaultSsr: true, 16 | }; 17 | }); 18 | 19 | startInstance.createMiddleware().server(({ next }) => { 20 | return next({ 21 | context: { 22 | fromStartInstanceMw: true, 23 | }, 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router"; 2 | import { NavigationBar } from "@/components/navigation"; 3 | import { HeroSection } from "@/components/landing/hero-section"; 4 | import { FeaturesSection } from "@/components/landing/features-section"; 5 | import { Footer } from "@/components/landing/footer"; 6 | import { MiddlewareDemo } from "@/components/demo"; 7 | 8 | export const Route = createFileRoute("/")({ 9 | component: LandingPage, 10 | }); 11 | 12 | function LandingPage() { 13 | return ( 14 |
15 | 16 |
17 | 18 | 19 | 20 |
21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { tanstackStart } from "@tanstack/react-start/plugin/vite"; 3 | import viteReact from "@vitejs/plugin-react"; 4 | import viteTsConfigPaths from "vite-tsconfig-paths"; 5 | import tailwindcss from "@tailwindcss/vite"; 6 | import { cloudflare } from "@cloudflare/vite-plugin"; 7 | 8 | const config = defineConfig({ 9 | plugins: [ 10 | // this is the plugin that enables path aliases 11 | viteTsConfigPaths({ 12 | projects: ["./tsconfig.json"], 13 | }), 14 | tailwindcss(), 15 | tanstackStart({ 16 | srcDirectory: "src", 17 | start: { entry: "./start.tsx" }, 18 | server: { entry: "./server.ts" }, 19 | }), 20 | viteReact(), 21 | cloudflare({ 22 | viteEnvironment: { 23 | name: "ssr", 24 | }, 25 | }), 26 | ], 27 | }); 28 | 29 | export default config; 30 | -------------------------------------------------------------------------------- /src/router.tsx: -------------------------------------------------------------------------------- 1 | import { createRouter } from '@tanstack/react-router' 2 | import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query' 3 | import * as TanstackQuery from './integrations/tanstack-query/root-provider' 4 | 5 | // Import the generated route tree 6 | import { routeTree } from './routeTree.gen' 7 | 8 | // Create a new router instance 9 | export const getRouter = () => { 10 | const rqContext = TanstackQuery.getContext() 11 | 12 | const router = createRouter({ 13 | routeTree, 14 | context: { ...rqContext }, 15 | defaultPreload: 'intent', 16 | Wrap: (props: { children: React.ReactNode }) => { 17 | return ( 18 | 19 | {props.children} 20 | 21 | ) 22 | }, 23 | }) 24 | 25 | setupRouterSsrQueryIntegration({ router, queryClient: rqContext.queryClient }) 26 | 27 | return router 28 | } 29 | -------------------------------------------------------------------------------- /src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 2 | 3 | function Collapsible({ 4 | ...props 5 | }: React.ComponentProps) { 6 | return 7 | } 8 | 9 | function CollapsibleTrigger({ 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 17 | ) 18 | } 19 | 20 | function CollapsibleContent({ 21 | ...props 22 | }: React.ComponentProps) { 23 | return ( 24 | 28 | ) 29 | } 30 | 31 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 32 | -------------------------------------------------------------------------------- /src/core/functions/example-functions.ts: -------------------------------------------------------------------------------- 1 | import { createServerFn } from "@tanstack/react-start"; 2 | import { z } from "zod"; 3 | import { exampleMiddlewareWithContext } from "@/core/middleware/example-middleware"; 4 | // import { env } from "cloudflare:workers"; 5 | 6 | const baseFunction = createServerFn().middleware([ 7 | exampleMiddlewareWithContext, 8 | ]); 9 | 10 | const ExampleInputSchema = z.object({ 11 | exampleKey: z.string().min(1), 12 | }); 13 | 14 | type ExampleInput = z.infer; 15 | 16 | export const examplefunction = baseFunction 17 | .inputValidator((data: ExampleInput) => ExampleInputSchema.parse(data)) 18 | .handler(async (ctx) => { 19 | console.log("Executing example function"); 20 | console.log(`The data passed: ${JSON.stringify(ctx.data)}`); 21 | console.log(`The context from middleware: ${JSON.stringify(ctx.context)}`); 22 | // console.log(`The Cloudflare Worker Environment: ${JSON.stringify(env)}`); 23 | return "Function executed successfully"; 24 | }); 25 | -------------------------------------------------------------------------------- /src/utils/seo.ts: -------------------------------------------------------------------------------- 1 | export const seo = ({ 2 | title, 3 | description, 4 | keywords, 5 | image, 6 | }: { 7 | title: string; 8 | description?: string; 9 | image?: string; 10 | keywords?: string; 11 | }) => { 12 | const tags = [ 13 | { title }, 14 | { name: "description", content: description }, 15 | { name: "keywords", content: keywords }, 16 | { name: "twitter:title", content: title }, 17 | { name: "twitter:description", content: description }, 18 | { name: "twitter:creator", content: "@tannerlinsley" }, 19 | { name: "twitter:site", content: "@tannerlinsley" }, 20 | { name: "og:type", content: "website" }, 21 | { name: "og:title", content: title }, 22 | { name: "og:description", content: description }, 23 | ...(image 24 | ? [ 25 | { name: "twitter:image", content: image }, 26 | { name: "twitter:card", content: "summary_large_image" }, 27 | { name: "og:image", content: image }, 28 | ] 29 | : []), 30 | ]; 31 | 32 | return tags; 33 | }; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tanstack-start-on-cloudflare", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vite dev --port 3000", 7 | "build": "vite build", 8 | "deploy": "pnpm run build && wrangler deploy", 9 | "serve": "vite preview", 10 | "cf-typegen": "wrangler types --env-interface Env" 11 | }, 12 | "dependencies": { 13 | "@radix-ui/react-collapsible": "^1.1.12", 14 | "@radix-ui/react-dialog": "^1.1.15", 15 | "@radix-ui/react-dropdown-menu": "^2.1.16", 16 | "@radix-ui/react-slot": "^1.2.4", 17 | "@tailwindcss/vite": "^4.1.17", 18 | "@tanstack/react-query": "^5.90.11", 19 | "@tanstack/react-query-devtools": "^5.91.1", 20 | "@tanstack/react-router": "^1.139.12", 21 | "@tanstack/react-router-devtools": "^1.139.12", 22 | "@tanstack/react-router-ssr-query": "^1.139.12", 23 | "@tanstack/react-start": "^1.139.12", 24 | "class-variance-authority": "^0.7.1", 25 | "clsx": "^2.1.1", 26 | "lucide-react": "^0.476.0", 27 | "react": "^19.2.0", 28 | "react-dom": "^19.2.0", 29 | "tailwind-merge": "^3.4.0", 30 | "tailwindcss": "^4.1.17", 31 | "tw-animate-css": "^1.4.0", 32 | "vite-tsconfig-paths": "^5.1.4", 33 | "zod": "^4.1.13" 34 | }, 35 | "devDependencies": { 36 | "@cloudflare/vite-plugin": "^1.15.3", 37 | "@testing-library/dom": "^10.4.1", 38 | "@testing-library/react": "^16.3.0", 39 | "@types/node": "^22.19.1", 40 | "@types/react": "^19.2.7", 41 | "@types/react-dom": "^19.2.3", 42 | "@vitejs/plugin-react": "^4.7.0", 43 | "jsdom": "^26.1.0", 44 | "typescript": "^5.9.3", 45 | "vite": "7.1.2", 46 | "vitest": "^3.2.4", 47 | "web-vitals": "^4.2.4", 48 | "wrangler": "^4.51.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const badgeVariants = cva( 8 | "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-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 transition-[color,box-shadow] overflow-hidden", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", 14 | secondary: 15 | "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", 16 | destructive: 17 | "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 18 | outline: 19 | "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", 20 | }, 21 | }, 22 | defaultVariants: { 23 | variant: "default", 24 | }, 25 | } 26 | ) 27 | 28 | function Badge({ 29 | className, 30 | variant, 31 | asChild = false, 32 | ...props 33 | }: React.ComponentProps<"span"> & 34 | VariantProps & { asChild?: boolean }) { 35 | const Comp = asChild ? Slot : "span" 36 | 37 | return ( 38 | 43 | ) 44 | } 45 | 46 | export { Badge, badgeVariants } 47 | -------------------------------------------------------------------------------- /src/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-card text-card-foreground", 12 | destructive: 13 | "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | function Alert({ 23 | className, 24 | variant, 25 | ...props 26 | }: React.ComponentProps<"div"> & VariantProps) { 27 | return ( 28 |
34 | ) 35 | } 36 | 37 | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { 38 | return ( 39 |
47 | ) 48 | } 49 | 50 | function AlertDescription({ 51 | className, 52 | ...props 53 | }: React.ComponentProps<"div">) { 54 | return ( 55 |
63 | ) 64 | } 65 | 66 | export { Alert, AlertTitle, AlertDescription } 67 | -------------------------------------------------------------------------------- /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 | 14 | const IndexRoute = IndexRouteImport.update({ 15 | id: '/', 16 | path: '/', 17 | getParentRoute: () => rootRouteImport, 18 | } as any) 19 | 20 | export interface FileRoutesByFullPath { 21 | '/': typeof IndexRoute 22 | } 23 | export interface FileRoutesByTo { 24 | '/': typeof IndexRoute 25 | } 26 | export interface FileRoutesById { 27 | __root__: typeof rootRouteImport 28 | '/': typeof IndexRoute 29 | } 30 | export interface FileRouteTypes { 31 | fileRoutesByFullPath: FileRoutesByFullPath 32 | fullPaths: '/' 33 | fileRoutesByTo: FileRoutesByTo 34 | to: '/' 35 | id: '__root__' | '/' 36 | fileRoutesById: FileRoutesById 37 | } 38 | export interface RootRouteChildren { 39 | IndexRoute: typeof IndexRoute 40 | } 41 | 42 | declare module '@tanstack/react-router' { 43 | interface FileRoutesByPath { 44 | '/': { 45 | id: '/' 46 | path: '/' 47 | fullPath: '/' 48 | preLoaderRoute: typeof IndexRouteImport 49 | parentRoute: typeof rootRouteImport 50 | } 51 | } 52 | } 53 | 54 | const rootRouteChildren: RootRouteChildren = { 55 | IndexRoute: IndexRoute, 56 | } 57 | export const routeTree = rootRouteImport 58 | ._addFileChildren(rootRouteChildren) 59 | ._addFileTypes() 60 | 61 | import type { getRouter } from './router.tsx' 62 | import type { startInstance } from './start.tsx' 63 | declare module '@tanstack/react-start' { 64 | interface Register { 65 | ssr: true 66 | router: Awaited> 67 | config: Awaited> 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 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: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 15 | outline: 16 | "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: 20 | "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 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | function Button({ 38 | className, 39 | variant, 40 | size, 41 | asChild = false, 42 | ...props 43 | }: React.ComponentProps<"button"> & 44 | VariantProps & { 45 | asChild?: boolean 46 | }) { 47 | const Comp = asChild ? Slot : "button" 48 | 49 | return ( 50 | 55 | ) 56 | } 57 | 58 | export { Button, buttonVariants } 59 | -------------------------------------------------------------------------------- /src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Card({ className, ...props }: React.ComponentProps<"div">) { 6 | return ( 7 |
15 | ) 16 | } 17 | 18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 19 | return ( 20 |
28 | ) 29 | } 30 | 31 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 32 | return ( 33 |
38 | ) 39 | } 40 | 41 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 42 | return ( 43 |
48 | ) 49 | } 50 | 51 | function CardAction({ className, ...props }: React.ComponentProps<"div">) { 52 | return ( 53 |
61 | ) 62 | } 63 | 64 | function CardContent({ className, ...props }: React.ComponentProps<"div">) { 65 | return ( 66 |
71 | ) 72 | } 73 | 74 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 75 | return ( 76 |
81 | ) 82 | } 83 | 84 | export { 85 | Card, 86 | CardHeader, 87 | CardFooter, 88 | CardTitle, 89 | CardAction, 90 | CardDescription, 91 | CardContent, 92 | } 93 | -------------------------------------------------------------------------------- /src/components/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@tanstack/react-router"; 2 | import { ArrowLeft, Home, Search, FileQuestion } from "lucide-react"; 3 | import { Button } from "@/components/ui/button"; 4 | import { Card, CardContent } from "@/components/ui/card"; 5 | 6 | export function NotFound({ children }: { children?: any }) { 7 | return ( 8 |
9 | 10 | 11 |
12 | {/* Icon */} 13 |
14 | 15 |
16 | 17 | {/* Heading */} 18 |
19 |

20 | Page Not Found 21 |

22 |
23 | {children || ( 24 |

25 | The page you're looking for doesn't exist or has been moved. 26 |

27 | )} 28 |
29 |
30 | 31 | {/* Actions */} 32 |
33 | 41 | 47 |
48 | 49 | {/* Help text */} 50 |
51 |
52 | 53 | 54 | Try checking the URL or use the search functionality 55 | 56 |
57 |
58 |
59 |
60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Commands 6 | 7 | ### Development 8 | - `pnpm dev` - Start development server on port 3000 9 | - `pnpm build` - Build for production 10 | - `pnpm serve` - Preview production build 11 | - `pnpm test` - Run tests with Vitest 12 | 13 | ### Shadcn Components 14 | - `pnpx shadcn@latest add ` - Add new Shadcn components (use latest version) 15 | 16 | ## Architecture 17 | 18 | This is a TanStack Start application - a type-safe, client-first, full-stack React framework built on top of: 19 | 20 | ### Core Stack 21 | - **TanStack Router**: File-based routing with type-safe navigation 22 | - **TanStack Query**: Server state management with SSR integration 23 | - **React 19**: Latest React with concurrent features 24 | - **Vite**: Build tool and dev server 25 | - **TypeScript**: Strict type checking enabled 26 | - **Tailwind CSS v4**: Utility-first styling with CSS variables 27 | 28 | ### Project Structure 29 | - `src/routes/` - File-based routes (auto-generates `routeTree.gen.ts`) 30 | - `src/components/` - Reusable React components 31 | - `src/integrations/tanstack-query/` - Query client setup and providers 32 | - `src/lib/utils.ts` - Utility functions (includes clsx/tailwind-merge) 33 | - `src/utils/seo.ts` - SEO helper functions 34 | - Path aliases: `@/*` maps to `src/*` 35 | 36 | ### Key Architecture Patterns 37 | 38 | **Router Setup**: The router is created via `getRouter()` in `src/router.tsx` which integrates TanStack Query context and SSR. Routes are auto-generated from the file system. 39 | 40 | **Query Integration**: TanStack Query is pre-configured with SSR support through `setupRouterSsrQueryIntegration`. The query client is accessible in route contexts. 41 | 42 | **Root Layout**: `src/routes/__root.tsx` defines the HTML document structure, includes devtools, and provides navigation links. It uses `createRootRouteWithContext` for type-safe context passing. 43 | 44 | **Styling**: Uses Tailwind CSS v4 with the Vite plugin. Shadcn components are configured with "new-york" style, Zinc base color, and CSS variables enabled. 45 | 46 | **TypeScript**: Strict mode with additional linting rules (`noUnusedLocals`, `noUnusedParameters`, etc.). Uses modern ESNext module resolution. 47 | 48 | ### Development Notes 49 | - Demo files (prefixed with `demo`) can be safely deleted 50 | - The project uses pnpm as the package manager 51 | - Devtools are included for both Router and Query in development 52 | - Routes support loaders, error boundaries, and not-found components 53 | - File-based routing automatically generates type-safe route definitions -------------------------------------------------------------------------------- /src/components/landing/hero-section.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import { Badge } from "@/components/ui/badge" 3 | import { ArrowRight, Github } from "lucide-react" 4 | 5 | export function HeroSection() { 6 | return ( 7 |
8 |
9 |
10 | 11 | Built with React 19, TypeScript & Vite 12 | 13 |
14 | 15 |

16 | TanStack Start 17 | Template 18 |

19 | 20 |

21 | A modern, type-safe, full-stack React framework combining the power of 22 | TanStack Router and Query with the latest web technologies. Start building 23 | production-ready applications today. 24 |

25 | 26 |
27 | 31 | 32 | 43 |
44 | 45 |
46 |

Trusted by developers building modern web applications

47 |
48 |
49 | 50 | {/* Background gradient */} 51 |
52 |
59 |
60 |
61 | ) 62 | } -------------------------------------------------------------------------------- /src/routes/__root.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | import { 3 | HeadContent, 4 | Outlet, 5 | Scripts, 6 | createRootRouteWithContext, 7 | } from "@tanstack/react-router"; 8 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 9 | import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; 10 | import * as React from "react"; 11 | import type { QueryClient } from "@tanstack/react-query"; 12 | import { DefaultCatchBoundary } from "@/components/default-catch-boundary"; 13 | import { NotFound } from "@/components/not-found"; 14 | import { ThemeProvider } from "@/components/theme"; 15 | import appCss from "@/styles.css?url"; 16 | import { seo } from "@/utils/seo"; 17 | 18 | export const Route = createRootRouteWithContext<{ 19 | queryClient: QueryClient; 20 | }>()({ 21 | head: () => ({ 22 | meta: [ 23 | { 24 | charSet: "utf-8", 25 | }, 26 | { 27 | name: "viewport", 28 | content: "width=device-width, initial-scale=1", 29 | }, 30 | ...seo({ 31 | title: 32 | "TanStack Start | Type-Safe, Client-First, Full-Stack React Framework", 33 | description: `TanStack Start is a type-safe, client-first, full-stack React framework. `, 34 | }), 35 | ], 36 | links: [ 37 | { rel: "stylesheet", href: appCss }, 38 | { 39 | rel: "apple-touch-icon", 40 | sizes: "180x180", 41 | href: "/apple-touch-icon.png", 42 | }, 43 | { 44 | rel: "icon", 45 | type: "image/png", 46 | sizes: "32x32", 47 | href: "/favicon-32x32.png", 48 | }, 49 | { 50 | rel: "icon", 51 | type: "image/png", 52 | sizes: "16x16", 53 | href: "/favicon-16x16.png", 54 | }, 55 | { rel: "manifest", href: "/site.webmanifest", color: "#fffff" }, 56 | { rel: "icon", href: "/favicon.ico" }, 57 | ], 58 | }), 59 | errorComponent: (props) => { 60 | return ( 61 | 62 | 63 | 64 | ); 65 | }, 66 | notFoundComponent: () => , 67 | component: RootComponent, 68 | }); 69 | 70 | function RootComponent() { 71 | return ( 72 | 73 | 79 | 80 | 81 | 82 | ); 83 | } 84 | 85 | function RootDocument({ children }: { children: React.ReactNode }) { 86 | return ( 87 | 88 | 89 | 90 | 91 | 92 | {children} 93 | 94 | 95 | 96 | 97 | 98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /src/components/landing/features-section.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 2 | import { Badge } from "@/components/ui/badge" 3 | import { 4 | Route, 5 | Database, 6 | Zap, 7 | Shield, 8 | Palette, 9 | Code, 10 | Server, 11 | Layers 12 | } from "lucide-react" 13 | 14 | const features = [ 15 | { 16 | icon: Route, 17 | title: "TanStack Router", 18 | description: "Type-safe, file-based routing with powerful features like nested layouts, loaders, and search params validation.", 19 | badge: "Type-Safe" 20 | }, 21 | { 22 | icon: Database, 23 | title: "TanStack Query", 24 | description: "Powerful data synchronization with server state management, caching, and background updates built-in.", 25 | badge: "Server State" 26 | }, 27 | { 28 | icon: Code, 29 | title: "React 19", 30 | description: "Latest React with concurrent features, improved performance, and modern development patterns.", 31 | badge: "Latest" 32 | }, 33 | { 34 | icon: Zap, 35 | title: "Vite", 36 | description: "Lightning-fast build tool with hot module replacement and optimized production builds.", 37 | badge: "Fast" 38 | }, 39 | { 40 | icon: Shield, 41 | title: "TypeScript", 42 | description: "Full TypeScript support with strict typing, IntelliSense, and compile-time error checking.", 43 | badge: "Type-Safe" 44 | }, 45 | { 46 | icon: Palette, 47 | title: "Tailwind CSS v4", 48 | description: "Modern utility-first CSS framework with CSS variables and a comprehensive design system.", 49 | badge: "Styling" 50 | }, 51 | { 52 | icon: Server, 53 | title: "SSR Ready", 54 | description: "Server-side rendering support with seamless hydration and SEO optimization out of the box.", 55 | badge: "Performance" 56 | }, 57 | { 58 | icon: Layers, 59 | title: "Shadcn/UI", 60 | description: "Beautiful, accessible component library with customizable themes and modern design patterns.", 61 | badge: "Components" 62 | } 63 | ] 64 | 65 | export function FeaturesSection() { 66 | return ( 67 |
68 |
69 |
70 |

71 | Everything you need to build modern web apps 72 |

73 |

74 | A carefully curated stack of the best tools and libraries for React development 75 |

76 |
77 | 78 |
79 | {features.map((feature) => { 80 | const IconComponent = feature.icon 81 | return ( 82 | 83 | 84 |
85 |
86 | 87 |
88 | 89 | {feature.badge} 90 | 91 |
92 | {feature.title} 93 |
94 | 95 | 96 | {feature.description} 97 | 98 | 99 |
100 | ) 101 | })} 102 |
103 |
104 |
105 | ) 106 | } -------------------------------------------------------------------------------- /src/components/landing/footer.tsx: -------------------------------------------------------------------------------- 1 | import { Github, ExternalLink } from "lucide-react" 2 | 3 | const navigation = { 4 | main: [ 5 | { name: 'TanStack Start', href: 'https://tanstack.com/start' }, 6 | { name: 'TanStack Router', href: 'https://tanstack.com/router' }, 7 | { name: 'TanStack Query', href: 'https://tanstack.com/query' }, 8 | { name: 'React', href: 'https://react.dev' }, 9 | ], 10 | tools: [ 11 | { name: 'Vite', href: 'https://vitejs.dev' }, 12 | { name: 'TypeScript', href: 'https://typescriptlang.org' }, 13 | { name: 'Tailwind CSS', href: 'https://tailwindcss.com' }, 14 | { name: 'Shadcn/UI', href: 'https://ui.shadcn.com' }, 15 | ], 16 | social: [ 17 | { 18 | name: 'GitHub', 19 | href: 'https://github.com/tanstack', 20 | icon: Github, 21 | }, 22 | ], 23 | } 24 | 25 | export function Footer() { 26 | return ( 27 |
28 |
29 |
30 |
31 |

TanStack Ecosystem

32 | 47 |
48 | 49 |
50 |

Development Tools

51 | 66 |
67 |
68 | 69 |
70 |
71 | {navigation.social.map((item) => { 72 | const IconComponent = item.icon 73 | return ( 74 | 81 | {item.name} 82 | 83 | 84 | ) 85 | })} 86 |
87 | 88 |
89 |

90 | Built with TanStack Start 91 |

92 |

93 | © {new Date().getFullYear()} TanStack. MIT Licensed. 94 |

95 |
96 |
97 |
98 |
99 | ) 100 | } -------------------------------------------------------------------------------- /src/components/ui/sheet.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as SheetPrimitive from "@radix-ui/react-dialog" 3 | import { XIcon } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | function Sheet({ ...props }: React.ComponentProps) { 8 | return 9 | } 10 | 11 | function SheetTrigger({ 12 | ...props 13 | }: React.ComponentProps) { 14 | return 15 | } 16 | 17 | function SheetClose({ 18 | ...props 19 | }: React.ComponentProps) { 20 | return 21 | } 22 | 23 | function SheetPortal({ 24 | ...props 25 | }: React.ComponentProps) { 26 | return 27 | } 28 | 29 | function SheetOverlay({ 30 | className, 31 | ...props 32 | }: React.ComponentProps) { 33 | return ( 34 | 42 | ) 43 | } 44 | 45 | function SheetContent({ 46 | className, 47 | children, 48 | side = "right", 49 | ...props 50 | }: React.ComponentProps & { 51 | side?: "top" | "right" | "bottom" | "left" 52 | }) { 53 | return ( 54 | 55 | 56 | 72 | {children} 73 | 74 | 75 | Close 76 | 77 | 78 | 79 | ) 80 | } 81 | 82 | function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { 83 | return ( 84 |
89 | ) 90 | } 91 | 92 | function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { 93 | return ( 94 |
99 | ) 100 | } 101 | 102 | function SheetTitle({ 103 | className, 104 | ...props 105 | }: React.ComponentProps) { 106 | return ( 107 | 112 | ) 113 | } 114 | 115 | function SheetDescription({ 116 | className, 117 | ...props 118 | }: React.ComponentProps) { 119 | return ( 120 | 125 | ) 126 | } 127 | 128 | export { 129 | Sheet, 130 | SheetTrigger, 131 | SheetClose, 132 | SheetContent, 133 | SheetHeader, 134 | SheetFooter, 135 | SheetTitle, 136 | SheetDescription, 137 | } 138 | -------------------------------------------------------------------------------- /.claude/agents/shadcn-ui-builder.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: shadcn-ui-builder 3 | description: Use this agent when you need to create, modify, or enhance UI components using Shadcn/UI with proper design patterns and file organization. Examples: Context: User wants to create a new dashboard component with cards and charts. user: 'Create a dashboard component with metric cards and a chart section' assistant: 'I'll use the shadcn-ui-builder agent to create a well-structured dashboard component following our design system.' The user needs UI components built, so use the shadcn-ui-builder agent to create components with proper Shadcn patterns, theme colors, and file organization. Context: User needs to improve the UX of an existing form component. user: 'This login form needs better validation feedback and loading states' assistant: 'Let me use the shadcn-ui-builder agent to enhance the form with better UX patterns.' Since this involves UI/UX improvements using Shadcn components, use the shadcn-ui-builder agent. 4 | model: sonnet 5 | color: green 6 | --- 7 | 8 | You are a Senior UI/UX Engineer specializing in Shadcn/UI component development with deep expertise in modern React patterns, accessibility, and design systems. You excel at creating beautiful, functional, and accessible user interfaces that follow best practices. 9 | 10 | Your core responsibilities: 11 | 1. **Component Architecture**: Design and build React components using Shadcn/UI primitives with proper composition patterns 12 | 2. **Design System Adherence**: Ensure all components follow consistent design patterns and use theme-based styling 13 | 3. **File Organization**: Structure components logically within the src/components folder with appropriate subfolders 14 | 4. **Accessibility First**: Build components that are accessible by default with proper ARIA attributes and keyboard navigation 15 | 5. **Performance Optimization**: Create efficient components with proper memoization and lazy loading where appropriate 16 | 17 | **Critical Rules You Must Follow:** 18 | - NEVER hardcode Tailwind colors - always use CSS variables and theme tokens (e.g., `bg-background`, `text-foreground`, `border-border`) 19 | - Use lowercase kebab-case for all file names (e.g., `user-profile.tsx`, not `UserProfile.tsx`) 20 | - Organize components in logical subfolders within `src/components/` (e.g., `src/components/forms/`, `src/components/layout/`) 21 | - Use the Shadcn MCP server to add new components when needed with `pnpx shadcn@latest add ` 22 | - Follow the project's Tailwind CSS v4 setup with CSS variables enabled 23 | 24 | **Component Development Process:** 25 | 1. **Analyze Requirements**: Understand the component's purpose, user interactions, and data flow 26 | 2. **Plan Structure**: Determine component hierarchy, props interface, and folder organization 27 | 3. **Design Patterns**: Choose appropriate Shadcn primitives and composition patterns 28 | 4. **Theme Integration**: Use semantic color tokens and spacing from the design system 29 | 5. **Accessibility**: Implement proper ARIA labels, keyboard navigation, and screen reader support 30 | 6. **Responsive Design**: Ensure components work across all device sizes 31 | 7. **Error Handling**: Include proper loading states, error boundaries, and fallbacks 32 | 33 | **Code Quality Standards:** 34 | - Use TypeScript with strict typing for all props and state 35 | - Implement proper error boundaries and loading states 36 | - Follow React 19 patterns including concurrent features when appropriate 37 | - Use semantic HTML elements and proper heading hierarchy 38 | - Implement proper focus management and keyboard navigation 39 | - Include hover, focus, and active states for interactive elements 40 | 41 | **UX Principles:** 42 | - Prioritize user feedback with clear loading, success, and error states 43 | - Implement progressive disclosure for complex interfaces 44 | - Use consistent spacing, typography, and interaction patterns 45 | - Provide clear visual hierarchy and scannable layouts 46 | - Ensure fast perceived performance with skeleton loaders and optimistic updates 47 | 48 | **File Organization Examples:** 49 | - Forms: `src/components/forms/login-form.tsx`, `src/components/forms/contact-form.tsx` 50 | - Layout: `src/components/layout/header.tsx`, `src/components/layout/sidebar.tsx` 51 | - UI Elements: `src/components/ui/custom-button.tsx`, `src/components/ui/data-table.tsx` 52 | 53 | When creating components, always consider the complete user journey, provide clear feedback for all interactions, and ensure the component integrates seamlessly with the existing TanStack Start application architecture. Ask for clarification if requirements are ambiguous, and suggest UX improvements when you identify opportunities to enhance the user experience. 54 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | 3 | @import 'tw-animate-css'; 4 | 5 | @custom-variant dark (&:is(.dark *)); 6 | 7 | body { 8 | @apply m-0; 9 | font-family: 10 | -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 11 | 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | code { 17 | font-family: 18 | source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 19 | } 20 | 21 | :root { 22 | --background: oklch(1 0 0); 23 | --foreground: oklch(0.141 0.005 285.823); 24 | --card: oklch(1 0 0); 25 | --card-foreground: oklch(0.141 0.005 285.823); 26 | --popover: oklch(1 0 0); 27 | --popover-foreground: oklch(0.141 0.005 285.823); 28 | --primary: oklch(0.21 0.006 285.885); 29 | --primary-foreground: oklch(0.985 0 0); 30 | --secondary: oklch(0.967 0.001 286.375); 31 | --secondary-foreground: oklch(0.21 0.006 285.885); 32 | --muted: oklch(0.967 0.001 286.375); 33 | --muted-foreground: oklch(0.552 0.016 285.938); 34 | --accent: oklch(0.967 0.001 286.375); 35 | --accent-foreground: oklch(0.21 0.006 285.885); 36 | --destructive: oklch(0.577 0.245 27.325); 37 | --destructive-foreground: oklch(0.577 0.245 27.325); 38 | --border: oklch(0.92 0.004 286.32); 39 | --input: oklch(0.92 0.004 286.32); 40 | --ring: oklch(0.871 0.006 286.286); 41 | --chart-1: oklch(0.646 0.222 41.116); 42 | --chart-2: oklch(0.6 0.118 184.704); 43 | --chart-3: oklch(0.398 0.07 227.392); 44 | --chart-4: oklch(0.828 0.189 84.429); 45 | --chart-5: oklch(0.769 0.188 70.08); 46 | --radius: 0.625rem; 47 | --sidebar: oklch(0.985 0 0); 48 | --sidebar-foreground: oklch(0.141 0.005 285.823); 49 | --sidebar-primary: oklch(0.21 0.006 285.885); 50 | --sidebar-primary-foreground: oklch(0.985 0 0); 51 | --sidebar-accent: oklch(0.967 0.001 286.375); 52 | --sidebar-accent-foreground: oklch(0.21 0.006 285.885); 53 | --sidebar-border: oklch(0.92 0.004 286.32); 54 | --sidebar-ring: oklch(0.871 0.006 286.286); 55 | } 56 | 57 | .dark { 58 | --background: oklch(0.141 0.005 285.823); 59 | --foreground: oklch(0.985 0 0); 60 | --card: oklch(0.141 0.005 285.823); 61 | --card-foreground: oklch(0.985 0 0); 62 | --popover: oklch(0.141 0.005 285.823); 63 | --popover-foreground: oklch(0.985 0 0); 64 | --primary: oklch(0.985 0 0); 65 | --primary-foreground: oklch(0.21 0.006 285.885); 66 | --secondary: oklch(0.274 0.006 286.033); 67 | --secondary-foreground: oklch(0.985 0 0); 68 | --muted: oklch(0.274 0.006 286.033); 69 | --muted-foreground: oklch(0.705 0.015 286.067); 70 | --accent: oklch(0.274 0.006 286.033); 71 | --accent-foreground: oklch(0.985 0 0); 72 | --destructive: oklch(0.396 0.141 25.723); 73 | --destructive-foreground: oklch(0.637 0.237 25.331); 74 | --border: oklch(0.274 0.006 286.033); 75 | --input: oklch(0.274 0.006 286.033); 76 | --ring: oklch(0.442 0.017 285.786); 77 | --chart-1: oklch(0.488 0.243 264.376); 78 | --chart-2: oklch(0.696 0.17 162.48); 79 | --chart-3: oklch(0.769 0.188 70.08); 80 | --chart-4: oklch(0.627 0.265 303.9); 81 | --chart-5: oklch(0.645 0.246 16.439); 82 | --sidebar: oklch(0.21 0.006 285.885); 83 | --sidebar-foreground: oklch(0.985 0 0); 84 | --sidebar-primary: oklch(0.488 0.243 264.376); 85 | --sidebar-primary-foreground: oklch(0.985 0 0); 86 | --sidebar-accent: oklch(0.274 0.006 286.033); 87 | --sidebar-accent-foreground: oklch(0.985 0 0); 88 | --sidebar-border: oklch(0.274 0.006 286.033); 89 | --sidebar-ring: oklch(0.442 0.017 285.786); 90 | } 91 | 92 | @theme inline { 93 | --color-background: var(--background); 94 | --color-foreground: var(--foreground); 95 | --color-card: var(--card); 96 | --color-card-foreground: var(--card-foreground); 97 | --color-popover: var(--popover); 98 | --color-popover-foreground: var(--popover-foreground); 99 | --color-primary: var(--primary); 100 | --color-primary-foreground: var(--primary-foreground); 101 | --color-secondary: var(--secondary); 102 | --color-secondary-foreground: var(--secondary-foreground); 103 | --color-muted: var(--muted); 104 | --color-muted-foreground: var(--muted-foreground); 105 | --color-accent: var(--accent); 106 | --color-accent-foreground: var(--accent-foreground); 107 | --color-destructive: var(--destructive); 108 | --color-destructive-foreground: var(--destructive-foreground); 109 | --color-border: var(--border); 110 | --color-input: var(--input); 111 | --color-ring: var(--ring); 112 | --color-chart-1: var(--chart-1); 113 | --color-chart-2: var(--chart-2); 114 | --color-chart-3: var(--chart-3); 115 | --color-chart-4: var(--chart-4); 116 | --color-chart-5: var(--chart-5); 117 | --radius-sm: calc(var(--radius) - 4px); 118 | --radius-md: calc(var(--radius) - 2px); 119 | --radius-lg: var(--radius); 120 | --radius-xl: calc(var(--radius) + 4px); 121 | --color-sidebar: var(--sidebar); 122 | --color-sidebar-foreground: var(--sidebar-foreground); 123 | --color-sidebar-primary: var(--sidebar-primary); 124 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 125 | --color-sidebar-accent: var(--sidebar-accent); 126 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 127 | --color-sidebar-border: var(--sidebar-border); 128 | --color-sidebar-ring: var(--sidebar-ring); 129 | } 130 | 131 | @layer base { 132 | * { 133 | @apply border-border outline-ring/50; 134 | } 135 | body { 136 | @apply bg-background text-foreground; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/components/default-catch-boundary.tsx: -------------------------------------------------------------------------------- 1 | import { Link, rootRouteId, useMatch, useRouter } from "@tanstack/react-router"; 2 | import type { ErrorComponentProps } from "@tanstack/react-router"; 3 | import { 4 | AlertTriangle, 5 | RefreshCw, 6 | ArrowLeft, 7 | Home, 8 | ChevronDown, 9 | Bug, 10 | Mail, 11 | } from "lucide-react"; 12 | import { Button } from "@/components/ui/button"; 13 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 14 | import { Alert, AlertDescription } from "@/components/ui/alert"; 15 | import { 16 | Collapsible, 17 | CollapsibleContent, 18 | CollapsibleTrigger, 19 | } from "@/components/ui/collapsible"; 20 | import { useState } from "react"; 21 | 22 | export function DefaultCatchBoundary({ error }: ErrorComponentProps) { 23 | const router = useRouter(); 24 | const isRoot = useMatch({ 25 | strict: false, 26 | select: (state) => state.id === rootRouteId, 27 | }); 28 | const [showDetails, setShowDetails] = useState(false); 29 | 30 | console.error(error); 31 | 32 | // Format error details for display 33 | const errorMessage = error?.message || "An unexpected error occurred"; 34 | const errorStack = error?.stack || ""; 35 | const hasStack = errorStack.length > 0; 36 | 37 | const handleReportError = () => { 38 | const subject = encodeURIComponent("Error Report"); 39 | const body = encodeURIComponent( 40 | `An error occurred in the application:\n\nError: ${errorMessage}\n\nStack Trace:\n${errorStack}\n\nPlease describe what you were doing when this error occurred:`, 41 | ); 42 | window.location.href = `mailto:support@example.com?subject=${subject}&body=${body}`; 43 | }; 44 | 45 | return ( 46 |
47 | 48 | 49 |
50 |
51 | 52 |
53 |
54 | Something went wrong 55 |

56 | We encountered an unexpected error. Please try again. 57 |

58 |
59 |
60 |
61 | 62 | 63 | {/* Error Alert */} 64 | 65 | 66 | 67 | {errorMessage} 68 | 69 | 70 | 71 | {/* Action Buttons */} 72 |
73 | 80 | 81 | {isRoot ? ( 82 | 88 | ) : ( 89 | 97 | )} 98 |
99 | 100 | {/* Error Details (Collapsible) */} 101 | {hasStack && ( 102 | 103 | 104 | 115 | 116 | 117 |
118 |

119 | Error Stack Trace: 120 |

121 |
122 |                     {errorStack}
123 |                   
124 |
125 |
126 |
127 | )} 128 | 129 | {/* Help Section */} 130 |
131 |
132 |
133 | If this error persists, please report it to our support team. 134 |
135 | 144 |
145 |
146 |
147 |
148 |
149 | ); 150 | } 151 | -------------------------------------------------------------------------------- /src/components/theme/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | type Theme = "dark" | "light" | "system"; 4 | 5 | type ThemeProviderProps = { 6 | children: React.ReactNode; 7 | defaultTheme?: Theme; 8 | storageKey?: string; 9 | attribute?: string; 10 | enableSystem?: boolean; 11 | disableTransitionOnChange?: boolean; 12 | }; 13 | 14 | type ThemeProviderState = { 15 | theme: Theme; 16 | setTheme: (theme: Theme) => void; 17 | resolvedTheme?: "light" | "dark"; 18 | systemTheme?: "light" | "dark"; 19 | }; 20 | 21 | const initialState: ThemeProviderState = { 22 | theme: "system", 23 | setTheme: () => null, 24 | resolvedTheme: undefined, 25 | systemTheme: undefined, 26 | }; 27 | 28 | const ThemeProviderContext = React.createContext(initialState); 29 | 30 | export function ThemeProvider({ 31 | children, 32 | defaultTheme = "system", 33 | storageKey = "ui-theme", 34 | attribute = "class", 35 | enableSystem = true, 36 | disableTransitionOnChange = false, 37 | ...props 38 | }: ThemeProviderProps) { 39 | const [theme, setThemeState] = React.useState(() => { 40 | // During SSR, always return the default theme to avoid hydration mismatch 41 | if (typeof window === "undefined") { 42 | return defaultTheme; 43 | } 44 | 45 | // Client-side: try to get theme from localStorage 46 | try { 47 | const stored = localStorage.getItem(storageKey) as Theme; 48 | return stored || defaultTheme; 49 | } catch { 50 | return defaultTheme; 51 | } 52 | }); 53 | 54 | const [systemTheme, setSystemTheme] = React.useState<"light" | "dark" | undefined>(() => { 55 | // During SSR, return undefined 56 | if (typeof window === "undefined") { 57 | return undefined; 58 | } 59 | 60 | // Client-side: detect system theme 61 | return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; 62 | }); 63 | 64 | const [isMounted, setIsMounted] = React.useState(false); 65 | 66 | const resolvedTheme = theme === "system" ? systemTheme : theme; 67 | 68 | const setTheme = React.useCallback( 69 | (newTheme: Theme) => { 70 | try { 71 | localStorage.setItem(storageKey, newTheme); 72 | } catch { 73 | // Ignore localStorage errors 74 | } 75 | setThemeState(newTheme); 76 | }, 77 | [storageKey] 78 | ); 79 | 80 | const applyTheme = React.useCallback( 81 | (targetTheme: "light" | "dark" | undefined) => { 82 | if (!targetTheme || typeof document === "undefined") return; 83 | 84 | const root = document.documentElement; 85 | 86 | if (disableTransitionOnChange) { 87 | const css = document.createElement("style"); 88 | css.appendChild( 89 | document.createTextNode( 90 | `*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}` 91 | ) 92 | ); 93 | document.head.appendChild(css); 94 | 95 | // Force reflow 96 | (() => window.getComputedStyle(document.body))(); 97 | 98 | setTimeout(() => { 99 | document.head.removeChild(css); 100 | }, 1); 101 | } 102 | 103 | if (attribute === "class") { 104 | root.classList.remove("light", "dark"); 105 | root.classList.add(targetTheme); 106 | } else { 107 | root.setAttribute(attribute, targetTheme); 108 | } 109 | }, 110 | [attribute, disableTransitionOnChange] 111 | ); 112 | 113 | // Apply theme on mount and when resolvedTheme changes 114 | React.useEffect(() => { 115 | if (isMounted) { 116 | applyTheme(resolvedTheme); 117 | } 118 | }, [resolvedTheme, applyTheme, isMounted]); 119 | 120 | // Handle system theme changes 121 | React.useEffect(() => { 122 | if (!enableSystem || typeof window === "undefined") return; 123 | 124 | const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); 125 | 126 | const handleSystemThemeChange = (e: MediaQueryListEvent) => { 127 | setSystemTheme(e.matches ? "dark" : "light"); 128 | }; 129 | 130 | mediaQuery.addEventListener("change", handleSystemThemeChange); 131 | 132 | return () => { 133 | mediaQuery.removeEventListener("change", handleSystemThemeChange); 134 | }; 135 | }, [enableSystem]); 136 | 137 | // Hydration effect - apply theme immediately on client 138 | React.useEffect(() => { 139 | setIsMounted(true); 140 | 141 | // Immediately apply the correct theme on hydration 142 | const currentTheme = theme === "system" ? systemTheme : theme; 143 | applyTheme(currentTheme); 144 | }, [theme, systemTheme, applyTheme]); 145 | 146 | // Prevent flash during SSR by applying theme via script 147 | React.useEffect(() => { 148 | if (typeof document === "undefined") return; 149 | 150 | // Create a script that runs before React hydration to prevent FOIT 151 | const script = document.createElement("script"); 152 | script.innerHTML = ` 153 | try { 154 | var theme = localStorage.getItem('${storageKey}') || '${defaultTheme}'; 155 | var systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; 156 | var resolvedTheme = theme === 'system' ? systemTheme : theme; 157 | 158 | if (resolvedTheme === 'dark') { 159 | document.documentElement.classList.add('dark'); 160 | document.documentElement.classList.remove('light'); 161 | } else { 162 | document.documentElement.classList.add('light'); 163 | document.documentElement.classList.remove('dark'); 164 | } 165 | } catch (e) {} 166 | `; 167 | 168 | // Only add if not already present 169 | if (!document.querySelector(`script[data-theme-script]`)) { 170 | script.setAttribute('data-theme-script', 'true'); 171 | document.head.appendChild(script); 172 | } 173 | }, [storageKey, defaultTheme]); 174 | 175 | const value = React.useMemo( 176 | () => ({ 177 | theme, 178 | setTheme, 179 | resolvedTheme: isMounted ? resolvedTheme : undefined, 180 | systemTheme: isMounted ? systemTheme : undefined, 181 | }), 182 | [theme, setTheme, resolvedTheme, systemTheme, isMounted] 183 | ); 184 | 185 | return ( 186 | 187 | {children} 188 | 189 | ); 190 | } 191 | 192 | export const useTheme = () => { 193 | const context = React.useContext(ThemeProviderContext); 194 | 195 | if (context === undefined) { 196 | throw new Error("useTheme must be used within a ThemeProvider"); 197 | } 198 | 199 | return context; 200 | }; -------------------------------------------------------------------------------- /src/components/theme/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Monitor, Moon, Sun, Check } from "lucide-react"; 3 | 4 | import { Button } from "@/components/ui/button"; 5 | import { 6 | DropdownMenu, 7 | DropdownMenuContent, 8 | DropdownMenuItem, 9 | DropdownMenuTrigger, 10 | } from "@/components/ui/dropdown-menu"; 11 | import { useTheme } from "./theme-provider"; 12 | 13 | interface ThemeToggleProps { 14 | variant?: "default" | "outline" | "ghost"; 15 | size?: "sm" | "default" | "lg"; 16 | showLabel?: boolean; 17 | align?: "start" | "center" | "end"; 18 | } 19 | 20 | export function ThemeToggle({ 21 | variant = "ghost", 22 | size = "default", 23 | showLabel = false, 24 | align = "end", 25 | }: ThemeToggleProps) { 26 | const { theme, setTheme, resolvedTheme } = useTheme(); 27 | 28 | // Animation variants for icons 29 | const iconVariants = { 30 | sun: "transition-all duration-500 ease-in-out", 31 | moon: "transition-all duration-500 ease-in-out", 32 | system: "transition-all duration-300 ease-in-out", 33 | }; 34 | 35 | const getCurrentIcon = () => { 36 | if (theme === "system") { 37 | return ( 38 |