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.

Let's Connect

Have a product to build?

I take on a small number of engagements each quarter. Send a brief and I'll get back within 24 hours.

Send a project brief

The more context you share, the faster I can scope a response.