Chương 2

CƠ SỞ
LÝ THUYẾT

Nền tảng lý thuyết của đề tài: MENN Stack, tại sao Next.js thay thế React thuần, các cơ chế Hybrid Rendering, hạ tầng triển khai Docker/Coolify và bộ chỉ số Core Web Vitals.

2.1 — Tổng quan về MENN Stack

MENN Stack là biến thể hiện đại của MERN Stack truyền thống, thay thế React thuần túy bằng Next.js — framework cho phép Hybrid Rendering. Đây là nền tảng kỹ thuật của toàn bộ đề tài.

MDatabase
EBackend API
NRendering Engine
NRuntime
M

MongoDB

Database

Hệ quản trị cơ sở dữ liệu NoSQL dựa trên tài liệu (document-oriented), cung cấp sự linh hoạt trong việc lưu trữ và truy xuất dữ liệu dưới dạng JSON. Schema-less — phù hợp với blog posts có cấu trúc thay đổi.

Tại sao chọn?

Không cần migration khi thêm field mới vào post. Mongoose ODM cung cấp validation, pre-save hooks và virtual fields.

Mongoose v8Document modelCoolify managedDocker internal network
E

Express.js

Backend API

Framework web tối giản cho Node.js, đóng vai trò xây dựng hệ thống RESTful API để xử lý các logic nghiệp vụ ở phía Backend: CRUD bài viết, xác thực user, upload ảnh lên Spaces CDN.

Tại sao chọn?

Middleware architecture cho phép xếp chồng: cors → auth → multer → controller. Dễ thêm route mới mà không ảnh hưởng existing logic.

REST APIJWT AuthMulter uploadMongoose integration
N

Next.js

Rendering Engine

Framework React dành cho Production. Đây là thành phần quan trọng nhất — đóng vai trò "Rendering Engine" cho phép thực hiện các chiến lược SSG, SSR, ISR và CSR trên cùng một ứng dụng.

Tại sao chọn?

Không có framework nào khác cho phép một trang dùng SSG và trang kề dùng SSR trong cùng codebase mà không cần config phức tạp.

SSG / SSR / ISR / CSRAPI Routes (BFF)Image OptimizationFile-based routing
N

Node.js

Runtime

Môi trường thực thi JavaScript phía Server — nền tảng chung cho cả Express backend và các tác vụ server-side của Next.js. Non-blocking I/O model phù hợp với ứng dụng nhiều concurrent request.

Tại sao chọn?

Dùng cùng ngôn ngữ (JavaScript) cho cả Frontend và Backend — giảm context switching, tái sử dụng utility functions và type definitions.

v20 LTSNon-blocking I/Onpm ecosystemShared JS codebase
2.2 — Tại sao Next.js thay vì React thuần túy (SPA)?

Mặc dù Next.js xây dựng trên nền React, nhưng React SPA bộc lộ nhiều hạn chế mà Next.js khắc phục triệt để trong bài toán tối ưu hiệu năng và SEO.

2.2.1 — Hạn chế của React SPA

📄

"Trang trắng" (Blank Page Problem)

635.83KB JS bundle — vượt ngưỡng 200KB của Lighthouse gấp 3 lần

Với React thuần (SPA), trình duyệt nhận một file HTML rỗng và một tệp JavaScript lớn. Người dùng phải chờ JS thực thi xong mới thấy nội dung — tăng chỉ số FCP (First Contentful Paint) lên mức "Poor" theo chuẩn Google.

<!-- HTML React SPA nhận được từ server -->
<!DOCTYPE html>
<html>
  <head>
    <script src="/assets/index-Bx3fG2kP.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <!-- Trống rỗng — không có nội dung nào -->
  </body>
</html>
🤖

Thách thức SEO

SEO score CSR ≈ 40/100 · SSG/ISR/SSR ≈ 100/100 theo Lighthouse

Googlebot thường không đợi JavaScript render xong. Khi crawl trang SPA, bot chỉ thấy <div id="root"></div> — không có title, description, hay nội dung bài viết. Kết quả: trang không được lập chỉ mục đúng cách, mất thứ hạng tìm kiếm.

// Googlebot thấy gì khi crawl CSR page:
GET /posts/bitexco-quan-1

Response HTML:
<div id="root"></div>
← Trống. Không có title, h1, p nào.

// Vs Next.js SSG/ISR:
<h1>Bitexco Financial Tower</h1>
<p>Tòa nhà biểu tượng của Quận 1...</p>
← Bot đọc được ngay, lập chỉ mục đầy đủ.
📦

Bundle Size & Third-party Overhead

~120KB dependency overhead bị loại bỏ hoàn toàn

