The Pattern We Keep Seeing
A company hires a development agency to build a corporate website. Six months and €80,000 later, they have a React single-page application with server-side rendering, a GraphQL API layer, a headless CMS, a PostgreSQL database, a Redis cache, Docker containers orchestrated by Kubernetes, and a CI/CD pipeline that takes 12 minutes to deploy a typo fix.
The site gets 500 visitors per day.
This isn’t a hypothetical. We’ve inherited projects exactly like this. The original developers weren’t incompetent — they were experienced engineers who applied patterns they knew from large-scale applications to a problem that didn’t need them. The result is a system that costs more to maintain than it cost to build, requires specialized knowledge to update, and delivers no measurable benefit over a solution ten times simpler.
What Over-Engineering Actually Costs
The sticker price of a project — the initial development cost — is a fraction of the total cost of ownership. Over-engineered systems multiply ongoing costs in ways that compound over years.
Maintenance Burden
Every dependency is a liability. A typical over-engineered web project might have 1,500+ npm packages in its dependency tree. Each one can introduce breaking changes, security vulnerabilities, or compatibility issues. Keeping the stack updated is a recurring tax that scales with complexity.
A static site generator with minimal dependencies might have 50-100 packages. The maintenance surface area is an order of magnitude smaller.
Knowledge Concentration
Complex architectures create knowledge silos. If only one or two developers understand how all the pieces fit together, you’ve created a critical business dependency on specific individuals. When those people leave — and they will — you’re facing either a knowledge transfer project or a partial rebuild.
Simple architectures are self-documenting. A new developer can open a Markdown file, an Astro component, and a CSS file and understand the entire system in an afternoon.
Opportunity Cost
Every hour spent managing infrastructure complexity is an hour not spent on what actually matters: content quality, user experience, conversion optimization, and business goals. We’ve seen teams spend more time debugging their build pipeline than improving their product.
Cognitive Load
This is the hidden cost. Complex systems require developers to hold more context in their heads. Decisions take longer. Changes require more analysis. The feedback loop between making a change and seeing the result stretches from seconds to minutes. Productivity drops, and the team doesn’t even notice because the complexity feels normal.
The Drivers of Over-Engineering
Understanding why over-engineering happens is essential to preventing it. It’s rarely malicious — it’s usually driven by reasonable-sounding instincts that lead to unreasonable outcomes.
Premature Scaling
“What if we go viral?” “What if we need to handle 100x the traffic?” These questions feel responsible, but they lead to building for a future that may never arrive. A static site on a CDN can handle viral traffic without any scaling architecture. A well-configured VPS can serve millions of page views per month. Build for your actual scale, and have a plan (not an implementation) for the next order of magnitude.
Technology Enthusiasm
Engineers love interesting problems. A straightforward corporate website isn’t an interesting problem, so there’s a natural temptation to make it interesting by introducing sophisticated architecture. This is professional malpractice dressed up as technical excellence. The client is paying for a solution to their business problem, not a playground for the developer’s curiosity.
Cargo Culting
“Netflix uses microservices, so we should too.” Netflix has thousands of engineers and billions of users. Their architectural decisions are responses to constraints you don’t have. Adopting their patterns without their scale is like buying a commercial kitchen to make breakfast for two.
Fear of Simplicity
There’s a perverse dynamic in our industry where simple solutions are seen as unsophisticated. A developer who proposes a static site for a corporate project might feel they need to justify why they’re not using the latest framework. But sophistication isn’t complexity — it’s solving the problem elegantly with the minimum necessary tooling.
How to Right-Size Your Architecture
The antidote to over-engineering is disciplined requirement analysis. Before choosing any technology, answer these questions honestly:
What are the actual functional requirements?
Not what might be needed someday. What does the system need to do in the next 12 months? List every feature. Be specific. “Display content” and “accept form submissions” covers 80% of corporate websites.
What are the actual performance requirements?
How many concurrent users? What’s the acceptable response time? What’s the expected data volume? Use real numbers, not aspirational ones. If you don’t have data, start with conservative estimates and measure after launch.
Who will maintain this system?
This is the question most teams skip. If the answer is “a junior developer and the marketing team,” your architecture needs to be simple enough for that reality. If the answer is “a dedicated DevOps team,” you have more latitude — but still shouldn’t use it without reason.
What’s the simplest architecture that meets these requirements?
Start with the simplest possible solution and add complexity only when you can demonstrate that the simpler approach fails to meet a specific, documented requirement. This isn’t laziness — it’s engineering discipline.
A Decision Framework
We use a rough framework when choosing architecture for client projects:
Static site generator (Astro, Hugo) when:
- Content changes less than daily
- No user authentication required
- Under 10,000 pages
- Team includes non-developers who need to edit content (via Markdown or headless CMS)
Lightweight server framework (Express, Fastify, Django) when:
- User authentication is core to the product
- Real-time data display is required
- Complex form processing or business logic on the server
- Database interactions beyond simple CRUD
Full application framework (Next.js, Nuxt, SvelteKit) when:
- Mix of static and dynamic pages with shared components
- SEO-critical dynamic content
- Complex client-side interactivity
- Team has framework expertise
Microservices when:
- Multiple independent teams work on different features
- Individual components need independent scaling
- Different components have fundamentally different technology requirements
- The organization has the operational maturity to manage distributed systems
Notice how microservices are last on the list. For most projects we encounter, the first or second option is the right one.
The Courage to Be Simple
The best engineering decisions often look boring from the outside. A Markdown file rendered to HTML and served from a CDN isn’t exciting. It doesn’t make for a compelling conference talk or an impressive-sounding tech stack. But it loads in under a second, costs nothing to host, never goes down, and can be updated by anyone with a text editor.
That’s not a compromise. That’s good engineering.
The next time you’re tempted to add a layer, a service, a dependency, or an abstraction, ask yourself: what specific, documented problem does this solve? If you can’t answer that question clearly, you’re not engineering — you’re decorating.
Build what you need. Nothing more.