傳統 Web 開發:伺服器端渲染與 MVC 架構

更新日期: 2025 年 3 月 4 日

在現代前端框架(如 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 請求,伺服器的處理流程如下:

  1. 處理請求:伺服器收到請求後,執行後端程式,例如從資料庫撈取商品資訊或使用 API 取得相關數據。
  2. 生成完整的 HTML:伺服器會根據請求的內容,將數據填充到 HTML 模板中,產生完整的 HTML 頁面。
  3. 回傳 HTML 給瀏覽器:瀏覽器接收到 HTML 內容後,立即解析並顯示頁面,使用者可以直接看到完整的網頁內容,而不需要等待 JavaScript 執行來載入資料。

伺服器端渲染的優勢

  • 快速的首屏顯示:由於 HTML 是伺服器直接生成的,因此瀏覽器只需解析並渲染即可,不需要額外的 JavaScript 處理,這對於 低性能裝置網路不佳 的環境非常有幫助。
  • 對 SEO 友善:搜尋引擎爬蟲可以直接讀取完整的 HTML 內容,提高網頁被索引的機率,適合部落格、新聞網站、電子商務網站等需要良好 SEO 的應用。
  • 與傳統技術兼容:SSR 的概念早在 Web 發展初期就已經存在,許多後端技術如 PHP、JSP、ASP.NET 都使用 SSR。

早期技術範例:PHP、JSP

在 2000 年代初期,許多網站開發者使用 PHPJSPASP.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 會決定如何處理該請求,例如:
    1. 解析請求內容(如獲取表單輸入的數據)。
    2. 呼叫 Model 來讀取或更新資料庫。
    3. 根據 Model 的結果選擇適當的 View,並將資料傳遞給 View,最終回應使用者。
  • 例如,在電商網站中,當使用者點擊「加入購物車」按鈕時,Controller 會接收這個請求,更新購物車數據,然後回傳一個更新後的頁面或 JSON 資料。

MVC 的工作流程範例

假設有一個電商網站,當使用者請求 /products 頁面時,MVC 的運作流程如下:

  1. 瀏覽器發送請求/products,請求所有產品的列表。
  2. Controller 接收到請求,向 Model 查詢所有產品的資料。
  3. Model 從資料庫取得產品列表,並將結果回傳給 Controller。
  4. Controller 取得資料後,將其傳遞給對應的 View,讓 View 負責顯示產品內容。
  5. 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 負責:

  1. 接收瀏覽器請求
  2. 處理業務邏輯(如存取資料庫)
  3. 將數據傳遞給 Template

這其實與傳統 MVC 架構中的 Controller(控制器) 非常相似,因此 Django 的 View 與 MVC 的 View 概念不同,更偏向 Controller 的角色

對比 Django MTV 與傳統 MVC

架構Django MTV傳統 MVC角色說明
數據管理層ModelModel定義資料結構,處理業務邏輯
介面呈現層TemplateViewDjango 使用模板語法,而傳統 MVC 的 View 可能是純 HTML 或前端框架
請求處理層ViewControllerDjango 的 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 圖片與標題。

  1. 進入 Django CMS 後台,找到對應的頁面內容。
  2. 修改圖片與標題,儲存變更。
  3. 使用者重新整理頁面後,新的內容即時生效,完全不影響後端部署。

👉 結果:前端內容由 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 做法
    1. 使用者輸入「iPhone」並按下 Enter。
    2. 瀏覽器發送請求到後端,後端執行資料庫查詢,並回傳一個完整的 HTML 頁面。
    3. 瀏覽器重新載入該頁面,顯示搜尋結果。
  • 前後端分離的做法(例如 Django REST Framework + Vue.js):
    1. 使用者輸入「iPhone」,前端使用 JavaScript 監聽輸入事件。
    2. 瀏覽器發送 AJAX 請求至後端 API(如 /api/products?query=iphone)。
    3. 後端回傳 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 檔案,這容易導致程式碼衝突,增加開發難度。

👉 影響: 傳統 MVC 架構讓前後端緊密耦合,使得團隊協作變得更加困難,尤其是在大型專案中。

維護困難的場景舉例

在傳統 MVC 架構下,一些需求看似簡單,但實際開發時卻變得繁瑣且難以維護。例如:

案例 1:在手機版頁面隱藏價格欄位

假設你有一個電商網站,現在老闆要求「在手機版上隱藏產品價格欄位,但桌面版仍然顯示」。

在傳統 MVC 架構中的挑戰
  1. 必須修改後端的 Template
    • 你可能需要在 Django Template 中加上 {% if is_mobile %}…{% endif %} 來控制顯示與隱藏。
  2. 無法完全交給前端處理
    • 由於 HTML 由後端產生,前端工程師無法直接用 CSS 或 JavaScript 來處理這個需求,必須依賴後端提供變數(如 is_mobile=True)。
  3. 影響其他功能
    • 如果這段程式碼影響了其他使用場景(如平板版),你可能還需要額外調整邏輯,增加開發與測試成本。

在前後端分離架構下,這個問題可以輕鬆解決:前端可以使用 CSS 媒體查詢(media query) 或 JavaScript 來根據螢幕尺寸調整顯示內容,無需後端介入。

案例 2:新增 API 供 Android App 使用

假設你的公司計畫開發 Android 應用程式,需要讓 App 獲取相同的產品資料。

在傳統 MVC 架構中的挑戰
  1. 後端返回的 HTML 無法直接使用
    • 傳統 MVC 的後端主要返回 完整的 HTML 頁面,但行動 App 只需要純粹的 JSON 資料。
  2. 可能需要重寫邏輯
    • 你可能需要額外撰寫一個 API(例如 GET /api/products),這與 GET /products(Web 版)邏輯類似,但需要重新設計與實作
  3. 開發成本上升
    • 你可能需要重構現有的 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 來處理整個應用。 🚀

Similar Posts