Dashboard App Structure
Section titled “Dashboard App Structure”Intended organization of apps/dashboard. For dependency rules see
03-dependency-rules.md.
The authoritative per-file rule set lives in apps/dashboard/ARCHITECTURE.md. This document is a higher-level summary for repo navigation.
Top-level layout
Section titled “Top-level layout”apps/dashboard/ src/ app/ App wiring: root router, root layout, entry points auth/ Client-side auth helpers and auth-client bootstrap app/ layouts/ App-shell and layout components (RootLayout, DashboardLayout, page intro) shared/ ui/ Shared low-level UI primitives (shadcn-style) No feature imports allowed here. features/ In-app domain feature modules (see below) hooks/ Cross-feature React hooks lib/ App-wide pure helpers, thin re-exports, locale wiring locales/ i18n locale files by language code routes/ TanStack Router file-based routes {-$locale}/ Locale-prefixed route group (dashboard)/ Authenticated dashboard routes login.tsx Auth routes (outside dashboard group) server/ auth/ Better Auth config, trusted origins, CSRF email/ SES email sending, templates, notification queue env/ Cloudflare binding types and accessors functions/ createServerFn() handlers, grouped by domain publications/ people/ works/ projects/ activity/ workspace-users/ workspace-settings/ workspace-modules/ site-tools/ import/ wrangler.jsonc Cloudflare Worker config (routes, bindings) vite.config.ts Vite + @cloudflare/vite-plugin vitest.config.ts Vitest workspace config ARCHITECTURE.md Detailed per-file placement rules (read this first)Route files (thin)
Section titled “Route files (thin)”Route files under src/routes/ are thin by design. A route file:
- defines the route (
createFileRoute) - declares search param validation (
validateSearch) - declares a route loader (
loader) — delegates to server functions - renders a feature-owned page entry from
src/features/{domain}/pages/ - defines route-scoped error and pending boundaries
A route file must not contain:
- business logic blocks
- large inline queries or mutations
- feature-specific transforms
- reusable UI sections
Server functions
Section titled “Server functions”src/server/functions/{domain}/index.ts is the correct location for all
createServerFn() definitions. This is where:
- D1 queries run
- auth sessions are checked
- KV reads/writes happen
- email sending is triggered
Server functions are never statically imported by client code. Feature packages
call them through api.ts using dynamic import() + call().
Features
Section titled “Features”apps/dashboard/src/features/{domain}/ is the canonical home for all
dashboard page components and screen composition.
Dashboard route files render feature page components. Feature page components live in the app, not in packages.
apps/dashboard/src/features/{domain}/ api.ts Calls server functions (dynamic import + call()) queries.ts TanStack Query options and hooks query-keys.ts Query key factory mutations.ts Mutation hooks model/ Feature-local route/search/model helpers types.ts Domain-local types components/ Domain UI components pages/ Domain page components and route-facing page entries ← canonical here index.ts Optional route/app-facing page barrel when the feature exposes multiple pages hooks/ Domain-specific hooks index.ts Public exports (only if needed)Feature packages (packages/feature-{domain}/) may own the data and logic
layer for a domain (query helpers, API wrappers, mutation hooks, reusable
components, domain types). They must not own dashboard page components or
route-option bundles. See
07-feature-source-of-truth.md for the
complete ownership model and violation inventory.
Practical rule: If you are adding or changing a dashboard page or screen,
put it in apps/dashboard/src/features/{domain}/pages/, not in
packages/feature-{domain}/.
| Code type | Canonical location |
|---|---|
| Dashboard page component | apps/dashboard/src/features/{domain}/pages/ |
| Route-facing page entry | apps/dashboard/src/features/{domain}/pages/ |
| Route guard / preload helper | apps/dashboard/src/features/{domain}/model/route.ts |
Route-option bundle (*RouteOptions) | apps/dashboard/src/features/{domain}/pages/ or the route file |
| Feature UI component (dashboard-specific) | apps/dashboard/src/features/{domain}/components/ |
| Query helpers / queryOptions | packages/feature-{domain}/src/ (if extracted) |
| API transport wrappers | packages/feature-{domain}/src/ (if extracted) |
| Mutation hooks | packages/feature-{domain}/src/ (if extracted) |
| Domain types (shared) | packages/feature-{domain}/src/ (if extracted) |
Shared UI (src/shared/ui/)
Section titled “Shared UI (src/shared/ui/)”- low-level primitives only (button, input, card, dialog, select, table primitives, form primitives)
- no feature imports — these are pure primitives
- no business/domain-named components in this folder
App-shell layout (src/app/layouts/)
Section titled “App-shell layout (src/app/layouts/)”- owns shell framing and cross-feature layout pieces
- examples:
DashboardLayout,RootLayout,DashboardPageIntro - may be consumed by many features and feature packages via
@host/app/layouts/*
src/lib/ — app helpers only
Section titled “src/lib/ — app helpers only”Valid content: pure utilities, formatting helpers, locale wiring, route helpers, thin platform re-exports, generic constants.
Invalid content: Cloudflare bindings, D1/KV access, auth enforcement, feature-specific logic, vague mixed-purpose files.
- Config:
src/server/auth/config.ts(Better Auth) - Trusted origins:
src/server/auth/trusted-origins.ts - Client bootstrap:
src/features/auth/client.ts - Sessions stored in D1 (
auth_users,auth_sessions,auth_accounts,auth_verifications) - CSRF protection documented in
apps/dashboard/docs/CSRF_PROTECTION.md
Locale / i18n
Section titled “Locale / i18n”- Shell-level bootstrap:
@legaciti/platform-i18n - Dashboard locale files:
apps/dashboard/src/locales/{locale}/ - Validate key parity:
pnpm check-locale-keys
Generated files (do not edit manually)
Section titled “Generated files (do not edit manually)”| File | Generated by |
|---|---|
src/routeTree.gen.ts | TanStack Router — regenerated on build/dev |
src/generated/events/emitters.ts | pnpm --filter @apps/dashboard events:generate — event bus artifact |
Key commands
Section titled “Key commands”pnpm dev:dashboard # local dev with remote D1pnpm dev:dashboard:remote # same, explicit remote flagpnpm --filter @apps/dashboard buildpnpm --filter @apps/dashboard type-checkpnpm --filter @apps/dashboard lintpnpm --filter @apps/dashboard testpnpm --filter @apps/dashboard events:generate # regenerate event artifacts