React SPA yêu cầu cài thêm nhiều thư viện bên thứ ba: react-router-dom (routing), tanstack-query (data fetching), axios (HTTP). Mỗi thư viện thêm vào bundle — parse time tăng, TBT tăng.

// React SPA cần cài thêm:
npm install react-router-dom    // +50KB
npm install @tanstack/react-query // +40KB
npm install axios               // +30KB

// Next.js có sẵn:
// ✓ File-based routing (zero config)
// ✓ getStaticProps / getServerSideProps
// ✓ fetch() native với cache control
// ✓ API Routes (không cần axios)

2.2.2 — Ưu thế vượt trội của Next.js

🔀

Hybrid Power

Next.js không ép buộc toàn bộ ứng dụng phải theo một cơ chế render duy nhất. Mỗi trang có thể độc lập chọn chiến lược render tối ưu nhất.

PageModeLý do
/ssg/homepageSSGLanding page — data ít đổi, cần load cực nhanh
/isr/homepageISRBlog list — cập nhật định kỳ, không rebuild toàn site
/ssr/homepageSSRSearch results — data realtime theo query
/csr/homepageCSRDemo SPA — minh họa hạn chế của CSR
/admin/dashboardCSRAdmin — không cần SEO, nhiều interaction

Zero Config Optimization

Next.js tích hợp sẵn các tối ưu hóa mà React SPA phải tự cấu hình hoặc cài thêm thư viện.

Image Optimization

Tự động nén, resize và chuyển sang WebP. <Image> component tự set width/height → tránh CLS.

Automatic Code Splitting

Mỗi route có JS chunk riêng. Trang /posts/[slug] không tải JS của /admin.

Link Prefetching

<Link> component tự prefetch trang đích khi người dùng hover → chuyển trang tức thì.

Font Optimization

next/font tự host Google Fonts — loại bỏ external request, tránh FOUT (Flash of Unstyled Text).

🔒

API Routes — BFF Pattern

Next.js cho phép viết API trung gian (Backend for Frontend) ngay trong codebase frontend. Trong dự án này, API routes đóng vai trò proxy bảo mật — browser không bao giờ biết địa chỉ Express backend.

// pages/api/blog-proxy.js
// Browser gọi /api/blog-proxy
// Next.js server gọi Express backend (internal)
// Browser không bao giờ thấy INTERNAL_BACKEND_URL

export default async function handler(req, res) {
  const BACKEND = process.env.INTERNAL_BACKEND_URL;
  const data = await fetch(`${BACKEND}/api/posts`);
  res.json(await data.json());
}
2.3 — Các cơ chế Rendering trong phát triển Web hiện đại

Cốt lõi của Hybrid Rendering là khả năng kết hợp linh hoạt bốn phương thức sau trên cùng một ứng dụng Next.js:

SSG

2.3.1

Static Site Generation (SSG)

Khi nào render: Build time
📦

Ưu / Nhược điểm

Tốc độ truy cập cực nhanh — serve từ CDN
Bảo mật cao — không có server-side code lúc runtime
TTFB thấp nhất trong 4 phương pháp
Không phù hợp với dữ liệu thay đổi thường xuyên
Mọi thay đổi nội dung yêu cầu build lại toàn bộ

Phù hợp cho

Trang giới thiệu (About Us), Landing Page quảng cáo, Tài liệu hướng dẫn kỹ thuật (Docs)

Next.js hookgetStaticProps()

Request Flow

1.Build time: Next.js chạy getStaticProps()
2.Fetch data từ Express backend
3.Render HTML hoàn chỉnh → lưu file
4.Deploy: upload HTML tĩnh lên server
5.Request: Traefik serve file tĩnh ngay lập tức
6.Browser nhận HTML đầy đủ — không cần server compute
SSR

2.3.2

Server-Side Rendering (SSR)

Khi nào render: Mỗi request
🍜

Ưu / Nhược điểm

Dữ liệu luôn được cập nhật mới nhất
SEO tuyệt đối — HTML có sẵn nội dung
Phù hợp với data cá nhân hóa theo user
Tăng tải cho Server (CPU/RAM) mỗi request
TTFB cao hơn do phải đợi Server xử lý và query DB

Phù hợp cho

Trang kết quả tìm kiếm, Bảng tin mạng xã hội, Trang giá vàng/chứng khoán realtime

Next.js hookgetServerSideProps()

Request Flow

1.Request đến từ browser
2.Next.js server chạy getServerSideProps()
3.Express backend query MongoDB
4.Mongoose hydrate documents
5.Next.js render HTML với data mới
6.Browser nhận HTML đầy đủ — TTFB = thời gian xử lý trên
CSR

2.3.3

Client-Side Rendering (CSR)

