How I Choose Between Web Architectures

August 11, 2026 Melalew Mengistu 15 min read

When I first started building websites, I didn't plan anything. I opened my editor, wrote HTML, added some CSS, and kept styling until it looked right. Then I added JavaScript, broke the layout, fixed the layout, broke the JavaScript, and started over. I would spend hours moving the same components around, renaming classes, and rewriting state logic because I hadn't decided how any of it connected before I started. The result was always the same: a working page that I was afraid to touch, held together by momentum rather than structure. Sometimes I introduced real bugs. Sometimes the brand looked inconsistent across pages because every component was styled in isolation. I was coding first and thinking later, and I paid for it with my time.

The turning point came when I joined a team project and watched another developer sketch out a folder structure and data flow before writing a single line of UI code. I thought it was overkill. Then I watched him build twice as fast as me with half the bugs. That's when I understood that web architecture isn't bureaucracy. It's the decision that saves you from rewriting your own work.

Web architecture comparison: monolith vs microservices vs JAMstack
Architecture decisions shape every scalability and security trade-off that follows.
What This Guide Actually Covers

This isn't a computer science lecture on design patterns. I'm breaking down how I actually choose a web architecture — what architecture means in practice, why skipping it costs more time than it saves, what kinds of projects actually need one, and whether you can automate the decision or rely on existing templates.

What Happens When You Skip Architecture

Web architecture is the skeleton of your application. It's how your components talk to each other, where state lives, how data flows from the user to the server and back, and what happens when you need to add a new feature six months later. When you skip it, you don't avoid complexity — you delay it. The complexity still arrives, but now it's buried inside files that were never meant to hold it.

I used to dump all my state into a single global object because it was easy. Then I needed two components to react to the same change differently, and I had to refactor everything. I used to style each page independently because it was faster on day one. Then the client asked for a rebrand, and I had to open twenty files to change a primary color. I used to fetch data directly inside components because it felt natural. Then I needed to cache the same data across routes, and I had to tear out every fetch call and replace it with a shared layer. Every one of those refactors was avoidable. They happened because I started building before I decided what I was building.

What Web Architecture Actually Is

At its simplest, web architecture is a set of decisions you make before you code. Where does state live? In a global store, in component memory, in the URL, or on the server? How do components communicate? Through props, events, a shared bus, or direct imports? What's the boundary between frontend and backend? Does the server render pages, or does the client fetch JSON and build the DOM? How do you handle routing, authentication, caching, and errors?

These aren't abstract questions. They determine whether your codebase grows cleanly or collapses under its own weight. A good architecture doesn't make your app faster on day one. It makes your app possible on day one hundred.

How I Choose

I don't pick architectures based on what's trending. I pick them based on what the project actually needs. I ask four questions before I choose a stack or a pattern.

How complex is the state? If I'm building a marketing site with a contact form, I don't need Redux or a global store. Component-level state is enough. If I'm building a dashboard where ten widgets need to stay synchronized with real-time data, I need a centralized state layer and a clear data flow.

Who owns the rendering? If SEO matters and the content is mostly static, I lean toward server-side rendering or static generation. The server sends HTML, the browser displays it, and JavaScript hydrates interactivity. If the app is behind a login wall and behaves like software — dragging, dropping, live updates — then client-side rendering with a strong component framework makes more sense.

How big is the team? A solo developer can get away with loose conventions. A team of five needs strict boundaries so no one accidentally couples their feature to someone else's internals. Micro-frontends, module federation, or strict monorepo boundaries become relevant when multiple teams ship code to the same domain.

What's the lifespan? A landing page that lives for three months doesn't need architecture. A product that will evolve for years needs patterns that can absorb change without requiring rewrites.

Don't architect what you don't need. A static blog doesn't need a micro-frontend strategy. A single-page dashboard doesn't need server-side rendering. I match the complexity of the architecture to the complexity of the problem. Over-engineering is just another form of technical debt.

Existing Architectures and Automation

There are already well-defined architectures you can adopt instead of inventing your own. MVC separates models, views, and controllers. MVVM binds views to view-models for reactive UIs. Component-based architecture breaks the UI into isolated, reusable pieces. Jamstack pre-builds pages and serves them from a CDN. Micro-frontends split a single application into independently deployable pieces. Each of these exists because someone else already solved a category of problem.

Can the choice be automated? Partially. Scaffolding tools like Next.js, Nuxt, or Angular CLI make architectural decisions for you by providing a default folder structure, routing layer, and state conventions. AI assistants can suggest patterns based on your requirements. But the final decision still needs a human. No tool knows whether your team is distributed across time zones, whether your content is updated hourly or yearly, or whether your biggest risk is load time or maintainability. I use automation to speed up implementation, not to replace judgment.

Decision: Server vs Client Rendering
// Static marketing page: server sends HTML
// - Better SEO
// - Faster first paint
// - Less JavaScript to hydrate

// Interactive dashboard: client builds DOM
// - Complex state synchronization
// - Real-time updates via WebSocket
// - Heavy user interaction

// Hybrid: server renders shell, client hydrates
// - Best of both when done carefully
// - Adds complexity; only use when both needs are real

Production Checklist

