Web Dev in 2025 Is Weird
I've been doing this for over a decade. The landscape keeps changing. Sometimes I feel like a grumpy old man yelling at clouds. But I think there are some genuinely weird things happening right now.
React won but also didn't?
React is everywhere. Most job postings want React experience. Most new projects reach for React by default. In that sense, it won.
But people are also increasingly annoyed by it. The complexity has grown over the years - hooks, context, suspense, server components. Every version adds new concepts to learn. The simple component model that made React attractive in 2015 has become... not simple.
I remember when React first clicked for me. Components were just functions that returned JSX. State was setState. That was basically it. Now? You need to understand:
- When to use
useStatevsuseReducervsuseRef - How closures work in hooks (or you'll get stale data)
- Why
useEffectruns twice in development - What server components are and when you can't use
useState - The difference between client and server boundaries
- Why your context provider is causing everything to re-render
Don't get me wrong - these features solve real problems. But the learning curve has gotten steep. I've seen junior devs struggle for hours with useEffect dependency arrays when the old componentDidMount would have been obvious.
Here's a real example that bites people constantly:
// This looks fine but has a bug
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1); // Always uses the initial count value!
}, 1000);
return () => clearInterval(interval);
}, []); // Missing dependency makes this broken
return <div>{count}</div>;
}
// The fix requires understanding how closures work
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1); // Functional update works correctly
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>{count}</div>;
}
Meanwhile, alternatives are gaining steam:
-
htmx is having a moment. The idea of 'just return HTML from the server' feels refreshingly simple after years of JSON APIs and client-side rendering. You write backend code that returns HTML fragments, and htmx swaps them into the page. No build step, no state management, just... HTML. Wild that this feels revolutionary in 2025.
-
Svelte and SolidJS compile away the framework, which is clever. Your components turn into vanilla JavaScript at build time. Less runtime overhead, better performance, simpler mental model.
-
Server-side rendering is cool again. Next.js, Remix, Astro - everyone's shipping HTML from the server now.
The pendulum swings. We went from server rendering (PHP, Rails) to SPAs (React, Angular) and now we're going back to server rendering but with TypeScript and components. Progress? I guess we had to learn it the hard way.
Build tools are a mess
A timeline:
- Grunt (remember Grunt?)
- Gulp
- Webpack (the big one)
- Parcel (zero config!)
- Rollup
- esbuild (finally, fast builds!)
- Vite (esbuild for dev, Rollup for prod)
- Turbopack (Rust-based, from the Next.js team)
- Bun (JavaScript runtime with built-in bundler)
Every year there's a new bundler that's 10x faster than the last one. Which is great! But also... we've been building JavaScript for a long time and we still can't agree on how to bundle it.
I spent two weeks in 2018 configuring Webpack for code splitting, tree shaking, and proper source maps. The config file was 300 lines. I understood maybe 40% of it. Then Parcel came along and did it all automatically. Then Vite made it even faster.
The pace of improvement is genuinely impressive - we went from 30-second webpack rebuilds to instant hot module replacement with Vite. But it's also exhausting to keep relearning the tooling every couple years.
My advice: just use Vite. It's fast, it works, and it's become the de facto standard. Don't overthink it. If you're starting a new project in 2025 and you're not using Next.js or another meta-framework, Vite is the answer.
TypeScript won
This one's actually settled. Nobody starts a new JavaScript project without TypeScript anymore. And that's good.
The type system catches real bugs. The editor experience (autocomplete, inline errors) is dramatically better. The cost of adding types is minimal once you're used to it.
I resisted TypeScript for years ('JavaScript is dynamically typed, that's the point!'). I was wrong. Types are good. TypeScript is good.
The moment I became a believer was when I refactored a function signature and TypeScript immediately showed me all 47 places I needed to update. Without types, I would have found those with runtime errors in production. Maybe not all of them. Maybe not for weeks.
Some practical tips I wish I'd known earlier:
Start with loose types and tighten gradually. Don't try to make everything perfectly typed on day one. any is fine while you're figuring things out. Come back later and add proper types when the structure stabilizes.
Use type inference. You don't need to annotate everything:
// Don't do this
const count: number = 5;
const isActive: boolean = true;
// TypeScript already knows
const count = 5;
const isActive = true;
// Do annotate function parameters and return types though
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
Zod or similar is your friend. TypeScript only checks at compile time. For runtime validation of user input or API responses, use Zod:
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
email: z.string().email(),
role: z.enum(['admin', 'user'])
});
// This validates AND gives you types
const user = UserSchema.parse(apiResponse);
The ecosystem has matured too. Five years ago, half the npm packages didn't have types. Now most do, and the ones that don't usually have community-maintained @types packages.
Tailwind is polarizing
The Tailwind debates are exhausting. People have strong opinions.
The 'con' argument: utility classes are ugly, it's like inline styles, it violates separation of concerns.
The 'pro' argument: it's fast to prototype, you don't have to name things, the constraints keep designs consistent.
I'm in the 'fine I guess' camp. It's not my favorite (I still prefer writing actual CSS), but I see why people like it. For building UIs quickly, it's effective. For this portfolio, I'm using plain CSS with CSS variables, because that's what I enjoy.
Here's what I've noticed: Tailwind is amazing for certain types of projects. Marketing sites, admin dashboards, MVPs - things where you need to ship fast and the design system can be standardized. It's less great for highly custom designs or when you need complex CSS (animations, grid layouts with weird requirements, etc).
The biggest benefit isn't actually the utility classes - it's that Tailwind forces constraints. You can't just use any color or spacing value. You pick from the design system. This prevents the problem where your app has 47 different shades of gray because everyone just eyeballed it.
Modern CSS is pretty good now though. Grid, flexbox, container queries, CSS custom properties - you can build complex layouts without any framework. Sometimes I wonder if Tailwind's popularity is partly because people don't realize how powerful vanilla CSS has become.
Use what makes you productive. Don't let Twitter arguments affect your tooling.
AI is changing everything (maybe)
Copilot and similar tools are interesting. I use them, they help, but they're not magic. Good for boilerplate, bad for architecture decisions.
The bigger question: what happens to junior developer jobs if AI can write basic code? I don't think anyone knows yet. But the skill of 'knowing what to build' is still firmly human. Translating business requirements into working software is harder than writing the code.
Here's what I've noticed in practice: AI assistants are incredible for writing tests, generating types from API responses, and converting repetitive patterns. They're less good at understanding your specific business domain or making tradeoffs between different approaches. Code review is still essential. The code works, but sometimes it's not the code you actually wanted.
Where AI actually helps me:
- Writing unit tests. I can describe the behavior and it generates good test cases.
- Converting data formats. "Turn this JSON into a TypeScript interface" - instant.
- Documentation. It's good at explaining complex code in plain English.
- Regex patterns. I still can't read regex fluently. AI can generate and explain them.
- Boilerplate CRUD operations. The 100th "create a REST endpoint for X" is tedious. Let AI do it.
Where AI struggles:
- Understanding existing codebases. It doesn't know your conventions, your abstractions, your historical decisions.
- Making architectural tradeoffs. Should this be a microservice? How should we handle caching? These need human judgment.
- Security. AI will happily generate code with SQL injection vulnerabilities or insecure authentication.
- Performance optimization. It can suggest generic improvements but doesn't understand your specific bottlenecks.
The real shift is that coding is becoming more about curation than creation. You generate code quickly, then spend time reviewing, refactoring, and ensuring it fits your system. Different skill set, still valuable.
Edge computing is suddenly everywhere
Edge functions, edge rendering, edge middleware - 'edge' is the new buzzword, and it's actually useful.
The idea: run your code geographically close to your users instead of in one central data center. Lower latency, faster responses, better user experience. Cloudflare Workers and Vercel Edge Functions make this trivial to set up.
What's interesting is how it's changing architecture patterns. You can now run serverless functions with <50ms cold starts. You can do server rendering for your authenticated pages from the edge. You can customize content based on geolocation without client-side JavaScript.
I built a feature recently that redirected users to region-specific pricing pages based on their location. With traditional server architecture, this would have required client-side JavaScript or accepting higher latency. With edge middleware, it happens at the CDN level before the page even loads. Sub-50ms response times globally. Pretty neat.
The downsides? Edge runtimes have restrictions - no full Node.js, limited package support, sometimes weird debugging. And for most applications, the latency difference doesn't actually matter. Your database is still in one region, so that's often the bottleneck anyway.
Here's a real example that tripped me up:
// This works in Node.js but fails at the edge
import bcrypt from 'bcrypt'; // Uses Node.js crypto APIs
// This works at the edge
import { hash } from '@node-rs/bcrypt'; // Compiled to WebAssembly
The edge runtime is essentially a JavaScript engine (V8) with web APIs, not a full Node.js environment. Packages that depend on Node-specific features (fs, crypto, child_process) won't work. You need to find edge-compatible alternatives or move that logic elsewhere.
Use it if latency matters (real-time apps, global audience). Don't use it just because it's trendy. For most CRUD apps, a traditional server in a single region is simpler and fine.
The real trend
The real trend is that we're slowly admitting that maybe the 2015-2020 era overcomplicated things.
-
SPAs for everything was a mistake. Not everything needs client-side routing and a full React app.
-
Microservices for startups was a mistake. You probably just need a monolith.
-
GraphQL for simple CRUD was a mistake. REST is fine.
We're swinging back toward simplicity. Server rendering. Fewer dependencies. Less JavaScript shipped to the client. It's not a return to 2010, but it rhymes with it.
Which is what we were doing before, but now it's called 'modern'.
What I actually recommend
If you're building something today:
-
For a simple website: Use HTML, CSS, and minimal JS. Add htmx if you need interactivity. Don't reach for React unless you need it.
-
For a web app: Vite + React/Vue/Svelte is fine. Pick one and learn it well.
-
For a backend: Whatever you know. Python, Node, Go, Rust, whatever. The language matters less than you think.
-
For deployment: Vercel, Fly.io, or just a VPS. Don't overthink it.
The best tech stack is the one you can ship with. Simple beats clever.