Project Structure
Navigate the apps and @repo/* packages, plus the rule that apps never import siblings.
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; your frontend is apps/web-next.
Apps
Each app is independently deployable and owns only framework-specific code.
| Directory | Role |
|---|---|
apps/web-next | Next.js App Router frontend (React 19, next-intl) - your application |
apps/backend | Standalone Hono server (@hono/node-server) mounting @repo/api; used by 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 |
generatesaas init ships exactly one frontend, leaving only apps/web-next. apps/docs is included only with generatesaas init --docs and removed otherwise.
Apps never import from sibling apps. Anything shared (auth, billing, config, types, helpers) must live in a packages/* package - 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 + blog) |
@repo/ai | AI helpers built on the ai SDK |
@repo/runtime | Env, logger, Redis, rate-limit store, Inngest client, request helpers |
@repo/i18n / @repo/utils / @repo/styles | Shared translations, helpers, and styles |
Backend packages target web-standard APIs (crypto.randomUUID(), node: imports, Uint8Array) so the backend stays portable across Vercel, Docker, Fly, Railway, and more.
How the frontend connects to the backend
apps/web-next never imports @repo/database/@repo/payments/etc. directly - it talks to the backend over its own typed clients.
| 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) |
AppType flows from @repo/api, so a backend route change is type-checked in the frontend.
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-next
A standard Next.js App Router layout. Key paths:
apps/web-next/
app/
[locale]/ # locale-prefixed routes, layouts, pages
api/[[...rest]]/route.ts # single optional catch-all; runs app.fetch from @repo/api
layout.tsx, sitemap.ts, robots.ts
components/ # UI (demo/ holds isolated demo content)
config/ # per-app navbar, sidebar, user-menu, banner (icons imported directly)
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)There are no separate Next route files for auth, jobs, or @repo/api - the one catch-all app/api/[[...rest]]/route.ts forwards every method to app.fetch from @repo/api, and the Hono app mounts the Better Auth handler (/auth/*) and the Inngest background-jobs endpoint (/inngest) internally.
- Translations use
next-intl-useTranslations()in components; messages load server-side ini18n/request.ts(scope fromNEXT_PUBLIC_I18N_SCOPE, default"web"). - Theming is owned by
next-themes; theme/consent reads are client-side so marketing routes stay prerendered. - Navigation, sidebar, user-menu, banners are per-app in
config/(icons imported directly - see./navigationand./banners).
Frequently asked questions
Why are apps forbidden from importing each other?
So each frontend deploys independently and shares logic only through @repo/* packages. A sibling import would couple two deploy targets together.
Where does a new shared utility go?
Into the most specific existing @repo/* package (or a new one) - never into an app. Apps hold only framework-specific code: components, pages, layouts, proxy.ts.
Why does apps/web-next/package.json omit "type":"module"?
Including it triggers ERR_REQUIRE_ESM on Vercel under Next 16. Leaving it off is intentional - don't re-add it.