all Technical posts

Mistakes We Made in the Past

Let’s dive into the mistakes we’ve made, the lessons they taught us, and how they’ve shaped the way we tackle projects today.

We all love a good success story—how teams implemented cutting-edge solutions, improved performance, or achieved incredible results. These stories are inspiring, sure, but they only tell one side of the coin. What about the failures, the missteps, and the moments we aren’t exactly proud of? Those moments often hold the greatest lessons. So let’s switch it up for a change and talk about our mistakes—the things that made us pause, reflect, and ultimately grow.

Mistake #1: Not migrating to a newer framework

“Why should we upgrade if it works?” That’s what we thought during a project where we stuck with an outdated UI framework for far too long. Sure, it worked—until it didn’t. Here’s why this was a mistake:

  • Developer Morale: Developers were reluctant to work on it. While others enjoyed modern frameworks with great developer experiences, this one felt slow and cumbersome.
  • Hiring Challenges: Finding new developers familiar with this outdated technology became nearly impossible. Many hadn’t even heard of it, and the learning curve was steep.
  • Productivity Loss: Building features took too much time—too much boilerplate, too much manual effort. Newer frameworks could have cut that time in half, if not more.
  • Lack of Support: Libraries became deprecated, and support disappeared, making it harder to solve problems. Meanwhile, technical debt grew as the client continued demanding new features.

Eventually, we did migrate, but it should have happened much sooner. Sometimes, you just have to bite the bullet and put in the effort. In the long run, it’s worth it.

Mistake #2: Migrating too quickly or too often

While upgrading in a timely manner is important, migrating too quickly can be just as problematic. Major framework updates, such as those for Next.js, often come with unforeseen issues. New features may be exciting, but they’re not always stable—especially for lesser-known functionalities or library dependencies used behind the scenes.

We’ve had instances where an update seemed fine initially but caused unexpected issues in production. These situations often required urgent fixes that could have been avoided with better planning. Now, we prioritize gradual updates, focusing first on critical vulnerabilities while allowing less urgent updates to wait until the timing is right.

Mistake #3: Changing without proper analysis

TL;DR: If it works, don’t try to fix it—unless you have a solid plan.

On one project, we decided to overhaul the folder structure. The existing structure had worked for years, but the team wanted to try something new. The decision was close to a tie, but we moved forward with the change anyway. Initially, it made sense; files were easier to locate, and the logic seemed sound. But as the solution grew, it quickly became unmanageable.

Without sufficient time or budget to clean up the mess, the structure became a source of frustration. As the project was handed off to new developers, each attempt to “fix” it added more complexity. While the project was eventually discontinued for other reasons, no one looked back fondly on that structure.

This doesn’t mean we shouldn’t try new things—we absolutely should. But changes should be properly analyzed, documented, and agreed upon by the entire team. A little foresight can save a lot of headaches.

Mistake #4: Trying to DIY everything

Writing code often involves solving problems, and there are two main approaches: writing the solution yourself or using something pre-built, like a library. Both are valid. Creating your own solution can feel incredibly rewarding—there’s nothing like that eureka moment when everything clicks. Plus, a custom solution might be shorter and will avoid adding unnecessary weight to your project.

But when things get complex or large in scale, DIY might not be the best approach.

Example #1: Input validation

We once wrote our own validation library for a specific need, and it worked. But as time went on and more use cases emerged, maintaining it became a hassle. Eventually, we refactored it, but even then, it became clear that adopting a well-established validation library would have saved time and effort. Now, we use a library that handles edge cases seamlessly, reducing hassle from start to finish. It’s more flexible, reliable, and saves us time.

Example #2: Server-Side Rendering

For larger-scale challenges, the stakes are even higher. I recall a Single Page Application (SPA) project built with standard React. The client required server-side rendering (SSR) for SEO purposes, but instead of adopting Next.js—which was already gaining traction at the time—we decided to DIY SSR. Big mistake.

Sure, it worked and was reasonably fast, but the complexity was overwhelming. Developer experience suffered, CI/CD pipelines were a nightmare, and build times were excruciating. When the client requested a full layout refresh, we proposed rewriting the application in Next.js within the same timeframe it would take to adapt the existing framework. The result? A faster, more efficient application that exceeded expectations.

For complex challenges like SSR, it’s often best to trust experts and leverage their products. While a DIY solution might work initially, it’s likely to cost more in time, effort, and frustration down the line. Established tools are built by teams who have invested significant time in addressing edge cases and optimizing functionality. Sometimes, the smartest decision is to take advantage of that expertise.

Conclusion

Let’s face it—mistakes are a part of life in software development. They’re not just bumps in the road; they’re opportunities to learn, adapt, and grow. By reflecting on past missteps, we can spot patterns and implement strategies to avoid repeating them. Whether it’s knowing when to upgrade, carefully analyzing changes, or deciding when to trust established tools, these lessons help us make smarter decisions.

At the end of the day, the goal isn’t to avoid mistakes entirely—that’s impossible. Instead, it’s about making better choices as we move forward. By sharing our failures as openly as our successes, we contribute to a culture of learning and growth—for our teams, our projects, and the broader developer community. And isn’t that what progress is all about?

Subscribe to our RSS feed

Talk to the author

Contact Erwin

Architect

Hi there,
how can we help?

Got a project in mind?

Connect with us

Let's talk

Let's talk

Thanks, we'll be in touch soon!

Call us

Thanks, we've sent the link to your inbox

Invalid email address

Submit

Your download should start shortly!

Stay in Touch - Subscribe to Our Newsletter

Keep up to date with industry trends, events and the latest customer stories

Invalid email address

Submit

Great you’re on the list!