The Adaptable Approach
What if your application could work both ways? Serve complete HTML pages for browsers (and users with JavaScript disabled), but return JSON for JavaScript-powered clients? This is the adaptable MVC approach — the same controller handles both scenarios.
Adaptable MVC: Two Paths, One Backend
Path A: JavaScript Enabled (Enhanced Experience)
+----------+ fetch('/api/users') +------------+ +-------+
| Browser | Accept: app/json | | Query | |
| (JS on) | --------------------> | Controller | ---------> | Model | <--> DB
| | <-------------------- | | <--------- | |
| Renders | JSON response | Checks | Data +-------+
| via DOM | | Accept hdr |
+----------+ +------------+
Path B: No JavaScript (Baseline Experience)
+---------+ GET /users +------------+ +-------+
| Browser | Accept: text/html | | Query | |
| (JS off)| --------------------> | Controller | ---------> | Model | <--> DB
| | <-------------------- | | <--------- | |
| Displays| Complete HTML page | Checks | Data +-------+
| HTML | | Accept hdr |
+---------+ +------------+
The key insight is that both paths share the same controller and model. The only difference is what the controller returns: HTML or JSON. The decision is made by inspecting the request.
Content Negotiation with the Accept Header
The controller inspects the request's Accept header (or the X-Requested-With header) to determine what format to respond with. This is the same content negotiation mechanism used in REST APIs.
When a browser navigates to a URL directly, it sends Accept: text/html. When JavaScript calls fetch() with JSON headers, it sends Accept: application/json. The controller uses this to decide its response format.
Node.js (Express) Example
PHP Example
Progressive Enhancement in Practice
In the adaptable approach, the JavaScript enhancement layer is optional. The HTML pages work perfectly on their own. When JavaScript is available, it intercepts form submissions and link clicks, replaces them with fetch() calls, and updates the DOM without a full page reload.
This means:
- Without JavaScript: forms submit normally, pages reload, everything works through standard HTML
- With JavaScript: forms are intercepted, data is sent via
fetch(), and the page updates dynamically
The server does not need to know or care which path the client takes. It simply responds with the appropriate format based on the Accept header.
Three-Way Comparison
| Aspect | Server-Side MVC | Client-Side MVC (SPA) | Adaptable MVC |
|---|---|---|---|
| Rendering | Server only | Browser only | Both (server baseline, browser enhanced) |
| Response format | HTML | JSON | HTML or JSON (based on Accept header) |
| JavaScript required | No | Yes | No (works without, enhanced with) |
| SEO | Excellent | Poor | Excellent |
| Interactivity | Low (page reloads) | High (instant updates) | Progressive (basic without JS, rich with JS) |
| Complexity | Low | Medium | Medium (two response paths in controller) |
| Accessibility | Best (standard HTML) | Requires extra effort | Best (falls back to standard HTML) |
When to Use Each Approach
- Server-Side MVC — when simplicity and SEO matter most. Content sites, blogs, admin panels, form-heavy workflows. Works everywhere, no JavaScript bundle required.
- Client-Side MVC (SPA) — when rich interactivity is essential and SEO is not a primary concern. Real-time dashboards, collaborative tools, chat apps, desktop-like experiences.
- Adaptable MVC — when you want the best of both worlds. The baseline experience works without JavaScript (accessible, SEO-friendly), but JavaScript enhances it with smooth transitions and partial updates. This is the progressive enhancement philosophy applied to application architecture.