Back to all articles
SaaS

The Next.js SaaS Stack I Actually Use in 2026

After shipping eight production SaaS products, here's the exact stack, file layout, and architectural decisions I make on every greenfield Next.js project — and the ones I avoid.

4 min readBy Asad Iqbal

Most "best Next.js stack 2026" articles read like a shopping list. After shipping eight production SaaS products, here's the stack and the file layout I actually reach for — and a few things I avoid.

The stack#

Frontend & framework#

  • Next.js 14+ (App Router). Server components by default. Route groups for marketing vs. authenticated app. ISR where it makes sense; dynamic rendering where it doesn't.
  • TypeScript strict. No exceptions. strict: true, noUncheckedIndexedAccess: true, exactOptionalPropertyTypes: true.
  • Tailwind CSS. I tried alternatives. None of them came close for the velocity of a one-person team.
  • Framer Motion + Lucide. For motion and icons. Small, predictable, zero-config.
  • Zod. For runtime validation at every trust boundary — API routes, form submissions, webhooks, AI responses.

Backend & data#

  • PostgreSQL. Always. It's the right default for almost every SaaS product from zero to a million users.
  • Prisma or Drizzle. I reach for Drizzle for greenfield projects in 2026 — better TypeScript inference, faster cold starts, no schema migration surprises. Prisma is fine; Drizzle is better.
  • Redis when I need queues, rate limits, sessions, or pub/sub. Upstash for serverless.
  • NextAuth (Auth.js) v5 for auth, with email + OAuth providers. Database sessions for B2B; JWT for B2C.

AI#

  • OpenAI as the default LLM. Anthropic Claude for long-context summarization and structured outputs. Ollama for local-first / regulated workloads.
  • LangChain only when the orchestration is genuinely complex. For most products, the OpenAI / Anthropic SDK + a small chat() wrapper is enough.
  • pgvector for embeddings. Add it to your Postgres from day one; the upgrade cost later is real.

Infra#

  • AWS (Amplify for Next.js, RDS for Postgres, S3 for assets, CloudFront for CDN). Amplify's Next.js support is genuinely good in 2026.
  • GitHub Actions for CI.
  • Sentry for errors. PostHog for product analytics.
  • Resend or SMTP for transactional email.

File layout that scales#

app/
  (marketing)/          ← public site (home, pricing, blog)
  (app)/                ← authenticated app (dashboard, settings)
  api/                  ← route handlers
components/
  ui/                   ← primitives (button, input, modal)
  features/             ← feature-level components (chat, billing, etc.)
  marketing/            ← landing-page sections
lib/
  auth/                 ← auth helpers
  db/                   ← DB client + queries
  ai/                   ← LLM wrappers, prompts, evals
  email/                ← transactional email templates
  config.ts             ← env-validated config
content/
  blogs/                ← markdown blog posts
types/
public/

The parentheses around (marketing) and (app) are route groups. They share a layout, organize URLs cleanly, and let you set different cache / middleware rules per group without rewriting everything.

Decisions I make on day one#

Authentication from line one#

Don't bolt auth on later. Add NextAuth on the first commit. Even if your MVP is "logged in only," you'll thank yourself.

Env validation at boot#

Use Zod to validate process.env at boot. Fail fast if a config var is missing. Saves hours of debugging in production.

// lib/config.ts
import { z } from "zod";
 
const schema = z.object({
  DATABASE_URL: z.string().url(),
  OPENAI_API_KEY: z.string().min(1),
  AUTH_SECRET: z.string().min(32),
  STRIPE_SECRET_KEY: z.string().min(1).optional(),
});
 
export const config = schema.parse(process.env);

Logging that survives#

Don't console.log in production. Use a thin wrapper that pipes to Sentry / Datadog / your log aggregator. Add request IDs. Tag with user ID when authenticated. You'll thank yourself at 2am.

Error boundaries at the route level#

Every meaningful route gets an error.tsx. Every feature gets a <ErrorBoundary> at the top. Errors should never crash the whole app.

Rate limits at the edge#

Add rate limits to every public API route on day one. Upstash + middleware makes this ~20 lines of code.

Things I avoid#

tRPC#

Lovely DX for solo work. Painful for multi-team codebases. For solo SaaS, just use plain fetch and typed route handlers — the boilerplate is fine and the surface area is smaller.

Microservices#

Don't split until you have a team of ten engineers and a clear reason. A single Next.js app with a clean lib/ directory serves most products to their first million users.

ORM-heavy abstractions#

Drizzle and Prisma both have a sweet spot. Use the query builder for complex reads. Use the model API for simple CRUD. Don't wrap everything in repositories "for cleanliness" — it just adds indirection.

Heavy CSS-in-JS#

I tried Emotion, styled-components, and Stitches. Every one of them produced measurable bundle bloat and runtime overhead. Tailwind + a few CSS variables wins.

Custom UI kits#

Use Radix UI or shadcn/ui. Don't build your own dialog or dropdown from scratch — accessibility is harder than it looks.

The boring decisions that pay off#

Use a monorepo only when you need one#

For a single SaaS, a flat Next.js repo is correct. Add a monorepo when you're shipping two products, a marketing site, and a mobile app. Not before.

Pin your Node version#

engines.node in package.json. .nvmrc in the repo. CI uses the same version as dev. Reproducible builds matter.

Test the things that break#

I don't aim for 100% coverage. I write tests for the things that have already broken — billing, auth, AI prompts, data migrations, anything that touches money or PII.

Document the things that matter#

A README.md that gets new engineers to "Hello World" in 15 minutes. A /docs page in the app for the non-obvious product flows. A CHANGELOG.md that you actually keep up to date.

How long it takes to ship#

A clean Next.js SaaS MVP — auth, billing, dashboard, one core feature, basic admin — takes me about 4 weeks of focused work. The same product on a "popular JavaScript framework" would take 8. The reason isn't the framework; it's the discipline of making good decisions early and not rewriting them.

What I'd do differently#

If I were starting today:

  • I'd skip the Redux/Zustand debate entirely and reach for TanStack Query + server components. Almost all "state" in a SaaS is server state.
  • I'd add observability from day one — Sentry + PostHog + a request log. Skipping this has cost me a week per project.
  • I'd write the boring docs first — architecture decisions, env var reference, deploy runbook. Documentation debt compounds faster than code debt.

If you're starting a Next.js SaaS and want to skip the architecture mistakes, I take on a small number of greenfield engagements each quarter.

What needs attention in your business?

Tell me about your website, application or workflow, and what is getting in the way. You don't need to choose a technical solution before we speak.

Discuss Your Website or System