Modern Full Stack: what to learn and what to ignore

The Premise

The full-stack landscape in 2026 is noisy. New frameworks arrive monthly. Vendors market every incremental change as a paradigm shift. The result: engineers waste months learning tools that don't survive contact with production.

This guide cuts through it. The criterion for everything here is simple: does it reduce complexity, increase leverage, or handle a real production problem? If not, it's noise.

Scope: This targets engineers building product — web apps, APIs, internal tools, SaaS. Not systems programming, embedded, or ML infrastructure. The recommendations here optimize for shipping reliable software without a large platform team.

The guide is opinionated. Where there's genuine choice, it says so. Where one option is clearly better for most use cases, it says that too.

Frontend

The Framework Question

React remains the dominant choice — not because it's technically superior but because the ecosystem, hiring pool, and tooling around it are unmatched. Next.js is the practical default for most projects: file-based routing, server components, API routes, and edge-compatible rendering in one package.

Svelte/SvelteKit is a legitimate alternative if you're starting fresh and don't need React ecosystem compatibility. Smaller bundle, simpler mental model, genuinely pleasant to write. Solid has better performance characteristics than React but narrow ecosystem.

Learn
  • React + Next.js (App Router)
  • SvelteKit (if greenfield)
  • React Server Components
  • Suspense + streaming SSR
  • Tailwind CSS
  • Zod for schema validation
  • TanStack Query (client data)
Ignore
  • Angular (unless org mandate)
  • Next.js Pages Router (new projects)
  • CSS-in-JS at runtime
  • Redux (for anything < large)
  • Class components
  • Styled-components / Emotion
  • Webpack config from scratch

CSS

Tailwind CSS has won the utility-first debate. It has drawbacks — verbose JSX, non-semantic class names — but the productivity gain and constraint system are net positive for most teams. Use it with a component library (shadcn/ui is the current best: copy-paste components you own, built on Radix primitives) or build your own design tokens on top.

CSS Modules are still valid for scoped styles without utility bloat. Vanilla CSS with custom properties is underrated for simpler projects.

Skip runtime CSS-in-JS. Emotion and styled-components add measurable hydration cost and complicate React Server Components. The performance argument alone is sufficient. If you want CSS-in-JS semantics, use vanilla-extract (build-time) instead.

State Management

Most applications don't need a global state library. Server state (data from APIs) belongs in TanStack Query or a similar cache layer. Local UI state belongs in useState / useReducer. The two cover 90% of cases.

If you genuinely need cross-component client state: Zustand (minimal, composable) or Jotai (atomic, bottom-up). Avoid Redux unless you're maintaining a codebase that already uses it — the indirection cost is real.

Build Tooling

Vite for anything not already on Next.js. Fast cold starts, HMR, native ESM. Avoid configuring Webpack from scratch; it's a time sink with no proportional gain. Turbopack (Next.js) is maturing and will likely be default soon.

Backend & APIs

Runtime Choice

Node.js with TypeScript is the pragmatic default for API work. The ecosystem is unmatched. Bun is faster and worth attention for new projects — it's now production-stable and a drop-in replacement for most Node workflows.

Go is worth learning if you build high-throughput services or CLI tools — the performance ceiling is much higher and the deployment story (single binary) is excellent. For CPU-bound work or services needing low latency, Go or Rust outperform JS runtimes decisively.

Python remains dominant in data pipelines and ML-adjacent backends. Don't use it as your primary API layer unless the project is already Python-heavy or interacts heavily with ML tooling.

API Design

Default to REST for external-facing APIs. Well-understood, widely supported, easy to document with OpenAPI. Use tRPC for internal full-stack TypeScript apps — end-to-end type safety with zero schema duplication is a genuine productivity multiplier.

GraphQL is appropriate when clients need flexible query shapes over heterogeneous data — typically in large product surfaces. For most applications it's over-engineered: resolver complexity, N+1 problems, and caching friction outweigh the flexibility.

Learn
  • TypeScript (non-optional)
  • REST + OpenAPI/Swagger
  • tRPC (TS monorepos)
  • Hono (lightweight HTTP)
  • Go (for perf-critical services)
  • Bun as Node replacement
  • Middleware patterns & request lifecycle
Ignore
  • GraphQL (unless genuinely needed)
  • REST with no typing/validation
  • Express.js (new projects)
  • SOAP / XML APIs
  • Microservices before product-market fit
  • Java Spring (unless org mandate)
  • gRPC (unless internal service mesh)

