Yashveer Singh
Connect
<- All posts
Web App and Frontend Development12 min read

The Modern Web App Stack: A 2026 Survey

The modern web app stack in 2026 is a set of technologies that are stable, well-maintained, and productively used by individual developers and small teams to build production web applications. The landscape has consolidated significantly from five years ago: TypeScript is the default language, React with Next.js is the dominant framework for full-stack applications, PostgreSQL is the default database, and Vercel or similar edge deployment platforms handle infrastructure. The remaining choices -- ORM, authentication, payment processing, deployment -- have clear defaults that work well for most cases.

Written by Yashveer Singh, founder of Yashveer Labs.

What you actually need to know

  • The 2026 web stack is more consolidated than it was three years ago. The majority of production web applications use a recognizable set of tools with clear defaults.
  • TypeScript is the default language. Starting a new JavaScript project without TypeScript requires a specific reason to opt out.
  • Next.js App Router is stable and production-ready. The early instability (2023-2024) is resolved; the App Router is the default for new projects.
  • Drizzle ORM is the new default for SQL in TypeScript projects, displacing Prisma as the primary recommendation.
  • The full-stack stack for most new SaaS products: Next.js 15 + TypeScript + Drizzle + PostgreSQL (Neon or Supabase) + Clerk (auth) + Stripe (payments) + Vercel (deployment).
Layer2026 DefaultAlternativeWhen to Switch
FrameworkNext.js 15 (App Router)Remix, AstroRemix: web-standards preference; Astro: content-first
LanguageTypeScriptJavaScriptOnly for throwaway scripts
ORMDrizzlePrismaPrisma: if team knows it well
DatabasePostgreSQL (Neon / Supabase)MySQL, SQLiteMySQL: legacy, SQLite: local dev only
AuthClerk / Auth.jsSupabase AuthSupabase: if using Supabase stack
PaymentsStripePaddlePaddle: if global tax handling is needed
DeploymentVercelRailway, Fly.ioRailway: need managed DB alongside
StylingTailwind CSSCSS ModulesCSS Modules: if Tailwind feels too opinionated
State managementTanStack QueryZustandZustand: global client state beyond server cache

The core argument

The modern web app stack in 2026 is not exciting to write about. The frameworks are mature, the tools are stable, and the choices are well-settled for most use cases. This is a good thing. The ecosystem churn that characterized 2018-2022 -- new frameworks monthly, incompatible major versions, tooling fragmentation -- has resolved into a relatively stable set of tools that teams can learn and productively use without constant re-evaluation.

The consequence is that the choice of stack is less important than the quality of implementation. A well-implemented Remix application is better than a poorly-implemented Next.js application. A team that knows Prisma deeply ships faster with Prisma than a team that switches to Drizzle and spends two weeks learning it. The stack advice is useful for teams starting fresh; for teams with existing expertise, staying with what the team knows is usually the right call.

The one area where the stack choice still matters significantly: the ORM and database layer. The difference between a type-safe ORM that generates good SQL and a runtime-typed ORM that generates N+1 queries is visible in application performance and developer experience. This is where Drizzle's advantages over Prisma are most pronounced, and where the switch is worth considering for new projects.

Next.js 15 with the App Router

The App Router, introduced in Next.js 13 and stabilized through versions 14 and 15, changes the fundamental model of Next.js from page-level to component-level server rendering. The key concepts:

Server components render on the server and send HTML to the client. They can fetch data directly without an API layer, access server-side resources (databases, file systems), and produce no client-side JavaScript. Most components should be server components by default.

Client components render on the client and have access to browser APIs, event handlers, and React hooks. Mark a component as a client component with 'use client' at the top of the file.

The practical model for a SaaS product: `` app/ layout.tsx -- Server component: wraps every page page.tsx -- Server component: main page with data fetching components/ data-table.tsx -- Server component: renders data search-input.tsx -- Client component: needs useState, onChange modal.tsx -- Client component: needs state for open/close ``

Server components fetch data without loading states, waterfalls, or client-side data fetching boilerplate:

```tsx // app/dashboard/page.tsx -- Server component async function DashboardPage() { // Direct database access -- no API route needed const user = await getCurrentUser(); const projects = await db.select().from(projectsTable) .where(eq(projectsTable.userId, user.id));

return <ProjectList projects={projects} />; } ```

Drizzle ORM

Drizzle's syntax is intentionally SQL-like, making it predictable for developers familiar with SQL:

```typescript // schema.ts import { pgTable, text, uuid, timestamp, pgEnum } from 'drizzle-orm/pg-core';

export const projectStatusEnum = pgEnum('project_status', ['active', 'archived', 'deleted']);

export const projects = pgTable('projects', { id: uuid('id').primaryKey().defaultRandom(), name: text('name').notNull(), userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), status: projectStatusEnum('status').default('active').notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), });

// Type inference from schema export type Project = typeof projects.$inferSelect; export type NewProject = typeof projects.$inferInsert; ```