These are the rules I hold every project to now:

  • Decide state ownership, rendering strategy, and component boundaries before writing UI code — architecture is cheaper on paper than in production
  • Match the architecture to the project's actual complexity; a landing page doesn't need the same skeleton as a SaaS dashboard
  • Use existing patterns and frameworks instead of inventing custom conventions unless the problem genuinely demands it
  • Let scaffolding tools handle boilerplate, but never let them make business-logic decisions for you

I don't start projects with components anymore. I start them with constraints. I write down what the app needs to do, who will maintain it, and what will change. Then I pick the lightest architecture that satisfies those constraints. The code I write afterward is cleaner, faster to build, and doesn't fall apart when I come back to it three months later. Architecture isn't overhead. It's the only thing that keeps your first draft from becoming your final mess.

SPA Performance Tradeoffs

Single-Page Applications deliver the entire application as a JavaScript bundle that runs in the browser. The appeal is interactivity — fast transitions, real-time updates, no full-page reloads. The cost is initial load performance and SEO complexity.

JavaScript bundles must be downloaded, parsed, and executed before the application is usable. For a large SPA, this can mean several seconds of blank screen or loading spinner on a mid-range device with a slow connection. Code splitting addresses this by breaking the bundle into chunks that load on demand — only the code for the current route is loaded immediately, with other routes loaded lazily when navigated to. Tree shaking removes unused exports from the bundle at build time. Together these reduce the initial bundle to the minimum necessary. But the fundamental tradeoff remains: SPAs trade initial load performance for runtime interactivity. If your users are on slow networks or low-end devices, this tradeoff deserves scrutiny before you choose SPA by default.

SEO is a related consideration. Search engine crawlers have historically struggled with JavaScript-rendered content. Google's crawler now executes JavaScript, but with a processing queue and delay. If your content needs to be indexed quickly and reliably — a blog, a product catalog, a documentation site — client-side rendering is the wrong choice. Server-side rendering or static generation will consistently outperform SPA for search indexing.

SSR, SSG, and ISR Explained

Server-Side Rendering (SSR) generates HTML on the server for each request. The browser receives a complete, rendered page — immediately readable and indexable. The tradeoff is server compute per request and slightly more complex deployment compared to a static file host. Next.js's getServerSideProps is the canonical implementation: data is fetched on the server at request time and injected into the page before it is sent to the browser.

Static Site Generation (SSG) renders HTML at build time. Every page is pre-built and stored as a static file, served directly from a CDN with no per-request compute. This is the fastest possible delivery mechanism for content that does not change per user. A blog, a documentation site, and a marketing site are all excellent candidates for SSG. The limitation is that content only updates when you redeploy.

Incremental Static Regeneration (ISR), introduced by Next.js, bridges the gap. Pages are statically generated but can be regenerated in the background at a configurable interval — say, every 60 seconds. A user who visits a product page gets the pre-generated static file (fast), but the page is silently rebuilt in the background after it is 60 seconds old, so the next visitor gets fresh content without a full redeploy. ISR is one of the more elegant architectural innovations in modern web frameworks: it delivers CDN-level performance while keeping content reasonably fresh.

The rule of thumb: SSG for content that changes infrequently and is the same for all users. SSR for content that is personalized or changes frequently. SPA for highly interactive tools where the JavaScript payload is justified by the interaction model.

Edge Computing and CDN-First Architecture

Traditional web architecture runs all server-side logic in one or a few data center regions. A user in Nairobi hitting a server in Virginia experiences 200ms of round-trip latency before a single byte is processed. Edge computing distributes compute to dozens of locations worldwide — the code runs at the edge node closest to the user, reducing latency to single-digit milliseconds for many users.

Cloudflare Workers and Vercel Edge Functions let you run JavaScript at the edge — geographically distributed, with strict execution time limits and no persistent file system. They are ideal for: request authentication and routing decisions, personalization headers, A/B testing logic, geo-based redirects, and lightweight API responses that do not require a database. For compute that requires a database or longer execution, traditional serverless functions (AWS Lambda, Vercel Functions) running in a fixed region remain the better choice.

The security implications of edge compute are worth considering. Your code runs in a shared multi-tenant environment managed by the provider. Secrets must be managed through the provider's environment variable system, not hardcoded. The execution context has no access to the filesystem. And because the code runs at the edge — potentially in dozens of countries — you need to think carefully about data residency if your application handles personal data subject to GDPR or similar regulations.

Security Implications of Each Architecture

Architecture choices have direct security consequences that are rarely discussed in framework documentation.

SPAs have a broader XSS attack surface than server-rendered apps because all data handling happens in the browser. The entire application state is client-side JavaScript, which means an XSS vulnerability can access any data the app can access. A Content Security Policy that blocks inline scripts is harder to implement in SPAs because many frameworks and third-party libraries use inline styles and scripts. CORS must be configured on the API server since the SPA makes cross-origin requests by default.

SSR reduces the XSS surface because sensitive operations happen on the server, but introduces server-side rendering injection risks if user data is interpolated into HTML templates without encoding. It also concentrates sensitive data processing on the server, which needs proper access controls and logging.

Microservices expand the attack surface through service-to-service communication: each internal API call is a potential injection point, authentication must be verified at each service boundary (not just at the edge), and a compromise of one service can be used as a pivot point to reach others. Zero-trust network architecture — where every service-to-service call is authenticated and authorized, regardless of network position — is the appropriate security model for microservices, and it adds significant engineering complexity.

Melalew Mengistu

Melalew Mengistu

Web engineer and web security specialist. Helps teams build and ship secure applications.