What 20 Years of Web Development Taught Me About Building Things That Last

Hard-won lessons on architecture, technical debt, choosing the right tools, and why the businesses that invest in doing it right always come out ahead.

January 15, 2025

Twenty years is a long time in web development. I've watched entire frameworks rise and fall. I've seen companies pivot from monoliths to microservices and back again. I've rebuilt systems that were built just five years prior because someone thought a trendy new approach would be better. And through all of it, I've learned a few things that don't fade with the seasons.

I'm not going to tell you I got everything right. Far from it. I've made plenty of mistakes—expensive ones. I've shipped code I'm not proud of. I've convinced clients to adopt architectures that seemed brilliant at the time but became anchors by year two. But those failures taught me more than any success ever could. They taught me what actually matters when you're building something that needs to work, scale, and remain maintainable when you hand it off to someone else.

The Right Level of Abstraction Is Worth Obsessing Over

Early in my career, I built a content management system for a mid-sized publishing company. We were fresh off reading about design patterns and microservices architecture, and we decided their simple editorial workflow needed a highly abstracted, loosely-coupled component system. It was elegant. It was future-proof. It was also completely unmaintainable.

Two years later, their in-house developer told me they'd rather rewrite the whole thing than add one new feature. He wasn't exaggerating. The abstractions we'd built were so aggressively generic that even understanding what the code was supposed to do required reading through five layers of interfaces, adapters, and provider patterns. A simple content approval workflow had become a philosophy seminar.

On the flip side, I worked with a startup that had the opposite problem. They'd built a MVP in three weeks with everything hardcoded—database queries jammed into controllers, styling baked into components, logic scattered everywhere. It worked great until they needed to scale. At that point, they faced a choice: refactor for a few weeks, or watch their infrastructure buckle under even modest growth.

What I learned is this: the right level of abstraction depends on what your code actually needs to do, not what it might do someday. You need to know your constraints. Is this a feature that will change every month? Architect for flexibility. Is this a core calculation that will run the same way for years? Keep it simple. The best codebases I've seen strike a balance—they're simple where they need to be, and properly abstracted where complexity genuinely exists.

This is why I build the way I do now. Every architectural decision has to answer: "Does this solve a real problem, or am I solving an imaginary future?" That discipline saves months of work later.

Technical Debt Compounds Like Interest, But Worse

There's a metaphor we use in development called "technical debt," and I've come to think it's actually understating the problem. Interest on financial debt is predictable. You borrow money, you pay a set percentage each year. Technical debt doesn't work that way.

I watched a financial services company build a trading platform that was "good enough." The engineers made reasonable compromises—some quick database queries that didn't have proper indexes, some code duplication because refactoring would have taken another sprint, a few workarounds instead of proper error handling. None of this was negligence. They were shipping under deadline, making conscious trade-offs.

For the first year, it was fine. The second year, performance issues started to emerge. They added caching and optimization. That worked for a while. By year three, adding any new feature took twice as long as it should because the underlying system had become so fragile that every change risked cascading failures. They finally committed to a six-month rewrite.

The math was brutal. If they'd invested 15% more time in the original build—more thorough testing, proper architecture, handling edge cases upfront—they would have saved that entire six-month rewrite. They would have saved hundreds of thousands of dollars. Instead, they paid interest on their debt for three years, and then paid the principal all at once.

I'm not advocating for perfection. Perfect doesn't exist, and chasing it is its own trap. But I've learned that decisions made under time pressure compound in ways that are hard to predict. The interest rate accelerates. What started as a two-week workaround becomes a two-month architectural problem.

This is why I'm fanatical about building things right the first time, even when it means the timeline is a bit longer. The businesses I've worked with that understood this—who budgeted properly and didn't panic about every week of development—they're still running those systems. The ones who cut corners? They're either rewriting or they're hemorrhaging money on maintenance.

Tools Change, but Principles Are Permanent

I've built with Cold Fusion, Perl, Ruby, Python, PHP, Node, Go, and countless JavaScript frameworks. Some of those languages are museum pieces now. Some of the frameworks are jokes at developer happy hours.

But here's what I notice: the companies that have survived all these shifts are the ones that understood that tools are just tools. They care about frameworks and languages because they should—the right tool for the job matters. But they don't build their entire thinking around a framework that might be obsolete in five years.

The principles that matter—separation of concerns, clear data flow, testability, performance as a feature, accessibility from day one, security that isn't bolted on afterward—those are timeless. You can build them with any reasonable framework. And when the framework does become obsolete, code written with those principles migrates cleanly. Code written as a spaghetti mess of framework magic does not.

I've seen teams make the mistake of optimizing everything for the framework du jour. They structure their entire business around React-specific patterns, or Vue conventions, or whatever was hot in 2023. When the industry winds shift, and they do, those teams are stuck either maintaining dying code or paying enormous costs to migrate.

I've also seen teams so determinedly framework-agnostic that they reinvent basic wheels and end up with weird, unmaintainable code. That's the other ditch.