Frameworks

Hono for lightweight HTTP in TypeScript — runs on Node, Bun, edge runtimes, and Cloudflare Workers with the same API surface. Fastify if you need a mature Node framework with plugins. Avoid Express for new projects — it's minimally maintained and lacks TypeScript ergonomics without significant configuration overhead.

For full-stack TypeScript, Next.js API routes or Route Handlers are often sufficient to avoid a separate backend service entirely.

Databases & Data Layer

Primary Database

PostgreSQL is the answer for 95% of applications. Mature, reliable, ACID-compliant, with JSON support that covers semi-structured data adequately. Don't reach for MongoDB because your data "looks like JSON" — Postgres handles it and gives you relational integrity.

Use SQLite for small deployments, local dev environments, and single-node apps. Turso makes SQLite production-viable with edge replication if your read patterns fit.

Don't pick MongoDB by default. Schemaless flexibility sounds appealing early and becomes a liability as the application grows. If you genuinely need document storage at scale with distributed writes, it's justified. Otherwise, Postgres with JSONB handles the use case.

ORM / Query Layer

Drizzle ORM is currently the best TypeScript ORM: SQL-like syntax, excellent type inference, lightweight, and doesn't hide the query from you. Prisma is more ergonomic for rapid development but has performance overhead at scale and a heavier migration story.

Raw SQL via postgres.js or Kysely (type-safe query builder) is appropriate when you need precise control or Drizzle's abstractions don't fit the query shape.

Caching

Redis for session storage, rate limiting, job queues, and hot-path caches. Upstash if you want serverless Redis without managing infrastructure. Don't implement an in-memory cache in your application process for anything that runs on multiple instances — it won't be consistent.

Full-Text Search

Postgres tsvector / tsquery handles most search needs adequately. Only reach for Elasticsearch or Typesense when you need faceted search, complex relevance tuning, or search at meaningful scale. Typesense is simpler to operate than Elasticsearch for most product use cases.

Auth

Auth is the most misimplemented part of most applications. The default recommendation: don't build it yourself.

Managed Auth

Clerk — best developer experience, handles sessions, MFA, social login, and organizations. Costs money at scale but the implementation time saved is real. Auth0 and WorkOS are the enterprise alternatives (SSO, SAML, directory sync).

Self-Hosted Auth

Auth.js (formerly NextAuth) for Next.js projects. Handles OAuth providers, JWT/session management, adapter pattern for multiple databases. Lucia if you want more control with less magic — it's a library, not a framework, and makes the session logic explicit.

Never roll your own password hashing, session token generation, or CSRF protection. Use established libraries (bcrypt, argon2 for passwords; cryptographically secure random for tokens). The failure modes are silent and catastrophic.

Patterns to Know

Understand the difference between authentication and authorization. Know how JWTs work and when they're appropriate (stateless, short-lived access tokens) vs. when session cookies are better (revocable, server-controlled). Understand OAuth 2.0 flows — authorization code + PKCE for public clients, client credentials for machine-to-machine.

Infrastructure & Deployment

Hosting Tier

Start on managed platforms. Vercel or Netlify for frontend/full-stack. Fly.io or Railway for containerized backends. Render as a simpler Heroku alternative. These eliminate ops overhead at early stages — the productivity gain outweighs the cost premium.

Move to AWS, GCP, or Azure when you have specific compliance requirements, need custom networking, or platform costs become significant. Don't do it prematurely; the operational burden is substantial.

Containers

Learn Docker. Writing a Dockerfile, building images, and understanding layer caching is table-stakes. You don't need to operate Kubernetes unless you're running more than a handful of services — managed container platforms (Fly, Railway, Cloud Run) abstract it.

If you do reach Kubernetes: learn it properly, including networking, probes, and resource requests. Half-understood Kubernetes is more dangerous than no Kubernetes.

CI/CD

GitHub Actions is the default. It's adequate for most pipelines: test, lint, build, deploy. Know how to write a workflow file, use matrix builds for parallel test runs, and manage secrets. Dagger is worth watching for complex build pipelines — portable, code-defined CI.

Learn
  • Docker + multi-stage builds
  • GitHub Actions
  • Fly.io / Railway / Render
  • Managed Postgres (Neon, Supabase)
  • Edge runtimes (Cloudflare Workers)
  • Environment variable management
  • Basic AWS (S3, IAM, Lambda)
