TanStack Start + Better Auth
Single skill for library configuration and Start-specific wiring in FMC apps (TILDA, Trassenscout migration, etc.).
When to apply
- Creating or changing
betterAuth()/createAuthClient()config - Mounting
/api/auth/*,tanstackStartCookies()plugin, OAuth sign-in routes beforeLoadgates,endpointAuth, session in server functions or API handlers- Public vs protected server functions (
public*.functions.tsnaming) - API key + session dual auth on automation endpoints
References (read as needed)
| Topic | File |
|---|---|
| TanStack routes, cookies, session helpers, TILDA patterns | references/auth.md |
| Trassenscout auth lint pattern | references/endpoint-auth-lint.md |
| betterAuth options, DB, plugins, sessions, hooks | references/better-auth-config.md |
Live docs: better-auth.com/docs · llms.txt
Related skills
tanstack-router-conventions—validateSearch, search serialization, loaders + Querytanstack-start-conventions— layout,.server.ts/.functions.ts, boundaries, SSRplaywright-skill— E2E stubbed auth (TILDA)
Non-negotiable rules
- Session helpers always receive
headers: Headersfrom the current request. - Route
beforeLoaddoes not call DB/session directly — server functions +getRequestHeaders(). - Use official
tanstackStartCookies()as the last plugin; verify no client-bundle leak afterbetter-authversion changes (auth.md). /api/auth/$and other handler-only API routes:ssr: false(selective-ssr.md).- API routes: auth inside each handler (no route-level
beforeLoad). - Admin/role checks with cookie cache:
getSession({ query: { disableCookieCache: true } }). - Compare API secrets with timing-safe equality.
- Two layers:
beforeLoad= route UX only;endpointAuth/ guards = data boundary. Never rely onbeforeLoadalone. - No auth middleware for route guarding — use layout
beforeLoad. Global auth middleware breaks public RPCs (public*.functions.ts,getSessionForRouteFn) and is not the FMC auth-route pattern.
Quick patterns
// beforeLoad (via server fn)
await routeSessionFn({ data: location })
// API handler
const session = await endpointAuth.session(request.headers)
// Server function
const headers = getRequestHeaders()
await endpointAuth.admin(headers)
Setup snapshot
betterAuth()inauth.server.ts(Prisma/Drizzle adapter, plugins)createAuthClient()with matching client plugins/api/auth/$→auth.handler(request)(cookies viatanstackStartCookies()plugin)session.server.tshelpers takingHeaders- CLI migrate/generate after plugin changes — details in better-auth-config.md