GenerateSaaS

Tech Stack

Explore the GenerateSaaS tech stack, from Next.js 16 and Hono RPC to Better Auth, Drizzle ORM with Postgres, Inngest, and Redis, fully typed end to end.

GenerateSaaS pairs a Next.js App Router frontend with a fully typed Hono RPC backend, sharing one set of @repo/* packages. Every layer is picked for type safety, deploy portability, and a boilerplate you fully own.

The stack at a glance

Each layer maps to a workspace package or app dependency, with the version pinned in the repo.

LayerTechLives inWhy
FrontendNext.js 16 App Router + React 19apps/web-nextServer Components by default, client only where interactive; prerenders marketing routes
BackendHono 4 RPC@repo/apiEnd-to-end typed client via hc<AppType>(); deploys anywhere, not just Vercel
AuthBetter Auth 1.6@repo/authSessions, OAuth, 2FA, passkeys, orgs, API keys - no third-party auth vendor
DataDrizzle ORM 0.45 + Postgres@repo/databaseTyped schema and queries; SQL you can read, migrations you control
JobsInngest 4@repo/runtimeDurable background/scheduled jobs with retries; no separate queue infra
UIshadcn/ui (@base-ui/react) + Tailwind CSS 4 + Phosphor icons@repo/ui-nextAccessible primitives you copy into the repo and own, in a shared package the app consumes; pnpm dlx shadcn add writes here; utility-first styling
CacheRedis (ioredis or @upstash/redis)@repo/runtimeCaching, rate-limit store, and distributed mutex; the environment variables depend on your cache choice
ValidationZod 4 + standard-schema@repo/api, app formsShared schemas validate API input via sValidator and forms via react-hook-form
Toolingpnpm 11 + Turborepo 2 + TypeScript 6repo rootFast workspace installs, cached task graph, strict types everywhere

There is no @repo/jobs or @repo/cache package - Inngest and Redis both live in @repo/runtime, which the app pulls in transitively through @repo/api.

How the pieces connect

  • The Next.js app imports backend logic only through @repo/* packages - never from a sibling app.
  • It builds a typed RPC client with hc<AppType>(), so a backend route change surfaces as a frontend type error.
  • Server Components read data through the server client; Server Actions and the browser client handle mutations and client reads.
  • next-intl drives i18n and next-themes drives dark mode.

Type safety end to end

The same types flow from database to UI with no codegen step.

// Drizzle schema → inferred row types → Hono route → typed RPC client
import { hc } from "hono/client";
import type { AppType } from "@repo/api";

const api = hc<AppType>(process.env.NEXT_PUBLIC_API_URL!);
const res = await api.posts.$get(); // fully typed response, no manual DTOs

The browser client lives in lib/api/client.ts; Server Components and Server Actions use getServerApi() from lib/api/server.ts, which forwards the session cookie.

Frequently asked questions

Do I have to deploy on Vercel? No. The Hono backend and @repo/* packages use web-standard APIs, so you can host on Vercel, Docker, Fly.io, Railway, or any Node host. See deployment.

Is there a separate API server? By default the Hono API runs inside the Next.js app. You can split it into a standalone backend later - see separate backend.

Is Redis optional? No. Redis backs caching, the rate limiter, and the distributed mutex in every environment. The self-hosted choice uses ioredis and requires REDIS_URL; the Upstash choice uses @upstash/redis and requires UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN - see environment variables.

Where do I configure which features are on? Feature flags live in @repo/config; the typed client and env vars are wired around them. Start with configuration.

Can I still add more shadcn/ui components? Yes. Every components.json pins the base-vega style, so pnpm dlx shadcn add <component> fetches the Base UI build and it drops into @repo/ui-next matching the components already there. Leave that style value alone - changing it pulls a different primitive library into the same app.

Why are dropdown menus and selects frosted glass? Because components.json sets "menuColor": "default-translucent". The vega registry ships menu components with cn-menu-target and cn-menu-translucent markers, and the CLI rewrites those markers at add time into a real surface based on this setting - a bg-popover/70 panel with a blurred before: layer. The markers are build-time only: they are never CSS classes, and if you ever see one survive into a component file, that file was written without the transform running and the panel will render flat. The other values are default, inverted, and inverted-translucent. Change it in every components.json at once, or newly added menus will not match the ones you already have.

The blur strength that setting produces - backdrop-blur-2xl, 40px - is the house standard for every floating panel and for the navbar chrome. The registry only reaches dropdown menus and selects, so popover.tsx, navigation-menu.tsx, the navbar and the bottom nav carry the same surface by hand. Match it on anything new you float over the page.

Re-adding a component you already have is the risky case. Six files in @repo/ui-next carry deliberate edits the registry does not ship. shadcn add prompts before it replaces an existing file and --overwrite skips that prompt, so a rewrite silently reverts them. Only popover.tsx breaks the build; the other five keep typecheck and tests green and surface as broken motion or layout in the browser.

FileLocal editWhat a rewrite costs
accordion.tsxHeight transition on the panelPanels stop animating in both directions - the registry keyframes read a CSS variable Base UI never sets
collapsible.tsxThe same height transitionEvery collapsible opens and closes instantly
navigation-menu.tsxCross-slide variants written in data-[name=value] bracket form, plus a bg-popover/70 backdrop-blur-2xl popup surface whose ring fades in with the panelThose four classes compile to no CSS, so panel content stops sliding as you move between menu items, and the panel loses its glass surface and grows a heavy doubled top border while it scales open
popover.tsxcollisionPadding forwarded to the positioner, plus a bg-popover/70 backdrop-blur-2xl popup surfaceThe prop leaves the component's types and every call site passing it fails to compile, and the panel reverts to an opaque card that no longer matches the menus
select.tsxalignItemWithTrigger={false} plus p-1 on the listPopups cover their trigger instead of dropping below it, and items sit flush against the popup edge
avatar.tsxrounded-[inherit] on the image, the fallback, and the ringA square avatar renders a circular image inside a square chip, with the ring cutting across the corners

Preview any rewrite with pnpm dlx shadcn add <component> --diff, which prints the delta and writes nothing. form.tsx has no registry entry at all, so it can only ever be reconciled by hand.

On this page