Queries: ```typescript // Simple select const userProjects = await db .select() .from(projects) .where(and( eq(projects.userId, userId), eq(projects.status, 'active') )) .orderBy(desc(projects.createdAt)) .limit(20);

// Join const projectsWithOwners = await db .select({ project: projects, ownerName: users.name, }) .from(projects) .innerJoin(users, eq(projects.userId, users.id)); ```

The TypeScript types are inferred from the schema. The return type of the select query above is correctly typed without any manual type annotation.

The authentication decision

Auth.js (NextAuth.js v5) and Clerk represent different positions on the build vs. buy spectrum for authentication:

Auth.js is a library -- you integrate it into your Next.js application, configure the providers, and style the auth UI yourself. It handles the OAuth flows and session management; you own the UI and the user database schema. More work, more control.

Clerk is a service -- it provides hosted sign-in and sign-up pages, a user management dashboard, and React components for authentication state. Less work, less control, monthly cost ($25+/month beyond the free tier).

For most SaaS products at early stage (under 1,000 users), Clerk's free tier and faster setup make it the pragmatic choice. The Clerk-managed sign-in page, user profile management, and organization support eliminate weeks of auth UI work. For products where auth UI must match brand exactly, or where Clerk's cost at scale is prohibitive, Auth.js is the alternative.

Database hosting

PostgreSQL remains the database of choice for production web applications. The hosting options:

Neon -- serverless PostgreSQL with connection pooling and branching (useful for preview deployments). Free tier: 3GB storage, compute scales to zero. Good pairing with Vercel.

Supabase -- PostgreSQL with auth, realtime, and storage included. Free tier: 500MB database, 50K MAU. Good for projects that want auth and database in one provider.

Railway -- managed PostgreSQL alongside the application deployment. Simplest pairing when using Railway for deployment.

PlanetScale -- MySQL-compatible serverless database. Relevant for teams that specifically need MySQL compatibility; for new projects starting with PostgreSQL, there is no reason to consider it.

Common mistakes teams make with the 2026 stack

  1. Using React Context for server-fetched data instead of TanStack Query. Context for server-fetched data produces unnecessary re-renders and no automatic cache management. TanStack Query provides stale-while-revalidate caching, background refetching, and automatic request deduplication.
  2. Not using TypeScript strict mode. TypeScript with strict: false misses most of the type errors that TypeScript is valuable for catching. Enable strict mode from the start of the project.
  3. Over-engineering the state management from day one. Most SaaS products do not need Redux or Zustand for global state -- TanStack Query for server state and React's useState/useReducer for local state are sufficient for most use cases at startup scale.
  4. Not using Next.js's built-in image optimization. next/image provides automatic WebP/AVIF conversion, size optimization, and lazy loading. Using raw <img> tags misses these optimizations.
  5. Treating the App Router as the same as the Pages Router. Server components, the fetch API with cache configuration, and the new rendering lifecycle are meaningfully different from the Pages Router model. Read the App Router documentation rather than applying Pages Router patterns.

Where to start: a 3-step modern stack setup

Step 1: Initialize a Next.js 15 project with TypeScript, Tailwind, and ESLint using the official starter. npx create-next-app@latest with the TypeScript option produces a working project with the correct configuration. Add Drizzle and the PostgreSQL client.

Step 2: Set up the database schema and connect to a Neon or Supabase database. Define the core entities (users, projects, or whatever the application's data model requires), run the first migration, and verify the connection works.

Step 3: Choose and configure authentication before writing any application features. Auth must be in place before any feature can be built with user context. Clerk's Next.js integration takes 30 minutes; Auth.js configuration for OAuth providers takes 2-3 hours. Either is faster than building auth later and retrofitting it into features built without it.

The Stack That Ships

Yashveer Singh. Founder of Yashveer Labs. The stack I use for new client projects in 2026 is Next.js 15 with TypeScript, Drizzle ORM against Supabase PostgreSQL, Clerk for authentication, Stripe for payments, React Email + Resend for transactional email, PostHog for product analytics, and Vercel for deployment. This is the stack I used for Expert Tutorials, for three client builds over the past year, and for my own tooling. It is not the most interesting stack to describe -- the tools are mature and the choices are well-settled -- but it is the stack that ships quickly, runs reliably, and does not surprise the team with framework instability or ecosystem fragmentation. Boring technology is not a failure of ambition; it is the precondition for moving fast on the product.

Related reading

FAQ

Frequently asked

Author

The reason I write these

I write these because the writing is the proof. Yashveer Singh, founder of Yashveer Labs. The systems I build are not theoretical. They are running right now, serving real users, generating real revenue. That is the bar I hold this writing to. If you want to hire someone who can match that bar, I am the call.

Related reading