Project Structure
How the monorepo is laid out - deployable apps, shared @repo/* packages, and the rule that apps never import from sibling apps.
A pnpm + Turborepo monorepo: thin deployable apps/* on top of shared @repo/* logic in packages/*. Workspace globs (apps/*, packages/*, tooling/*) live in pnpm-workspace.yaml; turbo.json orders the builds.
Apps
| Directory | Role |
|---|---|
apps/web | Next.js App Router frontend (React 19, next-intl) - your application |
apps/backend | Standalone Hono server (@hono/node-server) mounting @repo/api, for the separate-backend deploy. In fullstack mode the frontend hosts the API itself |
apps/docs | Fumadocs docs site; surfaces a "Docs" nav link when config.docs.enabled is true. Included only with generatesaas init --docs |
Apps never import from sibling apps. Anything shared (auth, billing, config, types, helpers) must live in a packages/* package, so there is exactly one place to change shared behavior. Cross-app imports break independent deploys.
Packages
The backend is one Hono app composed from these @repo/* packages; the frontend consumes only the ones it needs.
| Package | Purpose |
|---|---|
@repo/config | Central config object - feature flags + routes nearly every package checks |
@repo/api | Hono app; mounts routes and exports AppType for RPC typing |
@repo/auth | Better Auth config (session, social, 2FA, API keys, passkeys) |
@repo/database | Drizzle schema + client |
@repo/payments | Stripe/Polar billing - plans, credits, products, organizations |
@repo/mail / @repo/sms | Transactional email and SMS senders |
@repo/storage | File/object uploads (S3 or local) |
@repo/notifications / @repo/admin-notifications | In-app user notifications and admin alerts |
@repo/audit | Audit logging |
@repo/content | Shared markdown (legal pages + every content section) |
@repo/ai | AI helpers built on the ai SDK |
@repo/observability | Sentry / PostHog wiring shared by server, browser, and desktop |
@repo/runtime | Env, logger, Redis, rate-limit store, Inngest client, request helpers |
@repo/i18n / @repo/utils / @repo/styles | Shared translations, helpers, and styles |
@repo/ui | Shared shadcn/ui React primitives on Base UI plus the cn helper; pnpm dlx shadcn add writes here |
@repo/app-core | Shared client-side logic (hooks, headless controllers, stores) consumed by the web and desktop apps; DOM-free so a future mobile app can use it too - see Client Architecture |
@repo/e2e-support | App-agnostic Playwright fixtures - throwaway database, Mailpit, Inngest sync, device grant - shared by every e2e suite |
Backend packages target web-standard APIs (crypto.randomUUID(), node: imports, Uint8Array) so the backend stays portable across Vercel, Docker, Fly, Railway, and more - see deployment.
How the frontend connects to the backend
apps/web never imports @repo/database or @repo/payments directly - it talks to the backend over its own typed clients, and AppType flows from @repo/api so a backend route change is type-checked in the frontend.
| Concern | File | Export | Built from |
|---|---|---|---|
| RPC client (browser) | lib/api/client.ts | api | hc<AppType> from @repo/api |
| RPC client (SSR) | lib/api/server.ts | getServerApi() | hc<AppType> + cookie forwarding via next/headers |
| Auth client | lib/auth-client.ts | authClient | createAuthClient (Better Auth) |
SSR cookie forwarding is framework-specific - Next forwards via next/headers. Keep that wiring in the app; never abstract it into a shared package.
Inside apps/web
apps/web/
app/
[locale]/ # locale-prefixed routes, layouts, pages
api/[[...rest]]/route.ts # single optional catch-all; runs app.fetch from @repo/api
md/[locale]/[[...slug]]/ # markdown renditions served to AI agents
layout.tsx, sitemap.ts, robots.txt/, llms.txt/
components/ # UI (demo/ holds isolated demo content)
config/ # per-app navbar, sidebar, user-menu, banner
hooks/ lib/ providers/
i18n/request.ts # next-intl messages via getMessagesForLocale(locale, scope)
proxy.ts # request proxy / middleware layer
package.json # NOTE: no "type":"module" (avoids ERR_REQUIRE_ESM on Vercel)- No separate route files for auth, jobs, or
@repo/api. The one catch-allapp/api/[[...rest]]/route.tsforwards every method toapp.fetch, and the Hono app mounts the Better Auth handler (/auth/*) and the Inngest endpoint (/inngest) internally. - The catch-all imports the backend lazily on first request, so
next buildnever evaluates backend code and production Docker images build with no runtime env. - Translations use
next-intl-useTranslations()in components, messages loaded server-side ini18n/request.ts(scope fromNEXT_PUBLIC_I18N_SCOPE, default"web"). - Theming is owned by
next-themes; theme and consent reads are client-side so marketing routes stay prerendered.
Client Architecture
apps/*, @repo/ui and @repo/app-core split client code, and where yours goes.Configuration
@repo/config flags and routes that gate every capability.API
@repo/api Hono backend and the typed hc<AppType> client.Tech Stack
Separate backend
apps/backend on its own host.Integration keys
Every per-provider environment variable, the feature it powers, the config flag it pairs with, and the base-URL overrides that repoint a vendor.
Development
The everyday workflow - root pnpm + Turborepo scripts, the enforced code style, the pre-commit translate hook, and the workspace-safe way to add dependencies.