傳統 Web 開發:伺服器端渲染與 MVC 架構
更新日期: 2025 年 3 月 4 日
本文為 Web 架構進化史 系列文,第 1 篇:
在現代前端框架(如 React、Vue)盛行的時代,為什麼還要理解「伺服器端渲染」和「MVC 架構」?
答案很簡單:這些技術是 Web 開發的基石。
它們幫助我們理解「前後端分離」的初衷,並能更好地體會現代架構的優勢。
本文將以 Python 的 Django 框架為例,帶你走進傳統 Web 開發的世界,並探討它的核心設計與挑戰。
什麼是伺服器端渲染(Server-Side Rendering, SSR)?
伺服器端渲染(SSR)是一種 由伺服器生成完整 HTML 頁面 的技術。
當使用者在瀏覽器輸入網址並按下 Enter 時,伺服器會接收到請求,處理後端邏輯,並動態產生對應的 HTML 頁面,然後將完整的頁面回傳給瀏覽器顯示。
這種方式的關鍵特點是:在伺服器端完成 HTML 的渲染,然後再將完整的頁面發送給用戶端(瀏覽器)。
用戶端無需額外執行 JavaScript 來填充內容,因此 首屏載入速度較快,特別適合搜尋引擎優化(SEO)。
補充:渲染的兩種方式(SSR 與 CSR)
渲染(Rendering)可以在不同的地方進行,主要分為伺服器端渲染(SSR,Server-Side Rendering)和瀏覽器端渲染(CSR,Client-Side Rendering)。
伺服器端渲染意思是:伺服器會在接收到請求後,執行後端的邏輯(比如從資料庫取得內容),然後直接生成完整的 HTML 頁面,並將這個完整的 HTML 傳送給瀏覽器。
這樣一來,瀏覽器不需要額外執行 JavaScript 來組裝頁面,而是直接顯示伺服器傳來的內容,因此載入速度較快,也更有利於搜尋引擎優化(SEO)。
相對的,瀏覽器端渲染(CSR,Client-Side Rendering)則是由伺服器傳送一個基本的 HTML 結構,然後讓 JavaScript 在使用者的瀏覽器內執行,動態填充內容並渲染畫面。
這種方式通常用於單頁應用(SPA,Single-Page Application),但因為初始畫面需要等待 JavaScript 執行後才能顯示,所以首屏載入時間較長,對 SEO 也比較不友好。
簡單來說:
- SSR(伺服器端渲染):伺服器負責處理並產生完整 HTML,瀏覽器直接顯示。
- CSR(瀏覽器端渲染):伺服器只提供基本 HTML,JavaScript 在瀏覽器內動態填充內容。
瀏覽器與伺服器的「對話模式」
當用戶在瀏覽器中輸入網址後,瀏覽器會向伺服器發送 HTTP 請求,伺服器的處理流程如下:
- 處理請求:伺服器收到請求後,執行後端程式,例如從資料庫撈取商品資訊或使用 API 取得相關數據。
- 生成完整的 HTML:伺服器會根據請求的內容,將數據填充到 HTML 模板中,產生完整的 HTML 頁面。
- 回傳 HTML 給瀏覽器:瀏覽器接收到 HTML 內容後,立即解析並顯示頁面,使用者可以直接看到完整的網頁內容,而不需要等待 JavaScript 執行來載入資料。
伺服器端渲染的優勢
- 快速的首屏顯示:由於 HTML 是伺服器直接生成的,因此瀏覽器只需解析並渲染即可,不需要額外的 JavaScript 處理,這對於 低性能裝置 或 網路不佳 的環境非常有幫助。
- 對 SEO 友善:搜尋引擎爬蟲可以直接讀取完整的 HTML 內容,提高網頁被索引的機率,適合部落格、新聞網站、電子商務網站等需要良好 SEO 的應用。
- 與傳統技術兼容:SSR 的概念早在 Web 發展初期就已經存在,許多後端技術如 PHP、JSP、ASP.NET 都使用 SSR。
早期技術範例:PHP、JSP
在 2000 年代初期,許多網站開發者使用 PHP、JSP、ASP.NET 等技術來處理伺服器端渲染。
這些技術允許開發者直接在 HTML 中嵌入後端程式邏輯,例如:
PHP 範例(混合 HTML 與程式碼)
<html>
<body>
<?php
// 從資料庫取得產品清單
$products = get_products_from_database();
// 動態生成 HTML
foreach ($products as $product) {
echo "<div>{$product['name']}</div>";
}
?>
</body>
</html>
這種方式的優點是簡單直覺,開發者可以輕鬆地將資料插入 HTML。
但也有一些缺點:
- 程式碼與視圖混雜:業務邏輯與 HTML 結構混在一起,當專案變得龐大時,維護變得困難。
- 難以重用與測試:程式碼可讀性較差,缺乏模組化,導致團隊開發時較難分工合作。
由於這些問題,MVC(Model-View-Controller)架構 應運而生,將程式碼拆分成 模型(Model)、視圖(View) 和 控制器(Controller),提高程式的可維護性和可擴充性。
MVC 架構:解耦的關鍵設計
伺服器端渲染(SSR) 一直是提升網頁載入速度與 SEO 表現的重要技術。
這種方式簡單直覺,但當專案規模變大時,程式碼往往變得難以維護,因為業務邏輯與視圖層混雜在一起,導致開發難以擴展。
為了解決這些問題,MVC(Model-View-Controller)架構 應運而生。
MVC 是一種廣泛使用的設計模式,透過將應用程式拆分為不同的職責層級,有效分離數據管理、業務邏輯與視圖呈現,提升可維護性與可擴展性。
這種架構不僅讓後端開發更加模組化,也讓前後端分工更加清晰,使開發團隊能夠更高效地協作。
MVC 架構主要由以下三個部分組成,每個部分各司其職,共同協作完成請求的處理與頁面的呈現:
Model(模型)——管理資料與業務邏輯
- 負責應用程式的數據管理,通常與資料庫交互,例如讀取、更新或刪除資料。
- 包含業務邏輯,如驗證數據、計算統計結果等。
- 舉例來說,在電商網站中,
Product
(產品)模型可能會包含名稱、價格、庫存等資訊,並提供方法來計算折扣價格或檢查庫存狀態。
View(視圖)——負責呈現介面
- View 的主要工作是顯示資料,通常透過 HTML、CSS 和 JavaScript 來生成畫面,讓使用者能夠與應用程式互動。
- View 本身不處理業務邏輯,它只關心如何將數據呈現給使用者。
- 例如,一個電商網站的產品頁面 View 可能會顯示所有產品的列表,並根據 Model 提供的數據來動態生成內容。
Controller(控制器)——負責處理請求並協調 Model 與 View
- Controller 是應用程式的中樞,負責處理用戶請求,並決定要返回什麼內容給使用者。
- 當使用者在網站上點擊按鈕或提交表單時,請求會被 Controller 接收,Controller 會決定如何處理該請求,例如:
- 解析請求內容(如獲取表單輸入的數據)。
- 呼叫 Model 來讀取或更新資料庫。
- 根據 Model 的結果選擇適當的 View,並將資料傳遞給 View,最終回應使用者。
- 例如,在電商網站中,當使用者點擊「加入購物車」按鈕時,Controller 會接收這個請求,更新購物車數據,然後回傳一個更新後的頁面或 JSON 資料。
MVC 的工作流程範例
假設有一個電商網站,當使用者請求 /products
頁面時,MVC 的運作流程如下:
- 瀏覽器發送請求到
/products
,請求所有產品的列表。 - Controller 接收到請求,向 Model 查詢所有產品的資料。
- Model 從資料庫取得產品列表,並將結果回傳給 Controller。
- Controller 取得資料後,將其傳遞給對應的 View,讓 View 負責顯示產品內容。
- View 產生 HTML 頁面,並回傳給瀏覽器,讓使用者看到產品列表。
這樣的分層設計使得每個部分的責任明確,有助於維護與擴展。
例如,開發者可以修改 View 的 UI 而不影響 Model,也可以變更 Model 的數據結構而不影響 View,這大大提升了開發的靈活性。
Django 的 MTV 模式:MVC 的變形
Django 採用了一種與 MVC 相似但略有不同的架構,稱為 MTV(Model-Template-View)模式。
本質上,MTV 與 MVC 的概念相同,但 Django 根據其框架特性對命名進行了調整:
在傳統的 MVC 架構中,View(視圖) 主要負責呈現資料,但 Django 更強調 Template(模板) 的角色。
因為它內建了一個功能強大的模板引擎,能夠讓開發者透過 {{ variable }} 語法來動態渲染 HTML。
傳統 MVC 架構中的 View(視圖)通常不包含 {{ variable }}
這類模板語法,因為它的主要職責是負責數據的顯示,但不一定要使用模板語言來渲染內容。
在許多傳統 MVC 框架(如 Spring MVC、Ruby on Rails、ASP.NET MVC)中,View 可能是:
- 純 HTML + CSS(由前端工程師負責設計,後端透過 Controller 傳遞數據)
- 前端模板引擎(如 ERB、Thymeleaf、Razor)
- 前端框架(React、Vue)(現代開發模式下,後端只返回 JSON,前端自行渲染)
Django 為何將 View 改名為 Template?
Django 之所以將 MVC 的 View 改名為 Template,是因為 Django 的視圖層通常透過 Django Template 語言(DTL) 來渲染頁面。
而 DTL 的語法正是 {{ variable }}
這類標記,例如:
<p>產品名稱:{{ product.name }}</p>
這種模板語法與傳統 MVC 的 View 不太一樣,因此 Django 特意將這個部分命名為 Template,以強調它是純粹用來顯示數據的模板,而非負責請求處理的視圖層。
Django 的 View 更像 MVC 的 Controller
在 Django 框架中,views.py
中的 View 負責:
- 接收瀏覽器請求
- 處理業務邏輯(如存取資料庫)
- 將數據傳遞給 Template
這其實與傳統 MVC 架構中的 Controller(控制器) 非常相似,因此 Django 的 View 與 MVC 的 View 概念不同,更偏向 Controller 的角色。
對比 Django MTV 與傳統 MVC
架構 | Django MTV | 傳統 MVC | 角色說明 |
---|---|---|---|
數據管理層 | Model | Model | 定義資料結構,處理業務邏輯 |
介面呈現層 | Template | View | Django 使用模板語法,而傳統 MVC 的 View 可能是純 HTML 或前端框架 |
請求處理層 | View | Controller | Django 的 View 負責處理請求、調用 Model、回應前端 |
Django MTV 架構範例
以下是一個簡單的 Django 應用程式,展示如何使用 MTV 模式來顯示產品列表:
Model(定義資料結構)
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.IntegerField()
View(處理業務邏輯)
from django.shortcuts import render
from .models import Product
def product_list(request):
products = Product.objects.all() # 從 Model 取資料
return render(request, 'products.html', {'products': products})
Template(渲染 HTML 頁面)
<html>
<body>
{% for product in products %}
<div>{{ product.name }} - ${{ product.price }}</div>
{% endfor %}
</body>
</html>
傳統架構的痛點:為什麼需要前後端分離?
雖然 MVC 架構 改善了早期伺服器端渲染(SSR)中程式碼混雜、可讀性低、難以維護的問題,但在 Web 應用變得越來越複雜時,MVC 仍然面臨一些挑戰。
這些問題促使開發者尋求新的解決方案,最終演變出「前後端分離」的開發模式。
耦合性高的問題
在傳統 MVC 架構 中,前端的 HTML 模板(Template) 由後端控制。
例如在 Django 框架中,render()
函式會從後端 Model 取得資料,填充到 Template,然後將完整的 HTML 頁面返回給瀏覽器。
這種模式雖然簡單直覺,但在現代 Web 開發中卻帶來了許多限制,尤其是:
每次修改 Template 都需要重新部署伺服器
由於 HTML 模板與後端程式碼緊密綁定,即使只是簡單的 UI 修改,也必須經過完整的後端部署流程。
例如:
- 設計師希望調整 按鈕顏色、字體大小、版面間距,但這些 UI 變更需要修改 Django 的模板檔案(如
base.html
)。 - 由於這些模板檔案存在於後端程式中,前端無法直接修改,他們需要請求後端工程師幫忙變更,甚至重新部署整個伺服器。
- 在開發環境中,前端需要搭建完整的 Django 環境才能測試 UI,這大幅增加了開發與測試的成本。
👉 影響: 開發流程變慢,簡單的 UI 調整需要經過後端的參與,降低了前端開發的靈活度。
補充:哪種修改需要重新佈署?
事實上,在某些情境下,的確有不需要重新部署後端程式就能修改前端畫面的方法,但這取決於專案的架構與部署方式。
讓我們拆解來看:
需要重新部署的情況
如果HTML 模板(Template)與後端程式碼存放在一起,例如 Django 專案的
templates/
目錄內,通常修改後會需要重新部署,原因包括:
- 模板可能會被快取(Caching)
- 某些伺服器(如 Nginx、Gunicorn)可能會快取已渲染的 HTML,避免頻繁讀取檔案,因此修改後不一定會立即生效,可能需要重啟伺服器。
- 模板與後端邏輯緊密綁定
- 如果 HTML 依賴 Django View 或 Model 的變數,例如
{{ product.name }}
,修改模板的同時可能也會影響後端邏輯,因此通常會包含在完整的部署流程中。- 伺服器架構與部署機制
- 在傳統架構中,整個專案會被打包並部署到伺服器,修改任何一部分都可能需要重新啟動應用程式。
👉 結論:在這類情境下,修改前端模板通常需要重新部署整個後端。
不需要重新部署的情況
如果系統採用了前後端分離架構,則可以在不影響後端的情況下修改前端畫面,以下是幾種常見的方法:
✅ 方法 1:前端使用獨立的靜態檔案伺服器
- 做法:
- 如果前端的 HTML、CSS、JavaScript 是靜態檔案,並部署在 CDN 或獨立的前端伺服器(如 Nginx),那麼修改前端畫面只需要更新靜態檔案,而無需重新部署後端應用。
- 適用情境:
- 這種方式常見於 Vue.js / React / Angular + Django REST API 的架構,前端畫面與後端 API 完全獨立。
範例:
假設你的專案架構如下:backend/ # Django API 伺服器 frontend/ # Vue.js 或 React 應用
如果前端畫面需要修改(例如更改按鈕顏色),你只需要更新
frontend/
的 CSS 檔案,然後重新部署前端,而後端的 Django API 完全不受影響。👉 結果:前端修改畫面後,只需要讓瀏覽器重新讀取新的 CSS/JS 檔案,無需重啟後端應用。
✅ 方法 2:透過 CMS(內容管理系統)動態修改模板
- 做法:
- 如果網站是基於 CMS(如 Django CMS、WordPress、Drupal),那麼許多 UI 內容(如 HTML 片段、CSS、圖片)其實是存放在資料庫或後台管理系統內,而不是硬編碼在 Django 的
templates/
目錄中。- 這樣一來,設計師或行銷人員可以透過 CMS 後台修改頁面內容,而不需要動到 Django 原始碼,也不需要重新部署伺服器。
範例:
假設你有一個 Django CMS 網站,行銷團隊希望修改首頁的 Banner 圖片與標題。
- 進入 Django CMS 後台,找到對應的頁面內容。
- 修改圖片與標題,儲存變更。
- 使用者重新整理頁面後,新的內容即時生效,完全不影響後端部署。
👉 結果:前端內容由 CMS 控制,可動態調整,而無需修改 Django 程式碼或重新部署伺服器。
✅ 方法 3:使用 Django 的「模板載入器(Template Loader)」動態加載 HTML
- 做法:
- Django 允許透過
django.template.loaders.cached.Loader
來控制模板載入方式,某些設定(如django.template.loaders.filesystem.Loader
)允許 Django 直接從檔案系統載入模板,而不是快取在記憶體中。- 這樣一來,如果修改了 HTML 檔案,伺服器可以立即載入新版本,而不需要重新啟動應用程式。
設定方式(在
settings.py
):TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / "templates"], 'APP_DIRS': True, 'OPTIONS': { 'loaders': [ ('django.template.loaders.filesystem.Loader', []), ], }, }, ]
這樣 Django 會直接從
templates/
目錄中載入最新的 HTML,不會使用快取,因此修改模板後可以立即生效。👉 結果:只要修改
templates/
內的 HTML,Django 會立即載入新版本,而無需重啟伺服器。總結:何時需要重新部署?
情境 是否需要重新部署後端? 說明 Django 內建模板(傳統 MVC) ✅ 需要 修改 templates/ 內的 HTML,通常需要重新啟動伺服器,尤其是在生產環境中。 前後端分離(Vue / React) ❌ 不需要 只需更新前端靜態資源(CSS、JS),後端 API 無需變更。 使用 CMS(Django CMS、WordPress) ❌ 不需要 透過後台管理系統動態修改內容,不影響後端程式碼。 Django 啟用動態模板載入 ❌ 不需要 修改 HTML 後,Django 可立即讀取新版本,無需重啟應用。
難以實現動態 UI(即時交互)
傳統 MVC 架構的 HTML 是由伺服器端一次性渲染完成的靜態頁面。
如果想要在頁面上新增動態功能,例如:
- 即時搜尋建議(使用者輸入關鍵字時即時顯示匹配結果)。
- 即時更新購物車數量(點擊「加入購物車」按鈕後,不需刷新頁面)。
- 無刷新切換內容(點擊分頁按鈕時,不重新載入整個頁面,而是僅更新部分內容)。
在傳統 MVC 架構中,這些需求通常會遇到以下挑戰:
- 頁面每次更新都需要重新載入整個 HTML,造成不必要的流量與效能浪費。
- 每次與伺服器交互,都需要完整回應一個 HTML 頁面,而不是返回 JSON 讓前端動態渲染。
- 難以與 JavaScript 框架(如 React、Vue)整合,因為前端無法完全控制頁面內容,而是依賴後端模板輸出。
範例:
假設你在開發一個 Django 電商網站,當使用者輸入「iPhone」進行商品搜尋時,希望頁面可以即時顯示匹配的商品列表。
- 傳統 MVC 做法:
- 使用者輸入「iPhone」並按下 Enter。
- 瀏覽器發送請求到後端,後端執行資料庫查詢,並回傳一個完整的 HTML 頁面。
- 瀏覽器重新載入該頁面,顯示搜尋結果。
- 前後端分離的做法(例如 Django REST Framework + Vue.js):
- 使用者輸入「iPhone」,前端使用 JavaScript 監聽輸入事件。
- 瀏覽器發送 AJAX 請求至後端 API(如
/api/products?query=iphone
)。 - 後端回傳 JSON 格式的搜尋結果,前端動態渲染 UI,而無需重新載入整個頁面。
👉 影響: 傳統 MVC 無法有效支援即時互動,使得開發具有動態 UI 的 Web 應用變得困難。
團隊協作衝突,降低開發效率
在 Django 等傳統 MVC 框架中,前端工程師需要學習 Django Template 語法,例如:
{% for product in products %}
<div>{{ product.name }}</div>
{% endfor %}
這雖然可以讓後端更容易控制頁面內容,但也帶來了以下問題:
前端工程師無法獨立開發
- 必須在後端環境下才能測試 UI:
- 由於 Django 的 Template 語法與 HTML 混合,前端無法單獨開發 UI,而是必須依賴後端提供的模板環境。
- 這意味著,前端工程師在調整樣式或測試互動效果時,需要搭建完整的 Django 開發環境,甚至要學會 Python 基本語法才能順利測試。
- 前端無法完全控制頁面渲染方式:
- 傳統 MVC 由後端控制 HTML 輸出,因此前端開發者無法靈活決定 UI 呈現方式。
- 例如,前端希望使用 Vue.js 來渲染產品列表,但 Django 的模板系統已經綁定了 HTML 結構,導致 Vue.js 無法順利接管渲染。
👉 影響: 開發流程變得緩慢,前端無法獨立測試 UI,增加了不必要的依賴。
前端設計修改可能影響後端邏輯
由於 Django Template 直接與後端 View 綁定,這會導致以下問題:
- 改動一個小地方,可能影響整個後端邏輯
- 例如,設計師希望調整「產品名稱」的顯示方式,但這個欄位在 Django Template 中是透過
{{ product.name }}
來顯示的。 - 如果開發人員錯誤地修改了
product.name
的變數名稱,可能會導致後端 View 無法正確傳遞數據,進而影響整個頁面顯示。
- 例如,設計師希望調整「產品名稱」的顯示方式,但這個欄位在 Django Template 中是透過
- 不同團隊的修改可能產生衝突
- 在大型專案中,前端工程師、後端工程師、設計師 可能會同時修改相同的 Django Template 檔案,這容易導致程式碼衝突,增加開發難度。
👉 影響: 傳統 MVC 架構讓前後端緊密耦合,使得團隊協作變得更加困難,尤其是在大型專案中。
維護困難的場景舉例
在傳統 MVC 架構下,一些需求看似簡單,但實際開發時卻變得繁瑣且難以維護。例如:
案例 1:在手機版頁面隱藏價格欄位
假設你有一個電商網站,現在老闆要求「在手機版上隱藏產品價格欄位,但桌面版仍然顯示」。
在傳統 MVC 架構中的挑戰
- 必須修改後端的 Template:
- 你可能需要在 Django Template 中加上
{% if is_mobile %}…{% endif %}
來控制顯示與隱藏。
- 你可能需要在 Django Template 中加上
- 無法完全交給前端處理:
- 由於 HTML 由後端產生,前端工程師無法直接用 CSS 或 JavaScript 來處理這個需求,必須依賴後端提供變數(如
is_mobile=True
)。
- 由於 HTML 由後端產生,前端工程師無法直接用 CSS 或 JavaScript 來處理這個需求,必須依賴後端提供變數(如
- 影響其他功能:
- 如果這段程式碼影響了其他使用場景(如平板版),你可能還需要額外調整邏輯,增加開發與測試成本。
在前後端分離架構下,這個問題可以輕鬆解決:前端可以使用 CSS 媒體查詢(media query) 或 JavaScript 來根據螢幕尺寸調整顯示內容,無需後端介入。
案例 2:新增 API 供 Android App 使用
假設你的公司計畫開發 Android 應用程式,需要讓 App 獲取相同的產品資料。
在傳統 MVC 架構中的挑戰
- 後端返回的 HTML 無法直接使用:
- 傳統 MVC 的後端主要返回 完整的 HTML 頁面,但行動 App 只需要純粹的 JSON 資料。
- 可能需要重寫邏輯:
- 你可能需要額外撰寫一個 API(例如
GET /api/products
),這與GET /products
(Web 版)邏輯類似,但需要重新設計與實作。
- 你可能需要額外撰寫一個 API(例如
- 開發成本上升:
- 你可能需要重構現有的 Django View,讓它能夠同時支持 HTML 和 JSON 兩種回應格式,增加開發與測試時間。
在前後端分離架構下,這個問題變得簡單:後端只需要提供 RESTful API(JSON 格式),前端(Web 或 App)各自負責顯示數據,減少重複開發的工作量。
效能的瓶頸
伺服器負載高
- 在傳統 MVC 中,每次請求都需要執行後端渲染,即使只是更新小部分內容,伺服器仍然需要產生整個 HTML 頁面。
- 當網站訪問量變大時,伺服器的負擔會隨著請求數量指數級上升,導致回應延遲,甚至影響整體效能。
在前後端分離架構下,前端可以使用 Vue.js / React 這類框架來處理 UI 渲染,後端只需要返回 JSON 數據,大幅減少伺服器的負擔。
無法發揮瀏覽器效能
- 傳統 MVC 架構將所有計算集中在伺服器,但現代瀏覽器的運算能力其實已經很強大(例如 V8 引擎能夠高效執行 JavaScript)。
- 由於 MVC 的 HTML 直接由伺服器回傳,這代表瀏覽器無法參與渲染,浪費了前端設備的運算能力。
在前後端分離架構下,前端可以透過 JavaScript 進行部分計算與渲染(例如透過 AJAX 獲取資料並更新 UI),減少伺服器壓力,提升使用者體驗。
結論:前後端分離的必要性
傳統 MVC 架構在 Web 發展早期是一種有效的解決方案,但隨著需求變得更複雜,逐漸顯現出耦合性高、維護困難、效能瓶頸等問題。
「前後端分離」正是針對這些問題提出的改進方案,它讓:
✅ 前端開發者可以獨立工作,專注於 UI 與互動設計。
✅ 後端工程師專注於提供 API,提升開發效率與可維護性。
✅ 前端可以利用 JavaScript 框架進行動態渲染,減輕伺服器負擔。
這正是為什麼現在許多 Web 應用(如 React、Vue.js + Django REST Framework)都採用前後端分離架構,而不再使用傳統 MVC 來處理整個應用。 🚀