Skip to content

Dashboard App Structure

Migrated from root technical docs.

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.


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 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

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().


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 typeCanonical location
Dashboard page componentapps/dashboard/src/features/{domain}/pages/
Route-facing page entryapps/dashboard/src/features/{domain}/pages/
Route guard / preload helperapps/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 / queryOptionspackages/feature-{domain}/src/ (if extracted)
API transport wrapperspackages/feature-{domain}/src/ (if extracted)
Mutation hookspackages/feature-{domain}/src/ (if extracted)
Domain types (shared)packages/feature-{domain}/src/ (if extracted)

  • 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
  • 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/*

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

  • Shell-level bootstrap: @legaciti/platform-i18n
  • Dashboard locale files: apps/dashboard/src/locales/{locale}/
  • Validate key parity: pnpm check-locale-keys

FileGenerated by
src/routeTree.gen.tsTanStack Router — regenerated on build/dev
src/generated/events/emitters.tspnpm --filter @apps/dashboard events:generate — event bus artifact

Terminal window
pnpm dev:dashboard # local dev with remote D1
pnpm dev:dashboard:remote # same, explicit remote flag
pnpm --filter @apps/dashboard build
pnpm --filter @apps/dashboard type-check
pnpm --filter @apps/dashboard lint
pnpm --filter @apps/dashboard test
pnpm --filter @apps/dashboard events:generate # regenerate event artifacts