Complete Guide to Modern Web Development Stack
A deep dive into the stack I use daily: Next.js 16, React Server Components, Supabase, Prisma, and Vercel — with practical examples and lessons from production.
The Modern Web Development Stack: A Practitioner's Guide
After years of building production applications, I've settled on a stack that balances developer experience, performance, and scalability. This isn't a theoretical overview — it's the exact toolchain I use for every new project, refined through dozens of deployments.
Why Stack Choice Matters
The stack you choose in the first week of a project determines your velocity for the next two years. I've learned this the hard way: migrating a mid-size SaaS from Create React App to Next.js took three months. Choosing the right foundation from day one is the highest-leverage decision you'll make.
The Core Stack
Next.js 16 — The Application Layer
Next.js has become the de facto standard for React applications, and version 16 represents a maturity milestone. The key features I rely on daily:
React Server Components (RSC) eliminate the waterfall problem. Instead of fetching data on the client after JavaScript loads, your components fetch data on the server and stream HTML:
// app/projects/page.tsx — Server Component (default)
import { prisma } from '@/lib/prisma';
export default async function ProjectsPage() {
const projects = await prisma.project.findMany({
where: { status: 'PUBLISHED' },
orderBy: { publishedAt: 'desc' },
});
return (
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{projects.map((project) => (
<ProjectCard key={project.id} project={project} />
))}
</div>
);
}
No useEffect. No loading states for initial data. No client-side JavaScript shipped for this component. The HTML arrives fully rendered.
The Proxy Pattern replaces traditional middleware in Next.js 16. I use it for Supabase session refresh, security headers, and CSP:
// proxy.ts
import { createServerClient } from '@supabase/ssr';
export function proxy(request: NextRequest) {
const response = NextResponse.next();
// Refresh Supabase session on every request
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ cookies: { getAll: () => request.cookies.getAll(), setAll: (cookies) => { /* ... */ } } }
);
await supabase.auth.getUser();
return response;
}
Supabase — Auth + Real-time + Storage
Supabase gives you Postgres, authentication, real-time subscriptions, and file storage in a single service. The killer feature is Row Level Security (RLS) — security policies defined at the database level:
-- Users can only read their own orders
CREATE POLICY "Users read own orders" ON orders
FOR SELECT USING (auth.uid() = user_id);
-- Admins can read all orders
CREATE POLICY "Admins read all orders" ON orders
FOR SELECT USING (
EXISTS (SELECT 1 FROM users WHERE id = auth.uid() AND role = 'ADMIN')
);
This means your API routes don't need authorization logic — the database enforces it. Even if you make a mistake in your application code, unauthorized data never leaves the database.
Prisma — The Type-Safe ORM
Prisma bridges the gap between your TypeScript code and your database. The key insight: your schema is your API contract:
model Project {
id String @id @default(cuid())
title Json // Multilingual { en: string, it: string }
slug String @unique
technologies String[]
status ProjectStatus @default(DRAFT)
publishedAt DateTime?
@@map("projects")
}
Every time you change this schema, Prisma generates TypeScript types, migration SQL, and validates your queries at build time. No more runtime "column not found" errors.
Vercel — Zero-Config Deployment
Vercel's integration with Next.js is seamless. Push to main, get a production deployment in under 2 minutes. But the real value is in the edge network:
- Edge Functions for low-latency API routes
- ISR (Incremental Static Regeneration) for cache + fresh data
- Web Analytics built-in, no external scripts
Architecture Principles
1. Feature-Sliced Design
I organize code by feature, not by type:
src/
├── app/ # Next.js routes
├── features/ # Business logic
│ ├── admin/
│ ├── blog/
│ └── shop/
├── shared/ # Reusable utilities
│ ├── ui/ # Design system components
│ ├── lib/ # Helpers, hooks
│ └── types/ # Shared types
└── infrastructure/ # Database, external services
2. Server-First Data Fetching
Every page starts as a Server Component. I only add "use client" when I need interactivity (forms, animations, real-time). This typically means 80% of my components are server-rendered.
3. Type Safety End-to-End
From database schema (Prisma) → API route (TypeScript) → client component (TypeScript) → form validation (Zod). Every layer is typed, and errors are caught at build time.
Performance Results
On this very portfolio site, the stack delivers:
| Metric | Score |
|---|---|
| Lighthouse Performance | 100/100 |
| Lighthouse Accessibility | 100/100 |
| Lighthouse Best Practices | 100/100 |
| Lighthouse SEO | 100/100 |
| First Contentful Paint | < 0.8s |
| Largest Contentful Paint | < 1.2s |
| Cumulative Layout Shift | 0 |
These aren't cherry-picked lab results — they're consistent production scores.
Lessons Learned
- Start with Server Components. Add client interactivity only when needed. The mental model shift is worth it.
- Use Prisma for schema management, Supabase for auth/real-time. They complement each other perfectly.
- Deploy to Vercel from day one. The preview deployments for PRs alone save hours of QA time.
- Invest in your design system early. Shadcn/ui + Tailwind CSS gives you a professional look without custom CSS.
Conclusion
The modern web stack isn't about choosing the trendiest framework. It's about selecting tools that compound in value over time. Next.js, Supabase, Prisma, and Vercel form a cohesive ecosystem where each piece enhances the others. After two years of using this stack in production, I'm more productive than ever — and my applications are faster, more secure, and easier to maintain.