{"id":50410909,"url":"https://github.com/dested/tan-starter","last_synced_at":"2026-05-31T03:30:51.975Z","repository":{"id":354030989,"uuid":"1221855529","full_name":"dested/tan-starter","owner":"dested","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-09T15:10:19.000Z","size":135,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-09T17:28:02.927Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dested.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-26T19:08:53.000Z","updated_at":"2026-05-09T15:10:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dested/tan-starter","commit_stats":null,"previous_names":["dested/tan-starter"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dested/tan-starter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dested%2Ftan-starter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dested%2Ftan-starter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dested%2Ftan-starter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dested%2Ftan-starter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dested","download_url":"https://codeload.github.com/dested/tan-starter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dested%2Ftan-starter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33718446,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-31T03:30:51.424Z","updated_at":"2026-05-31T03:30:51.970Z","avatar_url":"https://github.com/dested.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tan-starter\n\nSSR React starter wired with the latest stack.\n\n- **TanStack Start** (Vite-based SSR + file-based routing)\n- **Prisma ORM 6** + **PostgreSQL**\n- **better-auth** (email + password)\n- **tRPC v11** + **TanStack Query** (`.queryOptions()` style)\n- **Tailwind v4** + **shadcn/ui** (new-york style, oklch tokens)\n- **Bun** (runtime, package manager, production server)\n- **Render.com** blueprint deploy (web service + managed Postgres)\n\n## Quickstart\n\nRequires [Bun](https://bun.sh) ≥ 1.1 and a local Postgres (or any reachable Postgres URL).\n\n```bash\nbun install\ncp .env.example .env\n# edit .env: set DATABASE_URL, replace BETTER_AUTH_SECRET with 32+ random chars\nbun run db:push       # sync schema to your database\nbun run dev           # http://localhost:3000\n```\n\nTo generate a secret quickly: `bunx --bun openssl rand -base64 32` (or any 32+ char string).\n\n## Scripts\n\n| script | what it does |\n| --- | --- |\n| `bun run dev` | dev server with HMR on :3000 |\n| `bun run build` | produces `dist/server/server.js` and `dist/client/*` |\n| `bun run start` | runs the Bun production server (`server.ts`) |\n| `bun run typecheck` | `tsc --noEmit` |\n| `bun run db:push` | sync `prisma/schema.prisma` directly into Postgres |\n| `bun run db:migrate` | create + apply a migration under `./prisma/migrations` (dev) |\n| `bun run db:generate` | regenerate the Prisma client (auto-runs on `bun install`) |\n| `bun run db:studio` | Prisma Studio |\n\n## Project layout\n\n```\nsrc/\n├── components/ui/         shadcn components (button, input, label, card)\n├── db/\n│   └── index.ts           PrismaClient singleton (HMR-safe)\n├── lib/\n│   ├── auth.ts            better-auth server config\n│   ├── auth-client.ts     better-auth React client\n│   ├── env.ts             zod-validated env\n│   └── utils.ts           cn()\n├── trpc/\n│   ├── init.ts            initTRPC + publicProcedure / protectedProcedure\n│   ├── router.ts          appRouter (`me`, `posts.list`, `posts.create`)\n│   └── react.tsx          TRPCReactProvider\n├── routes/\n│   ├── __root.tsx         layout, navbar, session loader\n│   ├── index.tsx          public landing\n│   ├── sign-in.tsx        email + password\n│   ├── sign-up.tsx        email + password\n│   ├── dashboard.tsx      protected, exercises tRPC + Prisma\n│   └── api/\n│       ├── auth/$.ts      mounts better-auth at /api/auth/*\n│       └── trpc/$.ts      mounts tRPC at /api/trpc/*\n├── styles/app.css         Tailwind v4 + shadcn tokens\n└── router.tsx             createRouter\n\nprisma/\n└── schema.prisma          User / Session / Account / Verification + Post\n```\n\n## How auth flows\n\n1. `__root.tsx` calls a server function that reads the session cookie and returns it as router context.\n2. Every route can read `Route.useRouteContext().session`.\n3. Protected routes (`dashboard`) check `context.session` in `beforeLoad` and `redirect()` if missing.\n4. `authClient.signIn.email({ email, password })` POSTs to `/api/auth/sign-in/email`; cookie is set; `router.invalidate()` re-runs the root loader so `session` is populated.\n5. `auth.handler(request)` is mounted at `routes/api/auth/$.ts` (catch-all splat).\n\n## How tRPC flows\n\n- `routes/api/trpc/$.ts` is a catch-all server route that dispatches to `appRouter` via `fetchRequestHandler`.\n- The tRPC context calls `auth.api.getSession({ headers: req.headers })` so every procedure sees `ctx.session`.\n- `protectedProcedure` throws `UNAUTHORIZED` when there is no session.\n- The client uses `@trpc/tanstack-react-query`: `useTRPC()` exposes `trpc.posts.list.queryOptions()` etc.\n\n## SSR data hydration\n\n`getRouter()` creates a per-request `QueryClient` + `TRPCClient` + `createTRPCOptionsProxy` and hands them to `setupRouterSsrQueryIntegration`. That dehydrates queries into the SSR HTML and rehydrates them into a fresh `QueryClient` on the browser. Prefetch in a route loader:\n\n```ts\nexport const Route = createFileRoute('/dashboard')({\n  loader: async ({ context }) =\u003e {\n    await context.queryClient.prefetchQuery(context.trpc.posts.list.queryOptions())\n  },\n  component: DashboardPage,\n})\n```\n\nThe component reads the same query with `useQuery(useTRPC().posts.list.queryOptions())` and gets cached data with no flicker.\n\nCookies are forwarded into the SSR-side tRPC client via `httpBatchLink({ headers })`, so `auth.api.getSession()` works inside the tRPC context during SSR. Both `publicProcedure` and `protectedProcedure` queries are safe to prefetch from loaders. The wiring uses a server-only helper (`src/lib/ssr-cookie-headers.ts`) gated by `createIsomorphicFn` so the import is tree-shaken out of the client bundle — see `CLAUDE.md` for the rules around editing it.\n\n## Deploy to Render\n\n1. Push this repo to GitHub.\n2. In Render, click **Blueprints → New Blueprint Instance**, point at the repo. The `render.yaml` provisions a managed Postgres database and a web service.\n3. After the first deploy, set `BETTER_AUTH_URL` to the public URL Render assigned (e.g. `https://tan-starter.onrender.com`) and redeploy.\n4. `preDeployCommand` runs `prisma db push --accept-data-loss` so your schema is applied automatically on every deploy. For real production workflows, commit migrations (`bun run db:migrate` locally) and switch the pre-deploy step to `prisma migrate deploy`.\n\n`DATABASE_URL` is wired to the managed Postgres automatically. `BETTER_AUTH_SECRET` is generated by Render once.\n\n**Why `runtime: node` instead of `runtime: bun`?** Render's blueprint spec doesn't have a `bun` runtime — but the Node runtime ships with Bun preinstalled, and setting `BUN_VERSION` in `envVars` activates it. Your build/start commands then just use `bun` directly. Bump `BUN_VERSION` in `render.yaml` to upgrade.\n\n## Adding a shadcn component\n\n```bash\nbunx --bun shadcn@latest add dialog\n```\n\nIt will land in `src/components/ui/` and respect the aliases in `components.json`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdested%2Ftan-starter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdested%2Ftan-starter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdested%2Ftan-starter/lists"}