Developers love talking about their stack. I'm no exception — but I want to be practical here. Every choice I've made has a reason, usually learned the hard way. This is not aspirational. It's what I actually ship with.
The Foundation
Next.js 15 (App Router)
I moved fully to the App Router in 2024 and haven't looked back. Server Components have genuinely changed how I think about data fetching — the mental model of "this component runs on the server, this one on the client" is powerful once it clicks.
What I specifically love:
- Nested layouts — shared UI that persists across routes without re-rendering
generateStaticParams— painless static generation for dynamic routesserver actions— form handling without building an API endpoint
The turbopack dev server (now stable in Next.js 15) is also noticeably faster. Cold starts that used to take 4-5 seconds are down to under a second.
TypeScript (strict mode)
I used to write TypeScript loosely — adding any whenever the types got complex. Strict mode forced me to be precise, and the resulting code is dramatically more maintainable.
// Before: lazy typing
const fetchPost = async (slug: any) => {
const data: any = await fetch(`/api/posts/${slug}`);
return data;
};
// After: strict, explicit, self-documenting
interface BlogPost {
slug: string;
title: string;
publishedAt: Date;
content: string;
}
const fetchPost = async (slug: string): Promise<BlogPost> => {
const res = await fetch(`/api/posts/${slug}`);
if (!res.ok) throw new Error(`Post not found: ${slug}`);
return res.json() as Promise<BlogPost>;
};
The second version is slightly more code. It also prevents an entire class of runtime bugs.
Tailwind CSS
The argument against Tailwind — "it clutters your markup" — is valid. The argument for it: I ship faster, I don't context-switch between files, and the design system is baked in.
I pair Tailwind with:
clsx+tailwind-mergefor conditional class logic- A design tokens file for custom colours, spacing, and typography scales that Figma matches exactly
Animation
Framer Motion
For complex UI animations — page transitions, gesture-based interactions, staggered list entrances — Framer Motion is still the best tool in the React ecosystem.
<motion.div
initial={{ opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
>
{children}
</motion.div>
One thing I've learned: cubic-bezier(0.22, 1, 0.36, 1) is the Apple easing curve. It makes everything feel premium. I use it for almost every motion on this site.
CSS Transitions for Micro-interactions
Not everything needs Framer Motion. Hover states, focus rings, colour changes — these are better handled with CSS transitions. It's lighter and avoids the JavaScript overhead.
.button {
transition: all 300ms cubic-bezier(0.22, 1, 0.36, 1);
}
State Management
In 2026, I use:
- React
useState/useReducer— for local component state - React Context — for shared state within a subtree (theme, auth user)
- Server state via
fetch+ React cache — for remote data in Server Components
I no longer use Redux. For the kinds of apps I build, it's overkill. The new React model — with Server Components managing data and Client Components managing interactivity — removes most of the problems Redux was solving.
The one exception: if I'm building something with complex optimistic UI (like a kanban board with drag-and-drop), I'll reach for Zustand. It's lightweight and predictable.
Forms
React Hook Form + Zod
This combination is excellent. Zod gives you runtime validation with TypeScript inference — you define your schema once and get both the form validation logic and the TypeScript types for free.
import { z } from 'zod';
const contactSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Please enter a valid email'),
message: z.string().min(10, 'Message must be at least 10 characters'),
});
type ContactForm = z.infer<typeof contactSchema>; // type derived automatically
Tooling
VS Code + these extensions
- ESLint + Prettier — non-negotiable
- Tailwind CSS IntelliSense — autocomplete for class names
- Error Lens — shows errors inline, no need to hover
- GitLens — makes
git blamea first-class citizen
Git workflow
- Feature branches, always
- Conventional commits (
feat:,fix:,chore:) - PR descriptions that explain why, not what
Deployment
Vercel for frontend. Zero-config Next.js deployment, preview environments on every PR, and edge functions that actually work.
For databases: Supabase (Postgres + auth + realtime in one). For file storage: Supabase Storage or Cloudinary for image-heavy projects.
What I Deliberately Don't Use
- Redux — solved by React's own primitives + Zustand for edge cases
- GraphQL — REST is simpler for most projects; GraphQL's benefits show only at massive scale
- Styled Components / Emotion — Tailwind + CSS modules cover my needs
- Create React App — it's archived. Next.js or Vite.
Final Thought
The best stack is the one you know deeply. I've seen projects fail not because of wrong technology choices, but because the team didn't understand the tools they were using.
Pick a stack. Go deep. Master the fundamentals. The framework you choose matters far less than how well you use it.
What's your frontend stack in 2026? Always curious what other developers are shipping with.