A single deliberate path forking away from a tangle of unused routes

Complexity, Deliberately Deferred

July 31, 2026 · Mitchel Pigsley

Starting a technology company, and specifically a B2C SaaS company, has its fair share of challenges. I'm sure this comes as no surprise. The technology is one part of this, sure, but there's research, marketing, distribution, design, user experience, and the list grows as you continue to understand your product and place in the market. On any given day, where do you spend your time? The market will not wait for you to figure it out, so spend that time wisely.

What I'm saying is that it's difficult, and as a result, I find myself hanging out in online spaces with other entrepreneurs and founders. Over time, I've begun to notice a sort of hive-mind mentality when it comes to technology in greenfield projects: complexity is baked in from the start. Infinite scale, microservices, Kubernetes fleets, and a caching architecture that resembles a nachos supreme. All of it paid for out of the only budget a founder really has, which is attention.

We've deliberately chosen a different path. Come on the journey with me.

Off-Ramps to Scale

Before I show you a single technology choice, let me give you the principle behind all of them. A startup obsessed with technical scale can drown in complexity and slowing development cycles before their hundredth user. On the other end of the spectrum, one of the typical ways I see vibe-coded apps fail is at scale because no one is paying attention. This often leads to major architectural rewrites or outright project failure. If you want to start small, you have to give yourself off-ramps you can quickly take when you need more scale. Put another way: a deferred decision is only cheap if reversing it is cheap. Here are a handful of scaling off-ramps we have built and why.

Single server deployment. It's incredible how much load a single, optimized server can handle. Even on lower-cost hardware, our tests have shown we can easily support 20+ live sessions at once. We can scale up to larger machines before we ever need to think about horizontal scale. A single server affords us simplicity. We maintain many connections for our live sync. When we run a single server, we know every connection is held in memory, so we can route live data quickly to every connection that needs it. Global caching can also happen right in memory.

But as soon as scale arrives, horizontal scaling is the plan. The off-ramps we have created are simple and involve adding a single new piece of infrastructure: Redis. Our in-memory cache service mimics the Redis interface so that we can drop in the Redis client package without rewrites. Our live connections already track the machine ID. If we pull up a connection's machine ID that doesn't match the current machine, we can simply add Redis pub-sub to route data to a different machine.

We even have off-ramps at the infrastructure level. I chose not to deploy NextJS to Vercel for this reason. The application is Dockerized and hosted on Fly.io, where scaling up, scaling out, and even going multi-region are all a configuration away. Infrastructure as code (IaC) is the general solution here; I've found it to be dead-simple with Fly.

Core Technology

With the principle in hand, our stack choices should start to make sense. Every one of them defers some complexity and keeps an exit open.

Let's start with one of the first technology decisions we made: the monolith. Everything that encapsulates our product offering, from our marketing website to our authenticated user and admin portals, and even further to our various API surfaces. It all exists as one codebase. We chose NextJS for this.

Now at the time, NextJS was something I was extremely skeptical of. I come from a Node, Express, and Webpack/Vite background where the Single-Page Application (SPA) reigns king. The thought of bundling it all up together into one all-encompassing framework run by a company I wasn't particularly fond of was something of a risk. Ultimately, I made the decision because a true monolith requires different endpoint rendering. I wanted REST API endpoints alongside our statically cached public pages or client-state-heavy experiences. And I wanted to use the same set of core components across all surfaces without choosing a mono-repo system like Turborepo, which, I've found, means opting into a good amount of complexity from the get-go. So TypeScript-flavored NextJS it was.

But because I was skeptical, I built myself an off-ramp here too. I decided to maintain a REST API instead of using server actions, which has always felt messy and hard to maintain to me. I am also building an application with an incredible amount of live-synced data that I was not planning on managing with NextJS, so an API felt right. And if NextJS ever stops earning its place, the application ports out. The framework is a decision, not a commitment. So far, I'm happy to report, it has earned its place.

REST APIs are difficult in other ways, like maintaining a data agreement between the back and front-ends. That's where Zod comes in. An endpoint's returned data is the same Zod schema that's parsing it on the front-end. Change the shape of the Zod schema, then all type errors light up red on your server and across your front-end. The type system lets you rapidly make significant data-model changes without risking the bugs that typically come with the effort. Drizzle, our ORM, does the same work one layer down. Converting complex queries into narrow TypeScript objects was a huge boost to development speed, and changes to our data model propagate outward from Drizzle and through our type system. Deferring complexity only works if changing your mind stays cheap. TypeScript is the vehicle for rapid change.

The last piece was our main datastore. I'm a bit of a MERN stack fanboy (MongoDB, ExpressJS, React, NodeJS). You could say it's my bread and butter. But instead of MongoDB or another document store, I ended up choosing the SQL path as I knew this new project was going to require a highly relational data model. The more interesting choice was Turso (SQLite).

I think it's a good idea to have software ideals. Things you want to optimize for and prioritize. It helps in the decision-making process. For example, one of my personal goals was to drive down latency at all costs, and in this pursuit I learned of embedded replicas offered by Turso. Reads stay local to the machine where your code is running. Zero network overhead. In practice, it was unbelievably fast for a read-heavy application. I was sold.

We are left with a very lean set of core technologies: NextJS (React, TypeScript, Node), Zod, and Turso w/ Drizzle. All chosen in the name of low complexity, quick development cycles, and a fast server. As the need arises, we can take the off-ramps.

Resisting Vibe-Design

Less code, more philosophy. In the age of AI and agentic vibe-coding, there has been an explosion of new websites. A quarter of startups in YC's 2025 cohort have codebases that are almost entirely AI-generated. I predict this trend will not slow down either. So how is any one app, SaaS, or online business meant to compete in a market that's growing more crowded?

The way we have chosen to stand out is with human design and a custom component library. One thing I've noticed is that websites created with Claude Code have a certain feeling. A sameness very reminiscent of the wave of Bootstrap-flavored websites that appeared nearly a decade ago now. Even ShadCN-based websites have a sameness, even though they can be "custom configured."

Every piece of text, each space between elements, and all buttons, inputs, dropdowns, tooltips, popovers, and every last visual element were crafted to mirror our human vision for design. We want to stand out with craft and quality.

At this point, you may be wondering why craft and quality are not in direct opposition to everything I've said so far. And to that I'll say: they are, and that's the point. Our component library is not deferred complexity. It's complexity we chose to pay for upfront, with the attention we saved by not building Kubernetes fleets. In a market flooding with generated sameness, design is what makes us recognizable, and the things that make you recognizable are the only things worth doing early.

Being deliberate with product design does not mean moving slow on development, either. If you run as fast as you can to every next feature, you'll trip over yourself. The goal is to develop quickly as soon as a decision is made.

Slay the Real Dragons

The dragons were never technical. No startup at our stage dies from a missing message queue. They die from bad distribution, weak retention, and building something nobody wants. Every decision I've walked you through exists so that our attention stays pointed at those dragons instead of feeding imaginary ones.

Complexity is not the enemy. Premature complexity is. Defer it deliberately, pave your off-ramps, and spend everything you save on the fight that actually decides whether you make it.

Join us and connect for poems, content, updates, direct questions, community discussion, and more.

© 2026 Realms of Shod. All rights reserved.