I spent three weeks setting up a Kubernetes cluster, a CI/CD pipeline with automated canary deployments, and a strictly typed hexagonal architecture for a tool that had zero users. I told myself I was building for scale. In reality, I was procrastinating on the hard part: finding out if anyone actually wanted the product.
When you work on a company project, over-engineering is often a safety net. You have a team to review your code and a company that pays for the infrastructure. When you are an indie developer, over-engineering is a trap. It feels like progress because you are writing code, but it is actually a form of resistance.
The Scaling Myth
Many of us suffer from the fear of the 'success disaster'. We imagine that the moment we launch, a million users will flood in and crash our single-instance Postgres database. Because of this, we spend days implementing Redis caching or microservices before we have a single signup.
Here is the truth: crashing because you have too many users is a high quality problem. It is a problem you want to have. If you spend a month building a system that can handle 100k requests per second, but you only get 10 users, you have wasted 99 percent of your effort.
The Minimal Viable Tech Stack
To move fast, you need to reduce the number of decisions you make. Every new tool in your stack is another point of failure and another thing to maintain.
For most indie projects, a boring stack is the best stack. Use a framework you know inside and out. If you know Rails, use Rails. If you know Laravel, use Laravel. If you prefer Next.js, stick with it. Do not use an indie project as a playground to learn a new language or a niche database unless the learning process is the primary goal of the project.
Stick to these rules for your first version:
- Use a monolithic architecture. Splitting things into services too early adds network latency and deployment complexity.
- Use a relational database. Postgres can handle way more than you think. Do not add NoSQL just because it sounds modern.
- Avoid complex state management. Use simple props or basic contexts. You do not need a massive Redux store for a landing page and a CRUD dashboard.
Technical Debt is a Tool
Developers are taught that technical debt is bad. In a corporate setting, that is mostly true. In an indie setting, technical debt is a strategic tool.
Taking on debt means you are choosing to write a quick, suboptimal solution now so you can validate a feature faster. If the feature fails, you delete the code and the debt vanishes. If the feature succeeds, you now have the data and the motivation to refactor it properly.
When I say 'quick solution', I do not mean broken code. I mean code that is easy to understand but not perfectly abstract. Avoid the temptation to create a generic 'BaseService' or a 'PluginSystem' for a feature that might only ever have one implementation.
The Shipping Metric
Your goal is not to write beautiful code. Your goal is to close the loop between an idea and user feedback.
Every hour spent tweaking a CSS animation or optimizing a database query that takes 10ms instead of 50ms is an hour not spent talking to users. If the app works and the UI is clean, ship it.
If you find yourself spending more than a day on a 'foundation' task that does not directly result in a user-facing feature, stop. Ask yourself: 'Will the app break if I do this the simple way?' If the answer is no, do it the simple way.
Concrete Takeaway
Next time you feel the urge to add a new library or refactor a working module for 'better architecture', do this instead: write a list of the three most important features your users need. If your current work does not directly contribute to those three things, stop and move back to the feature list. Ship the 'ugly' version first. You can't optimize a product that nobody uses.
Top comments (0)