Imported from dengzhaofun/apollokit (
AGENTS.md). Install upstream withnpx skills add dengzhaofun/apollokit. Copyright stays with the author.
apollokit — Agent Guide
Durable project context for AI coding agents (Claude Code, Cursor, Copilot, etc.). Keep this file up to date as the project evolves.
Project purpose
apollokit is a game SaaS platform. The frontend handles both the public-facing marketing site and the authenticated dashboard / project-management console in one TanStack Start app. A separate Hono worker backs it as the API + auth layer.
Monorepo layout
apps/
admin/ TanStack Start (Vite + React 19 + Tailwind v4) — marketing site + dashboard
Dev: http://localhost:3000 · Deploy target: Cloudflare Workers (apollokit-admin)
server/ Hono on Cloudflare Workers — API, Better Auth, Drizzle, Neon Postgres
Dev: http://localhost:8787 (wrangler default) · Deploy target: apollokit-server
packages/
ui/ @repo/ui — shared React components (source-level export, no build step)
Not currently consumed by admin; reserved for future shared UI.
eslint-config/ @repo/eslint-config — entries: ./base, ./next-js, ./react-internal
typescript-config/@repo/typescript-config — shared tsconfig bases (base, nextjs, react-library)
pnpm-workspace.yaml globs apps/* and packages/*. Internal packages are consumed via workspace:*.
Previously present, removed
apps/web and apps/docs were create-turbo default-template scaffolds with no business code. Removed in commit b3830b6 to clear the way for apps/admin. Do not recreate them.
Tech stack per app
| App | Framework | Bundler | React | Styling | Testing | Deploy |
|---|---|---|---|---|---|---|
admin |
TanStack Start | Vite 7 | 19.2 | Tailwind v4 | Vitest + RTL | Cloudflare Workers |
server |
Hono + Zod OpenAPI | (wrangler) | — | — | — | Cloudflare Workers |
server additionally uses Better Auth (^1.6.2), Drizzle ORM (^0.45.2), @neondatabase/serverless.
Commands
Run from repo root unless noted:
pnpm dev— turbo runsdevin every app with adevscript (persistent, not cached). Starts admin on 3000 and server via wrangler on 8787 in parallel.pnpm build— turbobuildacross the graph.apps/serverhas nobuildscript (deploys via wrangler, not turbo).pnpm lint— turbolint. Both admin and server participate.pnpm check-types— turbocheck-types. Admin runstsc --noEmit; server runstsc --noEmit.pnpm format— prettier on**/*.{ts,tsx,md}(run at root, NOT via turbo).
Filter a single workspace: pnpm turbo <task> --filter=admin (or server, @repo/ui, …).
Per-app:
- admin:
pnpm --filter=admin dev|build|preview|test|deploy. Deploy doesvite build && wrangler deploy. - server:
pnpm --filter=server dev(wrangler dev);pnpm --filter=server deploy(wrangler deploy --minify); Drizzle helpersdb:generate,db:migrate,db:push,db:studio; Better Auth schemaauth:generate.cf-typegenregeneratesCloudflareBindingsfromwrangler.jsonc.
Environment variables
Declared in turbo.json globalEnv:
DATABASE_URL— Neon Postgres connection string (Better Auth + Drizzle)BETTER_AUTH_SECRET— Better Auth signing secretBETTER_AUTH_URL— Better Auth base URL
Local dev values live at apps/server/.dev.vars (read by wrangler; not checked in). When admin needs to talk to server auth, surface the same BETTER_AUTH_URL via Vite-exposed env (prefix VITE_).
Deployment
apps/server→ Cloudflare Workers, project nameapollokit-server, compat date2026-04-11,nodejs_compatflag, observability on. Secrets managed withwrangler secret put.apps/admin→ Cloudflare Workers, project nameapollokit-admin, compat date2026-04-11,nodejs_compatflag, observability on. Entry is@tanstack/react-start/server-entry(scaffolded default for@cloudflare/vite-plugin).- Wrangler pinned to
^4.70.0in both workspaces.
Scaffold history (why admin looks like it does)
Admin was scaffolded with:
cd apps
pnpm dlx @tanstack/cli@latest create admin --add-ons cloudflare --no-examples
The original user-provided command was:
npx @tanstack/cli@latest create my-tanstack-app --agent --tailwind --add-ons cloudflare
It was adjusted because, in @tanstack/cli@0.63+:
--agentis not a valid flag (silently ignored, absent from help)--tailwindis a no-op (Tailwind is now the default)- There is no
--template blank;--no-examplesis the closest "minimal" switch - Project name
admin(positional) scaffolds directly intoapps/admin/when run fromapps/, avoiding a rename
Post-scaffold cleanup applied:
- Removed the CLI-generated nested
.git/and per-appnode_modules/(pnpm handles deps from root) - Rewrote
apps/admin/package.jsonto expose repo-standardlint+check-typesscripts and depend on@repo/eslint-config - Replaced
apps/admin/eslint.config.jswith one that imports@repo/eslint-config/react-internal(admin is Vite, NOT Next — do not use thenext-jspreset) - Kept
apps/admin/tsconfig.jsonstand-alone (likeapps/server) because the scaffold'smoduleResolution: "bundler"is incompatible with@repo/typescript-config/base.json'sNodeNext - Rewrote
apps/admin/wrangler.jsoncwithapollokit-adminname,2026-04-11compat date, observability on - Bumped
apps/serverwrangler from^4.4.0→^4.70.0to match admin (unified version) - Moved
pnpm.onlyBuiltDependenciesfrom admin's package.json to the workspace root
Turbo task contract
Every app must expose scripts named dev, build, lint, check-types — these are the only task names defined in turbo.json. apps/server is exempted from build (deploys via wrangler directly) but still has lint + check-types.
Do not add new turbo tasks without also declaring them in turbo.json. format is intentionally kept outside turbo (runs directly via prettier at root).
API response envelope
Every apps/server business route — everything under /api/* except the Better Auth mounts /api/auth/* and /api/client/auth/* — returns the standard envelope { code, data, message, requestId }. Success uses code: "ok"; module errors carry prefixed codes like check_in.config_not_found. DELETE / ack endpoints return HTTP 200 with data: null (never 204) so clients unwrap uniformly without branching on status. Full rules and the "how to write a route" checklist live in apps/server/CLAUDE.md § "Response envelope" — do not duplicate here.
Server-side helpers come from apps/server/src/lib/response.ts: ok(), fail(), envelopeOf(), commonErrorResponses, NullDataEnvelopeSchema. Route factories in apps/server/src/lib/openapi.ts already wire the validation and ModuleError → envelope error handlers.
Admin-side consumers call the server through apps/admin/src/lib/api-client.ts, which auto-unwraps .data on success and normalizes errors into ApiError (carries code, message, requestId). Call sites keep their original "resource object" typings — the envelope is invisible above the wrapper. No app code imports the generated SDK packages directly yet; they exist for future tenant-frontend consumers.
Test helpers live at apps/server/src/testing/envelope.ts: expectOk<T>(res) returns the unwrapped payload and asserts code === "ok"; expectFail(res, code) asserts a specific error code. Route-layer tests should use these — do not hand-write body.code === "ok" + drill .data.
After changing any server route schema, refresh the OpenAPI + SDK artifacts so the committed specs stay honest:
# Start wrangler dev (needs .dev.vars) in one terminal:
pnpm --filter=server dev
# Then in another terminal: one-shot pipeline
# (dump → cp → split → server SDK gen → client SDK gen → inject samples → admin docs)
pnpm sdks:generate
The single sdks:generate script ships the whole chain. If you need to run individual steps for debugging, the underlying commands are pnpm --filter=server openapi:dump → pnpm --filter=@repo/sdk-core run split → pnpm --filter=@apollokit/{server,client} run generate → pnpm --filter=@repo/sdk-core run inject-samples → pnpm --filter=admin run gen:api-docs.
pnpm --filter=@repo/sdk-core extract is the legacy in-process extractor — it trips on import "cloudflare:workers" in plain Node and is superseded by openapi:dump + cp inside sdks:generate. Don't use extract in new flows.
Known gotchas
- admin is Vite, NOT Next.js. Never apply
@repo/eslint-config/next-js, never importnext-only utilities, never extend@repo/typescript-config/nextjs.json. - TanStack Start does not support RSC (as of 1.167+). Use server functions (
createServerFn) and server routes instead. - Nested git/node_modules from scaffold.
@tanstack/cli createinitializes its own git repo and per-projectnode_modules. Always remove both after scaffolding into a monorepo. - @tanstack/cli flags change.
--agent,--tailwind, and--template blankhave all changed meaning or disappeared. Verify current behavior withpnpm dlx @tanstack/cli@latest create --helpbefore copying old commands. - pnpm onlyBuiltDependencies must live at workspace root, not in individual app package.json — pnpm explicitly warns about this.
- Wrangler compat dates must be ≤ today.
2026-04-11matches the project's current frozen date (seeapps/server/wrangler.jsonc). --no-examplesis cosmetic in current@tanstack/cli— the scaffold still ships with a Header/Footer/ThemeToggle and a decorated demo home route. Treat these as starter code to replace, not load-bearing infrastructure.
Next steps (for future agents picking this up)
- Wire admin ↔ server auth: share Better Auth session cookies between
apollokit-adminandapollokit-server. SetBETTER_AUTH_URLand trusted origins accordingly. - Marketing routes: replace the scaffold's
/+/aboutwith real marketing content; introduce a route group for public pages vs. authenticated dashboard. - Dashboard routes: add
(authed)route group with loader-based session guard that calls server's/api/auth/get-session. - Shared UI: if the same components start living in both admin and (hypothetical) future apps, promote to
packages/uiand wire admin to"@repo/ui": "workspace:*". - Tailwind v4 tokens: the scaffold ships with custom CSS variables (
--sea-ink,--lagoon-deep, etc.) insrc/styles.css. Replace with brand tokens before shipping.
TanStack Intent — skill mappings
@tanstack/intent discovered skills shipped by installed TanStack packages. Use these when working on the corresponding areas — load the named SKILL.md into context rather than guessing from memory. Because the skills are transitive deps under .pnpm/, paths are unstable; resolve at runtime with the companion command.
Skill mappings - when working in these areas, load the linked skill file into context.
skills:
- task: "bootstrap, entry points, root route document shell, routeTree.gen.ts, vite plugin wiring"
To load this skill, run: pnpm dlx @tanstack/intent@latest list | grep start-core/SKILL
- task: "deploy admin to Cloudflare Workers, selective SSR per route, SPA mode, static prerender, ISR"
To load this skill, run: pnpm dlx @tanstack/intent@latest list | grep start-core/deployment
- task: "server functions (createServerFn) — calling server-side code from UI without API boilerplate"
To load this skill, run: pnpm dlx @tanstack/intent@latest list | grep start-core/server-functions
- task: "server routes — HTTP endpoints defined on createFileRoute.server (API endpoints inside admin)"
To load this skill, run: pnpm dlx @tanstack/intent@latest list | grep start-core/server-routes
- task: "middleware — request middleware, server-function middleware, global createStart middleware, auth guards"
To load this skill, run: pnpm dlx @tanstack/intent@latest list | grep start-core/middleware
- task: "routing — path params, splat routes, optional params, i18n locale patterns"
To load this skill, run: pnpm dlx @tanstack/intent@latest list | grep router-core/path-params
- task: "search params — validateSearch, Zod/Valibot adapters, search middlewares, loaderDeps"
To load this skill, run: pnpm dlx @tanstack/intent@latest list | grep router-core/search-params
- task: "SSR — streaming, HeadContent/Scripts, createRequestHandler, head meta management"
To load this skill, run: pnpm dlx @tanstack/intent@latest list | grep router-core/ssr
- task: "TanStack type safety — Register declaration, from narrowing, getRouteApi, Link type inference"
To load this skill, run: pnpm dlx @tanstack/intent@latest list | grep router-core/type-safety
- task: "not-found + error handling — notFound(), CatchBoundary, errorComponent, route masking"
To load this skill, run: pnpm dlx @tanstack/intent@latest list | grep router-core/not-found-and-errors
How to use this block: when an agent starts a task that touches one of the listed areas, run the companion command to locate the current SKILL.md path in node_modules/.pnpm/..., then Read that file. Do not rely on memorized TanStack Router/Start API — the library moves fast and your training data is stale.
To refresh this list after upgrading TanStack packages: cd apps/admin && pnpm dlx @tanstack/intent@latest list and update the task descriptions if new skills appear or old ones move.