Ignore
  • Kubernetes (until you need it)
  • Terraform (premature for small teams)
  • Self-managed databases in prod
  • Jenkins / CircleCI (default to Actions)
  • Heroku (cost/value poor post-2022)
  • Multi-cloud from day one
  • Custom ingress controllers

Observability

Ship with logging from day one. Structured JSON logs to stdout, captured by your platform. Add error tracking (Sentry) before launch — unhandled exceptions in production are invisible without it. Add metrics and distributed tracing (OpenTelemetry) when you have enough throughput to make performance debugging necessary.

Tooling & DX

TypeScript

Not optional. Enable strict: true. The upfront cost of type discipline pays off within weeks on any project that runs more than a few months. Use zod for runtime validation at trust boundaries (API inputs, environment variables, form data) and infer static types from schemas.

Linting & Formatting

ESLint with TypeScript rules. Prettier for formatting — commit to it, add format-on-save, stop discussing style in code review. Biome is emerging as a single-tool replacement for both; faster and opinionated. Worth evaluating for new projects.

Testing

Write tests. The framework matters less than the habit. Vitest for unit/integration (Vite-native, Jest-compatible API, fast). Playwright for end-to-end — it's the current best: reliable, cross-browser, good debugging tools.

Test strategy: Unit test pure functions and complex logic. Integration test API routes and data access. E2E test critical user paths (auth, checkout, core workflows). Don't write E2E tests for everything — they're slow and brittle at scale.

Package Management

pnpm is the default choice: faster installs, disk-efficient, strict module resolution that catches phantom dependencies. Bun as an alternative if you're already using it as runtime. npm is fine; avoid yarn for new projects (pnpm supersedes it).

Version Control Hygiene

Conventional commits. Meaningful PR descriptions. Feature branches. Not glamorous, but the operational cost of poor git hygiene compounds over time — reverting bugs, bisecting regressions, understanding history.

Meta-Skills

These don't fit a specific tier but matter more than any particular tool choice.

01 — Read error messages

Most debugging time is wasted not reading the actual error. Stack traces, response bodies, compiler output — read them before reaching for documentation or asking for help.

02 — Understand HTTP

Status codes, headers, the request/response cycle, caching semantics (Cache-Control, ETags), CORS. Frameworks abstract this, but when something breaks at the network layer, you need the underlying model.

03 — SQL fundamentals

Write raw queries before leaning on ORMs. Understand joins, indexes, and query plans (EXPLAIN ANALYZE). ORM-generated queries are often inefficient; you can't fix what you don't understand.

04 — Git beyond add/commit/push

Interactive rebase, cherry-pick, reflog, bisect. These are practical tools, not advanced topics. They recover situations that otherwise cost hours.

05 — Security defaults

Parameterized queries (prevent SQL injection). Output encoding (prevent XSS). HTTPS everywhere. Least-privilege IAM. Content Security Policy. These are not advanced topics — they're baseline.

06 — Know when to stop

The most expensive architectural decision is the over-engineered one. Distributed systems, event sourcing, CQRS, and microservices are solutions to specific scale problems. Applying them at early stage adds complexity with no benefit.

Recommended Starter Stack

For a new product project with a small team, this covers everything without unnecessary complexity:

Layer Choice Notes
Frontend Next.js (App Router) must SSR, routing, API routes in one
Styling Tailwind + shadcn/ui must Components you own, no runtime overhead
Language TypeScript strict must Not negotiable
API layer tRPC or Next.js routes must tRPC for internal TS; REST for external
Database PostgreSQL (Neon/Supabase) must Managed; don't self-host early
ORM Drizzle ORM must Type-safe, SQL-close, lightweight
Auth Clerk or Auth.js situational Clerk if budget allows; Auth.js if self-hosted
Validation Zod must Runtime + static type inference
Cache / Queue Upstash Redis situational Serverless Redis; add when needed
Testing Vitest + Playwright must Unit + E2E; ship with tests from the start
Hosting Vercel + Fly.io must Frontend on Vercel; backend services on Fly
CI/CD GitHub Actions must Lint → test → deploy pipeline
Error tracking Sentry must Before first user
Package manager pnpm must Faster, stricter than npm

This stack handles the typical 0→production trajectory without requiring a dedicated DevOps role. It scales to meaningful user counts before needing architectural changes. Swap components as requirements dictate — the choices above are defaults, not mandates.