Building Modern Web Applications — The Stack That Actually Ships
The 2026 web stack is not 'pick a meta-framework and go.' It is a small set of unglamorous architectural decisions that determine whether your app is still alive in 2028. Here is the stack I ship every project on, and the seven decisions behind it.

There is a 2024 version of this article. I wrote it. It is bad. It told you to pick Next.js and integrate AI, with a code snippet using text-davinci-003 (an OpenAI model that has been deprecated for over a year). Reading it back is humbling.
This is the rewrite. Not because the framework changed — Next.js is still the right default for most React projects in 2026 — but because the architectural decisions underneath the framework choice matter more than the framework choice itself. This is the stack I actually ship every project on, and the seven decisions behind it.
The 2026 web stack is unglamorous and that is the point
The defining characteristic of the 2026 web stack is that the interesting decisions are not in the framework. The framework is mostly settled — Next.js, Astro, Remix, SvelteKit, or a few others depending on your use case. The decisions that determine whether your application is still healthy in 2028 are about:
- What you depend on (Postgres? Supabase? Firebase?)
- Who owns your data (your DB or someone else's SaaS)
- What is server-rendered vs. client-rendered
- Where AI integration lives (and where it does not)
- How you handle auth, payments, and transactional state
These are boring questions. Almost nobody markets around them. They are 90% of the engineering work.
The web stack is no longer a framework choice. It is a series of architectural choices that the framework only loosely constrains.

The stack I ship every project on (April 2026)
For ~85% of projects I scope today, this is the stack. Substitutions exist for specific needs (Astro for content-only sites, FastAPI for Python-shop backends), but the defaults are unchanged for the last 18 months:
| Layer | Choice | Why |
|---|---|---|
| Framework | Next.js 16 (App Router) | Best React server-side story, mature deployment story |
| Language | TypeScript (strict) | Type safety is non-negotiable above one developer |
| Styling | Tailwind CSS v4 | Utility-first works, but kept to layout — branding is in @layer components |
| UI primitives | Radix headless + custom CSS | I avoid pre-styled UI kits; brand needs are too specific |
| Database | PostgreSQL (Neon for serverless) | Boring, fast, supported everywhere |
| ORM | Drizzle | Type-safe queries, no magic, transparent SQL |
| Auth | iron-session for simple sites; Auth.js for multi-provider | Skip the auth-as-a-service layer unless you need social login at launch |
| Payments | Stripe | Period. The fintech complexity is the moat. |
| Nodemailer + SMTP (Websupport/Postmark) | Avoid Resend/SendGrid lock-in on transactional | |
| Hosting | Vercel (preferred) or Fly.io | Choose based on whether you need WebSocket or workers |
| Object storage | Cloudflare R2 (S3 API) | 10× cheaper than S3 with no egress fees |
| AI layer | Anthropic Claude API for production, fallback to Gemini | See § AI integration below |
That is the whole stack. If a project does not fit this, it is usually for a specific reason I can name (regulatory, real-time, niche language requirements). Default to boring. Optimise only where the project genuinely needs it.

The seven decisions that actually matter
1 · Server-first rendering by default
In 2026, marking every component 'use client' is a sign you do not understand the platform. Server Components are the default for a reason. They:
- Reduce JavaScript bundle size (the only thing that consistently matters for page-load performance)
- Centralize data access in one tier (the server) where auth, caching, and rate-limiting actually work
- Let you use database queries inside components without a separate API route layer
Client components are still necessary for interactivity (forms, state, animations, browser APIs). The discipline is to make them small and leaf-level — not to wrap the whole page in 'use client' because one button needs onClick.
2 · One database, one ORM, no abstractions on top
A surprisingly common 2024 pattern was: pick a database, then put a GraphQL layer on top, then put a SaaS BaaS layer on top, then add a feature-flag service on top, then realise half your stack is rented. The simpler 2026 pattern is: Postgres + Drizzle, with the application code talking to the database directly through type-safe queries.
This is not a religious position. GraphQL exists because someone built it for a real problem (mobile apps consuming inflexible REST APIs). For a web app where the frontend and backend live in the same codebase, GraphQL adds infrastructure without solving a problem you have.
3 · Tailwind for layout, CSS for branding
Tailwind utility classes are excellent for layout, spacing, and basic typography. They are bad for components with brand-specific styling. The compromise is the split I use in every project:
- Tailwind utilities in JSX for layout:
flex,grid,gap-*,px-*, breakpoints - Plain CSS (Modules or
@layer components) for brand styling: anything with hover states, transitions, brand colours, or component-internal logic
Without this split you end up with 12 Tailwind classes per element, your JSX becomes unreadable, and your design system effectively lives in the markup. The split keeps the code clean and the design system intentional.
4 · Auth is a build-it problem, not a buy-it problem
For a single-user admin or a 10-user team, iron-session is 30 lines of code and good enough forever. For multi-provider OAuth, Auth.js (formerly NextAuth) is mature and well-supported. For enterprise SSO, Clerk or Auth0 is appropriate.
What is rarely appropriate is buying auth-as-a-service for a small product that just needs email/password. You will pay $50–$500/month for a feature that took an afternoon to build. The vendor will change their pricing in year two.
5 · Drizzle for the ORM, not Prisma
This is the most controversial item in the list, so I will be direct. I shipped Prisma for three years and stopped 18 months ago for two reasons:
- Build performance. Prisma's code generation is slow and gets slower as your schema grows. On a project with 60+ tables, the dev loop became painful.
- Query opacity. Prisma generates SQL you cannot fully see or control. For 80% of queries this is fine. For the 20% where performance matters, you are debugging a black box.
Drizzle solves both. It is essentially a typed SQL builder. The queries you write look like SQL because they are SQL with a thin TypeScript layer. The migrations are explicit. The performance is transparent. Switch happened in 18 months across my client work and I have not looked back.
6 · AI integration where it moves a measurable metric
This is where most teams in 2026 are wasting money. Marketing pressure pushes companies to add "AI features" to everything. The result is decorative AI that does not improve any user-facing metric.
The right pattern is to identify one or two functions where AI actually moves a measurable metric:
- Search ranking (relevance scores improve with semantic embedding)
- Content classification (auto-tagging support tickets, routing)
- Draft generation (email replies, product descriptions, summaries)
- Anomaly detection (suspicious orders, fraud signals)
Then implement those carefully, with a fallback path when the AI is wrong, and instrumentation that shows the metric moving. Skip "add AI to everything" — it is the 2026 equivalent of "add blockchain" from 2018.
Need help scoping where AI actually belongs in your product? That is a discovery sprint conversation.

7 · Boring infrastructure for everything that ships money
This is a hard-earned lesson. For anything that touches money, identity, or compliance, use boring infrastructure with long track records.
- Payments: Stripe. Period. Their fintech complexity is what you are buying.
- Auth tokens: iron-session or Auth.js. Both are battle-tested.
- Email: SMTP via a deliverability-focused provider (Postmark, SES). Avoid trendy "modern email" SaaS for transactional unless you have a specific reason.
- Database: Postgres. Not because it is the best, but because it has 25 years of bug fixes and every senior engineer in the world has used it.
The trendy alternative is always 30% faster and 80% less proven. For a side project, fine. For your business, no.
Case study — small e-commerce on this stack
Concrete example. A small e-commerce platform I shipped last quarter, illustrating the choices in production.
The brief: Slovak family-run sporting goods retailer, ~120 SKUs, expanding from physical store to online. Budget €20,000 for the build, 12 weeks. Required: Slovak/English bilingual, custom product configurator for skis (length + binding + boots combinations), Stripe payments, Slovak invoicing integration (Pohoda/Money S3).
The stack chosen:
- Next.js 16 App Router for the storefront
- Drizzle + Postgres (Neon) for product catalog and orders
- Tailwind for layout + custom CSS for the ski configurator UI (interactive, needed proper state and animation)
- Stripe Connect for payments, integrated with the Slovak invoicing API
- Cloudflare R2 for product images
- Nodemailer + Postmark for order confirmations
- Lightweight semantic search using OpenAI embeddings for product search (the one piece of AI in the build)
The results after 90 days:
- 99.97% uptime measured by external monitor
- p95 page load 0.9 seconds (Core Web Vitals all green)
- Search-to-purchase conversion 5.2% (industry average for sporting goods sits at 2–3%)
- €0 in unplanned infrastructure costs — total hosting bill ran €34/month on Vercel + Neon free tier through launch volume
The unsexy answer to "what enabled the result" is: no special tricks. Boring stack chosen carefully, executed well, instrumented enough to know what works.
Takeaways — your 2026 web stack checklist
- Default to Next.js 16 + Server Components. Mark
'use client'only when you must. - Postgres + Drizzle. Skip the ORM that ships its own runtime.
- Tailwind for layout, plain CSS for branding. Stop putting design tokens in className strings.
- Buy auth only if you need OAuth or enterprise SSO. Otherwise iron-session is enough.
- Stripe for payments. Postmark or SMTP for email. R2 for storage. Boring is the point.
- AI where it moves a metric you can measure. Not as decoration.
- One major version behind the bleeding edge. Stability over novelty.
The 2026 web stack is not a framework. It is a sequence of architectural decisions that respect both engineering reality and business reality. Pick boring infrastructure, ship the actual work, instrument to know what works.
Related: Custom Web Application Development Cost in 2026 · Owning Your Stack in 2026 · How I Run Discovery Sprints