Kiến trúc frontend
Chủ đề Dành cho thành viên
Bài đọc thử: 3/3 (đây là bài miễn phí cuối của bạn)
· Xem gói thành viên
Bài toán
Frontend bắt đầu là vài trang render ở server; lớn lên thành ứng dụng có trạng thái, nhiều team cùng sửa, và mỗi tính năng thêm vào là thêm byte phải tải. Ba trục lặp lại trong các nguồn: render ở đâu (server, client, hay lai), tài nguyên gửi đi bao nhiêu và lúc nào (kích thước bundle, round-trip, cache), và nhiều team deploy độc lập thế nào khi tất cả cùng nằm trong một trang.
Trang này giữ phần kiến trúc và hạ tầng phía frontend. Công cụ build và CI ở CI và build ở quy mô lớn; đổi ngôn ngữ kiểu (Flow → TypeScript, strict null checks) ở Quản lý fleet và migrate code hàng loạt. App mobile (React Native, Flutter, SwiftUI, server-driven UI) cũng nằm ở đây vì gặp cùng ba trục, cộng thêm ràng buộc của thiết bị yếu và phiên bản client sống lâu.
Diễn tiến theo thời gian
- 2004→2020 · Facebook (Meta): từ PHP render ở server, lớp công nghệ chồng lên dần làm site chậm và khó bảo trì; viết lại thành app React phía client. CSS nguyên tử sinh lúc build (CSS trang chủ −80%), JS chia ba tầng theo thời điểm cần, code cho A/B test và loại dữ liệu chỉ gửi khi cần, ngân sách JS theo team, server chuẩn bị dữ liệu ngay khi nhận request (Relay),
@stream/@defer, prefetch theo route map. Meta 2020 - 2012 · Zillow: biểu đồ đi từ Flash sang Dojo (khoảng 200 KB JS) rồi sang vẽ phía server thành PNG + lớp tương tác 20 KB; người dùng tương tác nhiều hơn khoảng 7% trong A/B test. Zillow 2012
- 2012→2017 · Zillow: tài nguyên tĩnh tách khỏi monolith Java vào repo static-all (2012); bốn năm sau hơn 30 team cùng sửa, một gói deploy chung. Tách thành 60 bundle theo tính năng với phụ thuộc một chiều; loader JS theo tên module nên dời file không phải sửa tham chiếu. Zillow 2017
- 2014→2016 · Zillow: HotPads viết lại front-end JSP + jQuery 10 năm tuổi thành app React isomorphic trên Node.js gọi API back-end như app mobile; SSR lấy dữ liệu song song và stream
<head>trước; page view mỗi session +50%. Zillow 2016 - 2015→2019 · Zerodha: app giao dịch Kite: Android native (2015) và iOS chỉ là webview → React Native cho iOS (2017); trên Android tầm trung, tick giá qua
setStatekhông mượt và thư viện navigation lỗi nên vẫn phải giữ hai codebase → viết lại bằng Flutter khi còn alpha (2018, ngang tính năng sau khoảng 3 tháng nhờ codegen), thay app Android đầu 2019, iOS sáu tháng sau. Đội mobile hai người. Zerodha 2022 - 2015 · Zillow: performance budget trên SpeedCurve cho mọi trang chính bắt được trang mobile nặng lên 13,2 MB sau một bản phát hành; nguyên nhân là bucket control của một A/B test tải ngầm ảnh cho tính năng chưa bật. Engagement và SpeedIndex không đổi vì ảnh thừa tải sau phần trên màn hình. Zillow 2015
- 2015 · Zillow: trang chủ: đo bằng mốc User Timing khi ảnh hero vẽ xong; style inline, thẻ img ẩn để trình duyệt ưu tiên tải ảnh, nén ảnh, tải phần dưới màn hình bất đồng bộ; mốc hero nhanh hơn 67%. Zillow 2015
- 2016→2026 · Allegro: hơn 120 team, hơn 1.200 component trong một service render (Opbox); release train thủ công hai lần mỗi ngày thành nút thắt. Thử component dạng FaaS (bỏ vì chi phí mạng), rồi registry phiên bản: phiên bản mới mặc định tắt, team tự bật theo môi trường (0% → 5% → 95%), service vẫn tự deploy ba lần mỗi ngày qua canary; khoảng 240 ngày công tiết kiệm mỗi tháng. Allegro 2026
- 2016→2017 · Zillow: kích thước bundle webpack được đo ở mỗi push và có ngưỡng làm vỡ build; soát dependency định kỳ (moment-timezone chiếm 10% một app chỉ vì một chỗ dùng). Zillow 2017
- 2017→2020 · GitHub: SDK
@octokit/rest: định nghĩa endpoint sinh mỗi đêm từ tài liệu API thay vì viết tay; tách thành core + plugin để người dùng trình duyệt chọn phần cần (kích thước bundle); TypeScript thay plugin kiểm tham số lúc chạy; v17 còn 10 dòng ghép core với ba plugin, chạy test của v16 trên v17 làm lưới an toàn. GitHub 2020 - 2017 · Etsy: chất lượng JPEG chọn theo từng ảnh thay hằng số 85 (tìm nhị phân theo điểm SSIM lúc upload, thêm khoảng 500 ms): chất lượng trung bình khoảng 73, dung lượng ảnh −25–30%. Sau khi js/css/font đã cache, khoảng 92% byte của trang listing là ảnh. Etsy 2017
- 2018 · Bumble: lỗi client của web và app Badoo/Bumble (web phát hành 1–2 lần/ngày, app một lần/tuần) gom về một hệ tự host; bản tổng hợp lỗi theo phiên bản là một bước của mỗi lần phát hành, so hai phiên bản để thấy lỗi mới và hồi quy. Lỗi client đến theo cơn (SDK Facebook iOS 10/7/2020 làm nhiều app sập) nên API nhận chỉ kiểm định dạng rồi đẩy vào hàng đợi. Bài 12/2020. Bumble 2020
- 2019 · Tinder: remote config (Levers) cho iOS, Android, Web: client khai báo manifest mọi Lever và kiểu, bỏ giá trị sai kiểu, có giá trị dự phòng khi backend đã bỏ Lever, chính sách khoá theo session; mỗi lần đọc Lever tự ghi metric. Client không revert được như server nên cấu hình từ xa là cơ chế rollback chính. Tinder 2019
- 2019 · V8: code cache của Chrome gắn với URL script và cả file: tách thư viện ổn định khỏi logic hay đổi, giới hạn tần suất deploy, giữ đường chạy tất định để cache nóng; V8 nhắc các heuristic này sẽ đổi. V8 (Google) 2019
- 2019 · Zillow: chat trong Rental Manager: server GraphQL ghép schema theo tính năng (mỗi tính năng một thư mục schema/resolver/test) từ ba module, để nhiều team góp vào một API front-end. Zillow 2019
- 2020 · Zillow: ghép panorama 3D Home chuyển từ cloud (70 MB mỗi panorama, chờ vài phút) lên iPhone: 540 frame, chỉ giữ dải dọc, nén JPEG khi đang chạy để nằm trong vài trăm MB bộ nhớ, lưu xuống đĩa và ghép tiếp khi app bị đưa vào nền. Zillow 2020
- 2021 · Wix: từ render ở client (rẻ cho backend) sang SSR (tốt cho SEO, nhưng mỗi request sinh HTML riêng); tách dữ liệu riêng của khách khỏi HTML để cache được ở trình duyệt và CDN; Brotli, HTTP/2, CDN động. Wix 2021
- 2022→2025 · Discord: Android sang React Native (2022), rồi tối ưu theo power user: ảo hoá danh sách server (−14% bộ nhớ lúc khởi động với hơn 100 server), recycle trong chat list (tới −60% frame chậm), emoji picker Android viết lại native (chấp nhận hai codebase cho một màn hình), bỏ FlashList quay về list tự viết, FastestList trên RecyclerView với placeholder do phía native vẽ. Discord 2025
- 2022 · Grammarly: gợi ý từ backend là Delta cùng định dạng với chỉnh sửa (OT), client rebase gợi ý theo chỉnh sửa mà không chờ backend; giao thức chưa phải đổi khi gợi ý lớn lên từ một từ tới cả đoạn. Áp nhiều gợi ý: compose thành một Delta (sau khi rebase từng cái lên phần đã gộp) để UI không đứng. Grammarly 2022
- 2022 · WhatsApp: web app được phục vụ lại mỗi lần tải nên server có thể đưa mã khác cho một người; Code Verify so hash toàn bộ JavaScript của WhatsApp Web với hash do Cloudflare giữ, cập nhật theo mỗi lần deploy. WhatsApp 2022
- 2023 · Zillow: app Premier Agent chuyển query GraphQL sang Kotlin Multiplatform dùng chung cho iOS và Android; version theo commit hash gây nhầm, đổi sang tự tăng semver mỗi lần merge; hết bug do query lệch giữa hai nền tảng. Zillow 2023
- 2024→2025 · Notion: custom emoji theo mẫu mention có sẵn (con trỏ tới backend, cập nhật mọi nơi); picker dùng virtualization; tải toàn bộ lúc khởi động thay vì dựng edge cache, đổi lại giới hạn 500 emoji mỗi workspace cho tới khi có edge cache. Notion 2025
- 2024→2025 · Mercari: cho khách Đài Loan, Hồng Kông, đầu tiên dùng lại web Nhật (Next.js Pages Router, hơn 20 fetch cho trang chi tiết, nội dung đổi theo IP nên SEO theo vùng bất khả), rồi dựng app mới: Next.js App Router + RSC, một BFF chung cho web/iOS/Android bằng Protobuf + ConnectRPC thay GraphQL (theo tác giả: 6 tháng thay vì khoảng 18 tháng của lần viết lại trước), subdomain theo vùng với middleware rewrite sang
/[region]/[locale], hreflang; data access layer duy nhất gọi BFF, một lời gọi mỗi màn hình. Web monorepo pnpm + Turborepo với tầng app/feature/domain/core, i18n theo module qua webpack import context và bundler alias. Mercari 2025 Mercari 2025 Mercari 2025 - 2024 · Zillow: StreetEasy Koios: luồng hỏi đáp mô tả bằng JSON trên server, web và app vẽ theo; sửa luồng không cần phát hành app, trước đó mỗi trải nghiệm mất vài tháng tới vài quý. Zillow 2024
- 2025 · Mercari (mobile): sau các lần fork theo nước, React Native (US), Flutter (Hallo), app Global làm native trong repo của app Nhật đổi thành monorepo nhiều sản phẩm (iOS Bazel hơn 900 module; Android kiểm đồ thị phụ thuộc trên CI): dùng lại nền (design system, client dịch vụ nội bộ) nhưng không dùng lại module tính năng; theo bài, chọn native vì nền móng của Flutter không dùng lại được. Android một build cho mọi vùng, khác biệt do BFF và remote config. Phát hành hằng tuần: trunk-based + feature flag, nộp Apple review song song với kiểm chứng, phased release. Mercari 2025 Mercari 2025 Mercari 2025
- 2025 · HRT: GUI vận hành nội bộ cập nhật dưới một giây, người dùng mở nhiều tab và dashboard cùng nạp dữ liệu giống nhau. HRTWorker bọc SharedWorker: một class chạy ở chế độ client (tab) hoặc oracle (worker dùng chung), giao thức conversation stream qua
AsyncGenerator, decorator biến lời gọi method thành message; không phải đổi stack hay bắt backend biết về frontend. Hudson River Trading 2025 - 2026 · Railway: app chủ yếu chạy phía client nên bỏ Next.js (build hơn 10 phút, layout chắp vá trên Pages Router) sang Vite + TanStack; hai PR: gỡ phụ thuộc framework trước rồi đổi framework; build dưới 2 phút; chunk băm theo nội dung, phần lớn traffic phục vụ từ edge. Railway 2026
- 2026 · Yelp: đổi bundler Webpack → Rspack bằng cấu hình adapter, bật dần theo trang; build tích hợp −52%. Yelp 2026
- 2026 · Inngest: đội nhỏ kiêm nhiều việc thấy Next.js App Router/RSC quá nặng về nhận thức; lùi khỏi RSC, nâng cấp, thử Turbopack hai lần vẫn load trang dev 10–12 giây. Prototype ba framework, chọn TanStack Start, chuyển một lần (Dev Server khoảng một tuần, Dashboard vài tuần của một engineer có AI chép mẫu); load trang dev còn 2–3 giây; bù PR khổng lồ bằng UAT, một lần rollback. Inngest 2026
- 2026 · Cloudflare (nhà cung cấp): viết lại bề mặt API Next.js trên Vite (vinext) thay vì dịch ngược output build như OpenNext, trong chưa tới một tuần bởi một người điều khiển AI (khoảng 1.100 USD token), với test chuyển từ repo Next.js làm đặc tả; build nhanh 1,6–4,4 lần, bundle nhỏ hơn khoảng 57% trên app thử 33 route. Pre-render theo traffic thật: chỉ render trước các trang chiếm 90% lượt xem. Cloudflare 2026
- 2026 · Shopify: chuyển app Shopify (hơn 300 màn hình) từ React Native về native bằng công cụ agent Helix: checkpoint nhỏ với code cũ làm đặc tả, so ảnh bản mới với bản cũ ở cùng trạng thái bằng một model làm reviewer thiết kế (so pixel không dùng được giữa hai framework), review theo tài liệu kiến trúc. Shopify 2026
- 2026 · Yelp, 4/2026: server-driven UI dùng chung design system: một định nghĩa JSON mỗi component sinh thư viện Python, Kotlin, Swift, TypeScript; client gửi spec + version (
android@23.0), backend chọn version component gửi được và gọi hàmmigrateđể backport khi có breaking change. Yelp 2026 - 2026 · Doximity, 9/2026: newsfeed iOS từ UIKit sang SwiftUI không có ngày chuyển: section (lá) chuyển trước, nằm trong cell UIKit (
UIHostingConfiguration); rồi đảo cầu nối để SwiftUI giữ phần cuộn, section UIKit còn lại được bọc; bật cho một phần nhỏ người dùng, gỡ giàn giáo khi ổn; benchmark cuộn p50/p95/p99 làm mốc. Làm được vì feed server-driven và logic nằm ngoài view. Doximity 2026 - 2026 · Salesforce, 9/2026: cá nhân hoá real-time trên iOS, Android, React Native, Flutter bằng một SDK (lõi native, hai framework chéo qua lớp cầu): developer gắn content zone và đăng ký thành phần native được phép một lần, marketer đổi trải nghiệm qua metadata version hoá phát qua CDN, không cần phát hành app; định danh và quyết định ở server, SDK không lưu profile thô; xem trước bằng QR với profile thật của một người. Theo Salesforce: một khách tích hợp trong hai tháng, hơn 150 triệu quyết định trong ba tuần. Salesforce 2026
Các lựa chọn và đánh đổi
| Lựa chọn | Được | Mất | Nguồn |
|---|---|---|---|
| Render ở server (PHP, SSR) | Hiển thị sớm, SEO | Mỗi request tốn server; HTML riêng khó cache | Wix 2021 |
| App phía client | Tương tác phong phú | Bundle lớn, khởi động chậm nếu không chia tầng | Meta 2020, Railway 2026 |
| HTML giống nhau cho mọi khách + endpoint dữ liệu riêng | Cache ở trình duyệt, CDN | Thêm một round-trip cho phần cá nhân | Wix 2021 |
| Một service render gom mọi component | Tập trung giám sát, hiệu năng | Deploy thành nút thắt chung | Allegro 2026 |
| Registry phiên bản, team tự bật | Deploy độc lập, lùi tức thì | Cần hệ phân phối phiên bản, alert theo chủ | Allegro 2026 |
| Framework server-first (Next.js App Router) | SSR, tối ưu sẵn | Không hợp app client-first; build chậm; directive và cache ngầm nặng với đội không chuyên frontend | Railway 2026, Inngest 2026 |
| Đổi framework một lần (big-bang) | Nhanh, không phải hạ tầng chạy song song | PR không review được, phải bù bằng UAT | Inngest 2026, Railway 2026 |
| Framework cross-platform tự vẽ (Flutter) | Một codebase; mượt với dữ liệu cập nhật liên tục trên máy tầm trung | Lỗi đồ hoạ của thiết bị làm đen cả app; tự viết plugin cho tính năng nền tảng | Zerodha 2022 |
| React Native + phần native cho màn hình nặng | Ra tính năng nhanh trên nhiều nền tảng | Ít biên hiệu năng trên Android yếu; hai codebase cho màn hình viết lại native | Discord 2025 |
| Đổi framework tăng dần qua cầu nối hai chiều | Không có ngày chuyển; rủi ro khoanh trong từng phần | Hai framework cùng sống một thời gian; sizing của view lồng phải kiểm kỹ | Doximity 2026 |
Quy tắc rút ra
- Tài nguyên frontend chỉ tăng nếu không có ngân sách: đo kích thước theo team và cảnh báo trên mỗi merge request. Meta 2020
- Khai báo phụ thuộc tĩnh (dữ liệu, code) để gửi sớm và song song, thay vì phát hiện dần khi chạy (round-trip nối tiếp). Meta 2020 Xem Batching và khấu hao round-trip.
- Tách phần riêng của từng người khỏi phần dùng chung để cache được. Wix 2021
- Chọn kiến trúc render theo hình dạng sản phẩm, không theo mặc định của hệ sinh thái. Railway 2026
- Tách "deploy code" khỏi "bật phiên bản" khi nhiều team chia nhau một trang. Allegro 2026 Xem Deploy và release an toàn.
- Thời gian phản hồi lúc dev là một chỉ số đáng đo như hiệu năng production; khi nâng cấp và đổi bundler không cứu được thì vấn đề nằm ở mô hình của framework. Inngest 2026 Railway 2026
- Ước lượng chi phí migration bằng cách chuyển thử vài route: cả Railway và Inngest đều thấy chuyển một lần rẻ hơn dựng hạ tầng chạy song song. Inngest 2026 Railway 2026
- Tối ưu client theo đuôi phân phối (máy yếu, power user), không theo trung bình; thư viện chung tiện lúc làm có thể kém ở đúng chỗ đó, và được phép quay về cái tự viết hoặc chấp nhận hai codebase cho một màn hình khi số liệu đáng. Discord 2025 Zerodha 2022
- Đổi framework UI không cần ngày chuyển nếu logic đã tách khỏi view: chuyển lá trước trong khung cũ, rồi đảo cầu nối để khung mới chứa phần cũ còn lại. Đối chiếu với quy tắc 7: Railway và Inngest (web, đội nhỏ) thấy chuyển một lần rẻ hơn, Doximity (màn hình mobile dùng nhiều nhất) chọn tăng dần. Doximity 2026 Xem Migration không downtime.
- Phần phải khớp với API thật thì sinh từ tài liệu theo lịch, và tách thư viện client để người dùng trình duyệt chỉ kéo phần cần. GitHub 2020
- Khi tài nguyên tĩnh đã được cache, ảnh là phần lớn byte còn lại; nén theo từng ảnh bằng thước đo gần mắt người thay vì một hằng số chung. Etsy 2017 Xem Chi phí hạ tầng và build vs buy.
- Phần hay đổi phải nằm ngoài binary của app: gắn điểm mở rộng (content zone, thành phần được phép) một lần, phát nội dung và targeting qua cấu hình có version; định danh và quyết định giữ ở server. Salesforce 2026 Yelp 2026
- Dùng lại nền, không dùng lại tính năng: app mới trong cùng monorepo lấy design system, client dịch vụ nội bộ, hạ tầng build; module tính năng thì viết lại dù hai app đang giống nhau, vì dùng lại không có định hướng sinh trừu tượng sai. Chọn framework theo tài sản nội bộ dùng lại được, không theo lợi thế trên giấy. Mercari 2025 Mercari 2025
- Nội dung của một URL phải tất định: suy vùng hay tiền tệ từ IP người gọi phá SEO, cache và QA; đưa vùng vào URL (subdomain) và để BFF nhận nó như tham số. Mercari 2025
Câu hỏi mở
- Chưa có nguồn đo chi phí server của SSR so với CSR theo quy mô.
- Hai nguồn rời Next.js năm 2026 (Railway, Inngest) đều là app chủ yếu chạy phía client của đội nhỏ; chưa có nguồn kể trường hợp RSC/App Router thành công ở quy mô lớn để đối chiếu.
- Micro-frontend kiểu Allegro (một service render, nhiều phiên bản trong bộ nhớ) và kiểu ghép ở trình duyệt (module federation): chưa có nguồn so sánh.
- Đổi framework một lần hay tăng dần: Railway, Inngest (web, đội nhỏ) chọn một lần; Doximity 2026 (màn hình mobile dùng nhiều nhất) chọn tăng dần qua cầu nối hai chiều; Zerodha 2018 viết lại cả app nhưng chạy song song với app cũ tới khi thay. Chưa có nguồn so hai cách trên cùng loại app.
- React Native trên Android yếu: Discord 2025 và Zerodha (2017–2018) cùng gặp; Discord hẹn xem lại phần native khi lên New Architecture, chưa có bài kể kết quả.
- RSC/App Router ở quy mô lớn: Mercari 2025 chọn App Router + RSC cho web Global và báo real user metrics tốt hơn web Nhật dù chưa tối ưu (Mercari 2025), ngược hướng với Railway, Inngest; nhưng đây là app mới vài tháng tuổi, chưa có số liệu sau khi lớn.
- Liên hệ: CI và build ở quy mô lớn (bundler, cache build), Cache và invalidation (cache HTML ở CDN), Migration không downtime (đổi framework).
Được nhắc tới trong
Chủ đề (2)
Công ty (23)
Bài tóm tắt (38)
- Zillow 2012
- Zillow 2015
- Zillow 2015
- Zillow 2016
- Zillow 2017
- Etsy 2017
- Zillow 2017
- V8 (Google) 2019
- Zillow 2019
- Tinder 2019
- Bumble 2020
- GitHub 2020
- Zillow 2020
- Meta 2020
- Wix 2021
- WhatsApp 2022
- Zerodha 2022
- Grammarly 2022
- Zillow 2023
- Zillow 2024
- Mercari 2025
- Mercari 2025
- Mercari 2025
- Mercari 2025
- Notion 2025
- Mercari 2025
- Hudson River Trading 2025
- Mercari 2025
- Discord 2025
- Salesforce 2026
- +8 nữa