Skip to content

Architecture

Tech stack

CategoryTechnology
FrameworkNext.js 16 (App Router, Turbopack)
LanguageTypeScript (strict mode)
API LayertRPC v11 + TanStack Query v5
DatabasePostgreSQL + Prisma ORM
AuthBetter Auth (email/password + Google + GitHub OAuth)
PaymentsPolar.sh (subscriptions + customer portal)
Background JobsInngest
AIVercel AI SDK + OpenRouter
CacheRedis
AnalyticsPlausible (self-hosted or cloud, optional)
UIshadcn/ui + Tailwind CSS v4
i18nnext-intl (en + de)
StateZustand (client) + TanStack Query (server)
Input ValidationZod
FormsReact Hook Form + Zod
Linting/FormattingBiome (not ESLint/Prettier)
Package manager / runtimeBun

There are no automated tests and no CI configuration in this repo - it's a starter template, not a production codebase with an established test suite.

Request flow

  1. React components call tRPC via TanStack Query hooks (useTRPC() from @/trpc/client).
  2. The tRPC router at src/trpc/routers/_app.ts aggregates all feature routers.
  3. Feature routers in src/features/*/server/router.ts handle business logic and call Prisma.
  4. Background jobs are defined in src/inngest/functions.ts and served via /api/inngest.
  5. Redis is available via getRedis() from @/lib/redis for caching/rate limiting.
  6. Auth endpoints are served by Better Auth at /api/auth/[...all].

There is no middleware.ts - route protection happens per-layout via server-side requireAuth() / requireUnauth() calls (see auth-and-billing.md), not Next.js middleware.

Project structure

src/
├── app/                              # Next.js App Router
│   ├── (auth)/                       # Unauthenticated route group
│   │   ├── layout.tsx                 Centered card layout
│   │   ├── login/page.tsx             "/login"
│   │   └── signup/page.tsx            "/signup"
│   ├── (dashboard)/                  # Authenticated route group
│   │   ├── layout.tsx                 Calls requireAuth(), wraps children in AppShell
│   │   ├── dashboard/page.tsx         "/dashboard"
│   │   ├── settings/page.tsx          "/settings" - language toggle
│   │   └── demo/                      "/demo/*" - see features.md
│   │       ├── ai/page.tsx
│   │       ├── booking/page.tsx
│   │       ├── customers/page.tsx
│   │       ├── inngest/page.tsx
│   │       └── posts/page.tsx
│   ├── actions/set-locale.ts         # Server actions: set/clear the "ui-locale" cookie
│   ├── api/
│   │   ├── auth/[...all]/route.ts    # Better Auth catch-all (sign-in/up/out, OAuth, Polar checkout/portal)
│   │   ├── chat/route.ts             # POST - AI SDK streamText via OpenRouter (resumable)
│   │   ├── chat/[id]/stream/route.ts # GET - resumes an in-flight/completed chat stream
│   │   ├── inngest/route.ts          # Inngest's Next.js serve handler - registers all functions
│   │   └── trpc/[trpc]/route.ts      # tRPC fetch adapter for the whole appRouter
│   ├── layout.tsx                    # Root layout: fonts, next-intl, TRPC provider, Plausible, devtools
│   └── page.tsx                      # "/" - redirects to /dashboard
├── components/
│   ├── ui/                           # shadcn/ui primitives (see frontend.md)
│   ├── app-shell.tsx                 # Dashboard shell wrapping the sidebar nav
│   ├── app-sidebar.tsx               # Sidebar navigation content
│   ├── confirm-dialog.tsx            # Standard delete/confirm dialog
│   ├── critical-confirm-dialog.tsx   # Retype-to-confirm dialog for destructive actions
│   └── language-toggle.tsx           # i18n locale switcher
├── features/                         # Domain features - see features.md
│   ├── auth/                         # Login/register/logout, user info card
│   ├── demo/
│   │   ├── ai/                       # Streaming AI chat demo
│   │   ├── booking/                  # Multi-step wizard demo (Zustand)
│   │   ├── customers/                # Paginated/filterable table demo
│   │   ├── inngest/                  # Trigger + live-status job demo
│   │   └── posts/                    # Canonical CRUD example (real Prisma model)
│   ├── subscriptions/                # Polar subscription-state hooks
│   └── user/                         # Current-user data + isPolarEnabled flag
├── lib/                              # See "Key files" below
├── trpc/
│   ├── init.ts                       # baseProcedure, protectedProcedure, premiumProcedure
│   ├── client.tsx                    # TRPCReactProvider (IndexedDB persistence, 7-day cache)
│   ├── server.tsx                    # Server-side caller + HydrateClient
│   ├── query-client.ts               # TanStack Query config
│   └── routers/_app.ts               # Root router - register new feature routers here
├── inngest/
│   ├── client.ts                     # Inngest client instance
│   ├── functions.ts                  # Background job definitions
│   └── realtime.ts                   # Realtime channel definitions
├── i18n/request.ts                   # next-intl config (cookie-based locale, en/de)
├── stores/ui-store.ts                # Zustand example store (sidebar collapsed state)
├── config/
│   ├── env.ts                        # Zod-validated environment variables
│   └── constants.ts                  # App-wide constants (APP_NAME, PAGINATION, ...)
└── generated/prisma/                 # Generated Prisma client (gitignored, from `prisma:generate`)

Key files

FilePurpose
src/lib/db.tsPrisma singleton (prevents multiple instances in dev under HMR)
src/lib/auth.tsBetter Auth server config (email/password + OAuth + Polar plugin)
src/lib/auth-client.tsBetter Auth client-side hooks
src/lib/auth-utils.tsrequireAuth(), requireAuthAndPremium(), requireUnauth(), getSessionOnServer()
src/lib/redis.tsRedis client - getRedis() for lazy-connect
src/lib/polar.tsisPolarEnabled flag + polarClient - the central "is billing configured" gate
src/lib/ai.tsOpenRouter provider client + AI_MODEL
src/lib/stream-context.tsResumable-stream context for the AI chat demo
src/lib/analytics.tsPlausibleEvents type for typed usePlausible() custom events
src/config/env.tsZod-validated env object - see environment-variables.md
src/config/constants.tsApp-wide constants
src/trpc/init.tstRPC procedures + middleware
src/trpc/routers/_app.tsRoot router - register new routers here
src/inngest/client.tsInngest client instance
src/inngest/functions.tsBackground job definitions
src/i18n/request.tsnext-intl config
prisma/schema.prismaDatabase schema

tRPC procedure types

ProcedureAuth requiredPremium required
baseProcedureNoNo
protectedProcedureYes (Better Auth session)No
premiumProcedureYesYes (Polar active subscription, or always when Polar is unconfigured)

Defined in src/trpc/init.ts. protectedProcedure throws UNAUTHORIZED if there's no session. premiumProcedure builds on it and additionally throws FORBIDDEN unless the user has an active Polar subscription - except when Polar isn't configured at all (isPolarEnabled is false), in which case it grants access to everyone, so premium-gated demos still work with zero Polar setup.

Where to go next