Introduction
The shift from traditional server-side MVC to modern frontend frameworks (Vue.js, React, Angular) represents one of the most significant architectural changes in web development. Understanding what changes — and what trade-offs you’re making — is essential for choosing the right approach for your project.
Traditional MVC (Rails, Django, Laravel)
In server-side MVC, the server handles everything:
Browser → HTTP Request → del (DB) → View (Template) → HTML → Browser
Model: Business logic and data access (ActiveRecord, Django ORM) View: Templates that render HTML (ERB, Jinja2, Blade) Controller: Handles requests, coordinates model and view
# Rails MVC example
class ArticlesController < ApplicationController
def index
@articles = Article.published.order(created_at: :desc).limit(20)
# @articles is passed to the view template
end
end
<!-- app/views/articles/index.html.erb -->
<% @articles.each do |article| %>
<h2><%= article.title %></h2>
<p><%= article.excerpt %></p>
<% end %>
The server renders complete HTML pages. The browser receives finished HTML and displays it.
Advantages of Server-Side MVC
- Simple mental model — one data layer, one rendering location
- SEO-friendly — search engines get complete HTML
- Fast initial page load — no JavaScript needed to render content
- Simpler state management — state lives on the server
- Turbolinks — can feel like an SPA without the complexity
MVVM with Frontend Frameworks (Vue.js, React)
MVVM (Model-View-ViewModel) separates the UI logic into a ViewModel that binds data to the view:
Browser ←→ Vue/React Component (ViewModel) ←→ API (JSON) ←→ Server (Model)
The browser now handles rendering. The server becomes a JSON API.
// Vue.js component — data, template, and logic in one file
<template>
<div>
<h2 v-for="article in articles" :key="article.id">
{{ article.title }}
r |
| State management | Simple | Complex |
| Team structure | Full-stack | Frontend + Backend |
## Resources
- [Vue.js Documentation](https://vuejs.org/)
- [React Documentation](https://react.dev/)
- [Rails Hotwire](https://hotwired.dev/) — SPA-like experience without a JS framework
- [Inertia.js](https://inertiajs.com/) — bridge between server-side and client-side
- [Flux Architecture](https://facebook.github.io/flux/) — Facebook's data flow pattern
onents
class ArticlesController < ApplicationController
def index
render inertia: 'Articles/Index', props: {
articles: Article.published.as_json(only: [:id, :title, :excerpt])
}
end
end
Summary
| Aspect | Server-Side MVC | Frontend Framework (SPA) |
|---|---|---|
| Rendering | Server | Browser |
| Data layers | 1 (server) | 2 (server + client) |
| SEO | Excellent | Requires SSR/SSG |
| Initial load | Fast | Slower |
| Interactivity | Limited | Excellent |
| Complexity | Lower | Highefline capability (PWA) |
- Mobile app-like experience is required
- You have separate frontend and backend teams
Hybrid Approach (Best of Both)
Modern frameworks blur the line:
- Rails + Hotwire (Turbo + Stimulus): Server-rendered with SPA-like interactivity, no JavaScript framework needed
- Next.js / Nuxt.js: SSR + client-side hydration — SEO-friendly SPAs
- Inertia.js: Use Vue/React components with server-side routing (no API needed)
# Rails + Inertia.js — server routing, Vue compwer (download + execute JS bundle)
Navigation: Fast (only fetch data, update DOM)
When to Use Each
Use Server-Side MVC When
- Content is primarily static or read-heavy
- SEO is critical (blogs, marketing sites, e-commerce)
- Team is small and wants simplicity
- You want fast initial page loads
- The application is primarily CRUD operations
Use Frontend Framework (SPA) When
- Highly interactive UI (dashboards, editors, real-time apps)
- Complex state that changes frequently
- You need ofe
- Prerendering: Render pages with a headless browser at build time
6. Performance Trade-offs
Initial load: SPAs are slower — must download JavaScript bundle before rendering content.
Subsequent navigation: SPAs are faster — no full page reloads, only data fetches.
Time to Interactive (TTI): SPAs can be slower if the JavaScript bundle is large.
Traditional MVC:
First visit: Fast (server renders HTML)
Navigation: Slower (full page reload each time)
SPA:
First visit: Slo Server-side rendering for SEO
### 5. SEO Challenges
Single-page applications render content with JavaScript. Search engine crawlers may not execute JavaScript, resulting in empty pages:
SPA with frontend framework: Two routing systems — server routes for the API, client-side router for navigation:
// Vue Router — client-side routing
const routes = [
{ path: '/', component: Home },
{ path: '/articles', component: ArticleList },
{ path: '/articles/:id', component: ArticleDetail },
]
```javascript
Navigation happens without page reloads, but you need to handle:
- Deep linking (sharing URLs)
- Browser back/forward
-articles = articles },
SET_LOADING(state, loading) { state.loading = loading },
SET_ERROR(state, error) { state.error = error }
},
actions: {
async fetchArticles({ commit }) {
commit('SET_LOADING', true)
try {
const response = await fetch('/api/articles')
commit('SET_ARTICLES', await response.json())
} catch (err) {
commit('SET_ERROR', err.message)
} finally {
commit('SET_LOADING', false)
}
}
}
})
4. Routing
**introduces complexity:
- State management: Where does shared state live? (Vuex, Redux, Pinia, Zustand)
- Data fetching: When to fetch, how to handle loading/error states
- Optimistic updates: Update UI before server confirms
- Cache invalidation: When is local state stale?
// Vuex store — centralized frontend state management
const store = createStore({
state: {
articles: [],
loading: false,
error: null
},
mutations: {
SET_ARTICLES(state, articles) { state. ['edit', 'delete']
}
</script>
<style scoped>
.address-card { border: 1px solid #ddd; padding: 1rem; }
</style>
Use it anywhere:
<AddressCard
v-for="addr in addresses"
:key="addr.id"
:address="addr"
@edit="handleEdit"
@delete="handleDelete"
/>
3. Two Data Layers
The biggest architectural change: you now have two data layers.
Backend: PostgreSQL → Rails/Django → JSON API
Frontend: Vue/React state → Component tree → DOM
Data must be synchronized between them. This // fetch results, update DOM manually… });
### 2. Component Reusability
Frontend frameworks enable true component reuse — HTML, CSS, and JavaScript in a single, portable unit:
{{ address.fullAddress }}
## Key Differences
### 1. Data Binding
**Traditional MVC:** Data flows one way — server renders data into HTML, browser displays it. User interactions require a new request.
**MVVM:** Two-way data binding — changes in the UI automatically update the data model, and vice versa:
```java </h2>
Comments