Don't let AI choose your app architecture

When you ask an AI to build an app, the AI will pick an architecture. The architecture will be reasonable. The stack will be popular. The patterns will be conventional. None of this guarantees the architecture fits your actual app's requirements.

The reason AI defaults to popular conventional choices is mechanical: AI is trained on the corpus of code that exists, and the corpus is heavily weighted toward popular conventional choices. The AI's recommendation is essentially "what most people built last year." Sometimes that's what you need. Often it isn't, and accepting it as your architecture commits you to whatever the corpus average looks like rather than what your specific app needs.

I want to walk through what AI defaults toward, the four architectural decisions worth making deliberately, and how to decide which ones you can let AI handle versus which need your judgment. The discipline isn't to override every AI choice; it's to recognize which choices are load-bearing for your specific case.

What AI defaults toward

When asked to build a web app, AI defaults usually look like: React on the frontend, Node.js or Python on the backend, PostgreSQL or MongoDB for data, a popular hosting platform, a popular auth provider, popular vendor integrations. Each individual choice is fine. The combined default is whatever the majority of recent tutorials and example projects looked like.

The defaults work well when your app is similar to those tutorial/example projects. They work less well when your app needs:

Specific performance characteristics the defaults don't optimize for.

Specific integration patterns the defaults didn't anticipate.

Specific scaling requirements the defaults don't address.

Specific compliance requirements the defaults don't consider.

Specific user-experience patterns the defaults don't naturally support.

The trap is that you don't know which of your requirements is going to push against the defaults until you've built far enough to discover it. By then, switching defaults is expensive.

Decision one: stack choice

The stack is the foundational choice that constrains everything downstream. Picking the wrong stack means either fighting the stack constantly or eventually rewriting onto a different one.

The criteria that should drive deliberate stack choice:

What does the app actually do? An app that's primarily interactive UI has different stack requirements than one that's primarily background data processing or one that's primarily real-time communication.

What's the durability expectation? An MVP that might get rewritten in a year can use a less mature stack. A product expected to run for years should use a stack with community momentum that suggests it'll still be supported and improving.

What's the team's actual capability? A stack the team doesn't know becomes the bottleneck. A stack the team knows well can support whatever the app needs.

What integrations are required? Some stacks have rich ecosystems for certain integrations (payments, analytics, communication tools) and weak ecosystems for others. Match the stack to the integration surface.

The AI default may match these criteria for your case. It often doesn't. Verify the match before accepting the default.

Decision two: data model

Data modeling is where most app architecture decisions become irreversible. The schema you start with shapes the queries you can run, the features you can add, the integrations you can support, the performance characteristics you can hit. Changing the schema later is much more expensive than designing it deliberately at the start.

The AI will generate a schema that handles the immediate cases you described. The schema will be valid. The schema will likely not anticipate:

The queries you'll want to run six months in.

The relationships you'll need to express as the product grows.

The access patterns that will matter for performance at scale.

The data export and integration requirements that emerge later.

The compliance constraints (audit trails, retention, deletion) that production data has.

Spending an hour on data modeling before code starts is the highest-leverage architectural work you can do. Sketch the entities, the relationships, the cardinalities, the indexing patterns. Then let the AI generate against the model you designed rather than against its own first guess.

Decision three: integration boundaries

The app will integrate with external systems: payment processors, email services, analytics tools, communication platforms, AI vendors. Each integration is a coupling between your code and an external system you don't control.

The AI's default approach is usually to integrate directly: the vendor's SDK gets called from wherever the business logic needs it. The integration is fast to set up and hard to change. When you need to switch vendors (price increases, feature gaps, vendor failures), every place the old vendor was called needs to be updated.

The deliberate choice: design integration boundaries as small interfaces in your code. The business logic calls paymentProvider.charge() rather than Stripe.charges.create(). The implementation of paymentProvider is a thin adapter to whatever vendor you've chosen. Swapping vendors is changing the adapter, not refactoring the business logic.

This is the same principle as don't couple your orchestration to any one AI lab applied to integration architecture. The discipline upfront is small. The optionality preserved over the life of the app is significant.

Decision four: observability surface

Observability is the architectural decision that gets skipped most often because it doesn't feel architectural. It feels like an operational concern that can be added later. By the time you discover it can't be added easily later, you're firefighting blind in production.

The AI default is usually no observability. The app gets built, ships, and the team only finds out about failures when users complain. Adding observability after the fact requires touching every code path the team wants to instrument, which is much more expensive than instrumenting from the start.

The deliberate choice: design observability as part of the architecture. Structured logging built in. Error tracking integrated. Health endpoints exposed. Key business events emitted as observable signals. This costs hours at architecture stage. It saves days or weeks at firefighting stage.

The pattern works because observability is a cross-cutting concern. Designing it in once is much cheaper than retrofitting it everywhere later.

How VibeKoded's own stack was chosen

The stack of this site is Next.js with React and TypeScript on the frontend, R3F for the interactive mesh visualization, GSAP and Lenis for animation and smooth scroll, Formspree for form handling, the Anthropic SDK for the chat. Each piece was picked deliberately for a specific reason.

Next.js because the site's pages are content-shaped (blog posts, service descriptions, intake form) and Next's static generation plus React server components fit that shape well. The performance characteristics and SEO behavior come from the framework choice.

R3F (React Three Fiber) for the mesh because the central /log page needed an interactive visualization, and R3F lets the visualization be a real React component rather than a separate canvas API to manage. The architecture stays coherent.

GSAP plus Lenis for the animations because the site's identity includes specific timing and scroll behavior, and the libraries that produce that quality consistently across browsers were the deliberate choice rather than the default of CSS-only animation.

Formspree for forms because it removes the form-backend work without forcing a third-party iframe (the Amendment D pivot that drove this choice).

Anthropic SDK for the chat because the site already uses Claude extensively and the SDK is well-documented.

None of these were AI defaults. They were deliberate choices matched to what the site actually needed. The result is a stack that supports the requirements without fighting them, which is what designing architecture deliberately gets you.

When AI default is fine

You don't have to override every architectural choice. Defaults are fine when:

The app's requirements are typical (the kinds the defaults were optimized for).

The team's capability matches the default stack.

The data model is genuinely simple (a couple of entities, basic relationships).

Integration count is small (maybe one or two external systems).

Observability concerns are minimal (the app is read-only, low stakes, or has obvious health signals).

When most of these are true, the AI default will work fine. When some of them aren't true, the corresponding architectural decision is where deliberate choice pays back.

The discipline is to identify which decisions are load-bearing for your specific app and make those deliberately. The decisions that aren't load-bearing can be defaults. Distinguishing the two is the meta-skill that makes AI-assisted app development actually work.


Got an app to build and want help making the architectural decisions that actually matter for your case? Send the app description, the team's stack experience, and the integration requirements. VibeKoded can scope the prototype, build the MVP, or hand off the production app. → Work with VibeKoded