Khi nào render: Sau khi JS bundle load
🛒

Ưu / Nhược điểm

Tương tác mượt mà sau khi tải — SPA experience
Giảm tải cho Server sau khi page đã load xong
Phù hợp cho UI phức tạp, nhiều state
Tốc độ tải trang đầu tiên chậm — "Trang trắng"
SEO kém — Bot khó lập chỉ mục nội dung từ JS

Phù hợp cho

Dashboard quản trị, Trang cài đặt tài khoản, Ứng dụng quản lý công việc nằm sau login

Next.js hookuseEffect() + fetch()

Request Flow

1.Request đến từ browser
2.Server trả HTML rỗng ngay lập tức
3.Browser tải JS bundle (~500KB+)
4.React parse, compile, execute bundle
5.useEffect chạy, gọi fetch()
6.State update → DOM render — Chỉ đến đây user thấy nội dung
ISR

2.3.4

Incremental Static Regeneration (ISR)

Khi nào render: Build time + background revalidate
🍖

Ưu / Nhược điểm

Kết hợp tốc độ SSG và tính cập nhật SSR
Không cần rebuild toàn bộ site khi data thay đổi
Giảm tải tối đa cho Server và Database
Có độ trễ về tính cập nhật — user đầu tiên sau hết cache có thể thấy data cũ trong khi trang đang được tái tạo

Phù hợp cho

Trang chi tiết sản phẩm thương mại điện tử, Bài viết Blog/Tin tức, Danh mục sản phẩm

Next.js hookgetStaticProps() + revalidate

Request Flow

1.Request 1: Serve HTML tĩnh đã build (nhanh như SSG)
2.Nếu quá revalidate interval → kích hoạt background regen
3.Background: fetch data mới từ Express
4.Next.js rebuild HTML mới ở background
5.Request 2+: Serve HTML mới — user không biết gì
6.On-demand: Admin tạo bài → webhook → force revalidate ngay
2.4 — Hạ tầng triển khai và Quản lý Container
🌊

2.4.1

VPS & DigitalOcean

  • Máy chủ ảo riêng (VPS) — toàn quyền kiểm soát từ hệ điều hành đến cấu hình mạng
  • Droplet SGP1 (Singapore) — latency đến HCM City < 15ms
  • Phân bổ tài nguyên tối ưu: toàn bộ stack (Frontend + Backend + DB) trên 1 Droplet $18/tháng
  • Không bị giới hạn bởi platform rules của Vercel hay Heroku
🐳

2.4.2

Docker & Coolify

  • Docker: Đóng gói ứng dụng vào Container — đảm bảo chạy đồng nhất trên mọi môi trường (Local → Staging → Production)
  • Coolify: Self-hosted PaaS — tự động hóa cấu hình Traefik, cấp phát SSL (Let's Encrypt), CI/CD từ GitHub
  • Nixpacks: Auto-detect build system — không cần viết Dockerfile thủ công
  • Zero-downtime deploy: Health check trước khi switch traffic sang container mới
2.5 — SEO và Core Web Vitals

Để đánh giá hiệu quả của Hybrid Rendering, đề tài dựa trên bộ chỉ số Core Web Vitals của Google — thước đo khách quan nhất về trải nghiệm người dùng thực tế.

LCP

Largest Contentful Paint

Thời gian hiển thị phần tử nội dung lớn nhất

Tối ưu: < 2.5s
FID

First Input Delay

Thời gian phản hồi khi người dùng tương tác lần đầu

Tối ưu: < 100ms
CLS

Cumulative Layout Shift

Độ ổn định của bố cục trang web

Tối ưu: < 0.1

Hybrid Rendering cải thiện Core Web Vitals như thế nào?

LCPSSG ~0.8s ✓CSR ~4.5s ✗

SSG/ISR có HTML + image URL sẵn → browser preload ngay. CSR phải chờ JS → fetch → render.

CLSSSG ~0.02 ✓CSR ~0.18 ✗

SSG/ISR biết dimensions trước → reserve space. CSR skeleton swap gây layout shift.

TBTSSG ~40ms ✓CSR ~380ms ✗

CSR bundle lớn hơn (inline toàn bộ components) → main thread bị block lâu hơn.

TTFBSSG ~15ms ✓SSR ~350ms

SSG serve file tĩnh từ disk. SSR phải fetch DB + render. CSR HTML rỗng nhanh nhưng FCP chậm.

Xem chi tiết tại: /docs/vitals · Đo lường thực tế tại: /seminar/experiment

Tiếp theo

Chương 3: Thực nghiệm

Xây dựng ứng dụng thực tế, đo lường và so sánh hiệu năng 4 rendering mode trên môi trường Cloud

Thực nghiệm →