The sweet spot is knowing your principles so well that you can work cleanly in any tool. That's craftship.

Build for the Next Developer, Not for You

This one nearly cost me my reputation early on.

I wrote some genuinely clever code—a piece of URL parsing logic that was maybe twelve lines and did something that would normally take fifty. It was clever because I understood the domain deeply and found an elegant solution. It was also undocumented, used some non-obvious language features, and made perfect sense to me and possibly no one else.

Three months later, a colleague tried to modify it for a new feature. He spent an hour trying to understand what it was doing. He gave up and rewrote it in a straightforward way that was twice as long but immediately understandable.

I learned something uncomfortable that day: clever code is a form of technical arrogance. You're prioritizing the moment of writing over months of maintenance. You're assuming future readers will be as clever as you, or that they'll care enough to figure it out.

The best code I've written in the last decade has been boring. It's explicit. It uses straightforward patterns. It has clear variable names. It has comments explaining why something is done a certain way, not what the code does—anyone can read what it does. When someone else (or future me) needs to modify it, they can do it quickly and safely.

I've come to genuinely enjoy writing code this way. It's harder than it sounds. It requires discipline to choose clarity over cleverness every single time. But code is read far more often than it's written, and that next developer—they might be brilliant, or they might be tired on a Friday afternoon, or they might just think differently than you do. You owe them clarity.

The Business Case for Doing It Right

This is the conversation I have most often with clients, and it usually starts with them concerned about cost and timeline.

"We can ship this in six weeks," I'll say, "if we cut corners here, here, and here. Or we can ship it in eight weeks with architecture that will scale, code that your next developer won't want to burn down, and a system you can actually extend without it becoming fragile."

The impulse is always to take the six weeks. I get it. Time to market matters. Revenue matters. But what I've learned is this: that two-week difference comes back to haunt you.

A saas company I worked with chose the eight-week path. Their product was solid from day one. When they needed to scale from one thousand to ten thousand users, they didn't panic. The architecture held. The database hummed. When they wanted to add a new feature, it took two weeks instead of six because the codebase was clean and well-understood.

I can't say they wouldn't have succeeded with rushed code—maybe they would have. But I know their CAC (customer acquisition cost) wasn't inflated by money spent on emergency refactoring. I know their engineers weren't burning out trying to maintain spaghetti. I know they could hire new developers who could be productive in a reasonable timeframe.

There's also something intangible about working with code that's built well. Team morale goes up. You spend less time in debugging and more time building. Bugs are easier to catch because the codebase is simple enough to understand. New features don't introduce random cascading failures.

The most profitable companies I've worked with weren't the ones that were fastest to market. They were the ones that understood that investing in architecture, in testing, in documentation, in doing things right, was an investment in their ability to move fast for years to come. "Just ship it" culture works great until you need to ship something new. Then you're shipping in molasses.

Why Experience Matters When Choosing Who Builds Your Technology

Here's the thing about twenty years of experience: it's mostly failures wearing different masks.

I've seen scaling problems before. I've felt the specific pain of a system designed for a hundred users that suddenly needed to handle ten thousand. I've inherited messy code and known where it was going to break. I've watched teams optimize for the wrong metrics and built up knowledge about which tradeoffs actually matter.

That experience is the difference between someone who knows frameworks and someone who knows architecture. I can recognize patterns that look good on the surface but will become problems in two years. I can push back when a client wants to build something in a way I know will hurt them, because I've seen the outcome before.

I think about this when I meet with potential clients. There's always someone cheaper—someone who codes faster or charges less per hour. Sometimes they're great. But there's value in working with someone who has strong opinions based on hard experience. Someone who will tell you when an idea is going to cost you more than it saves. Someone who has seen enough versions of the same problem that they can spot it early.

That's why I built Pfaff Digital the way I did. No handoffs to junior developers who don't understand the architecture. No diluting opinions from a committee. Just experience, consistency, and accountability. I've learned that continuity matters. When I'm writing code for your system, I'm thinking about the developer who will maintain it five years from now—and ideally, I'm that developer, or I'm leaving something so well-documented and well-architected that the next person feels like I'm there alongside them.

What Lasts

At the end of twenty years, I've built a lot of things. Some of them are gone—companies shuttered, projects cancelled, technologies abandoned. But the ones that lasted had common threads. They were built with principles in mind, not just deadlines. They were architected to be understood by others. They were maintained properly instead of just patched. And they were built by people who cared about doing it right, even when it would have been faster to do it wrong.

That's the standard I hold myself to now. If you're thinking about building something—a product, a platform, a system that's going to matter to your business—that's the standard you should hold whoever you choose to.

If you want to talk about what building things right looks like for your project, I'd be happy to have that conversation. You can book a discovery call at /contact, and we can talk through what you're trying to build and what it would actually take to build it well.

Twenty years taught me that there's no such thing as a shortcut that doesn't eventually get expensive. The businesses that win are the ones that understand that.