Cú hích lúc 2 giờ sáng: Tại sao Frontend lại khiến tôi kiệt sức
Lúc đó là 2 giờ sáng, và tôi đang ngụp lặn trong một stack trace trải dài qua ba micro-frontend và bốn lớp quản lý state dư thừa. Tất cả sự phức tạp này chỉ để cập nhật một nút ‘Like’ duy nhất trên bảng điều khiển. Thư mục node_modules của tôi đã phình to lên đến 450MB, và pipeline build mất sáu phút chỉ để di chuyển vài pixel. Tệ hơn cả là những người dùng sử dụng kết nối 3G đang phải vật lộn để tải xuống một bundle JavaScript nặng 2.5MB trước khi họ có thể nhìn thấy nội dung.
Tôi nhận ra chúng ta đã quá phức tạp hóa việc gửi dữ liệu lên server. Chúng ta đang coi trình duyệt như một hệ điều hành nặng nề thay vì một trình xem tài liệu. Đêm đó, tôi đã loại bỏ mọi sự phức tạp và thử dùng HTMX. Sau khi chạy công nghệ này trong môi trường production được một năm, kết quả đã rõ ràng: thời gian tải nhanh hơn, code ít hơn và ít đau đầu hơn đáng kể. Nó đã thay đổi hoàn toàn quan điểm của tôi về lượng JavaScript mà chúng ta thực sự cần.
Bắt đầu nhanh: Từ tĩnh sang tương tác chỉ trong 5 phút
HTMX cho phép bạn truy cập AJAX, CSS Transitions, WebSockets và Server-Sent Events trực tiếp thông qua các thuộc tính HTML. Bạn không cần bước build hay một trình đóng gói (bundler) phức tạp. Hãy quên đi 500MB tài liệu phụ thuộc (dependencies); bạn chỉ cần một thẻ script duy nhất.
1. Tích hợp HTMX
Thêm đoạn mã này vào phần head của HTML. Nó chỉ nặng khoảng 12KB sau khi gzipped—chỉ bằng một phần nhỏ so với React hoặc Vue.
<script src="https://unpkg.com/[email protected]"></script>
2. Yêu cầu AJAX đầu tiên
Thay vì viết hàm fetch() và cập nhật DOM thủ công, bạn định nghĩa hành vi ngay trên chính element đó. Đây là một nút bấm sẽ tải danh sách người dùng khi được nhấp vào.
<button hx-get="/api/users"
hx-target="#user-list"
hx-swap="innerHTML">
Tải danh sách người dùng
</button>
<div id="user-list">
<!-- Danh sách người dùng sẽ hiển thị ở đây -->
</div>
Khi người dùng nhấp vào nút này, HTMX gửi một yêu cầu GET đến /api/users. Server phản hồi bằng HTML thô thay vì JSON. Sau đó, HTMX sẽ tự động hoán đổi (swap) đoạn mã đó vào thẻ div #user-list.
Cơ chế hoạt động: Tư duy lại mối quan hệ Client-Server
Hầu hết các framework hiện đại đều tuân theo một mô hình cứng nhắc: Server gửi JSON, Client phân tích JSON, Client dựng HTML, và cuối cùng, Client cập nhật DOM. HTMX loại bỏ bước trung gian. Bằng cách gửi trực tiếp các đoạn mã HTML fragment, bạn đang tuân theo triết lý cốt lõi của HATEOAS (Hypermedia as the Engine of Application State).
Các công cụ cốt lõi
- hx-get / hx-post: Những thuộc tính này định nghĩa phương thức HTTP và URL đích của bạn.
- hx-target: Chọn element nào sẽ được cập nhật. Nếu bạn bỏ trống, HTMX sẽ cập nhật chính element đã kích hoạt yêu cầu.
- hx-trigger: Kiểm soát điều gì bắt đầu yêu cầu. Đó có thể là một cú nhấp chuột (click), một sự thay đổi (change), di chuột (hover) hoặc thậm chí là một sự kiện tùy chỉnh.
- hx-swap: Xác định cách nội dung mới được đưa vào trang. Bạn có thể thay thế toàn bộ element, chỉ thay thế nội dung bên trong, hoặc chèn thêm vào một danh sách.
Quản lý State không rườm rà
Trong một ứng dụng React điển hình, bạn dành nửa thời gian để đồng bộ state cục bộ với database. Với HTMX, state nằm trên server. Nếu người dùng xóa một hàng, server sẽ xử lý việc xóa và trả về một phản hồi trống hoặc một danh sách đã cập nhật. Bạn không còn cần phải lo lắng về việc store Redux cục bộ bị mất đồng bộ với database SQL của mình. Database là nguồn chân lý duy nhất (source of truth), và HTML là sự phản chiếu trực tiếp của nó.
Ví dụ thực tế: Xây dựng tìm kiếm trực tiếp (Live Search)
Live search thường là một tính năng phức tạp yêu cầu các event listener, hàm debounce và các vòng lặp state. HTMX xử lý việc này chỉ với vài dòng code khai báo:
<input type="text" name="search"
placeholder="Tìm kiếm người dùng..."
hx-post="/search"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#search-results"
hx-indicator=".loader">
<div class="loader htmx-indicator">Đang tìm kiếm...</div>
<div id="search-results">
<!-- Kết quả cập nhật tại đây -->
</div>
Modifier delay:500ms đảm bảo bạn không làm quá tải server sau mỗi lần nhấn phím. HTMX sẽ tự động hiển thị .loader trong khi yêu cầu đang được xử lý và ẩn nó đi khi dữ liệu được trả về. Backend của bạn chỉ cần trả về một danh sách các thẻ <div> chứa kết quả tìm kiếm.
Bài học từ thực tế
Chuyển từ kiến trúc nặng về JSON sang HTMX đòi hỏi một sự thay đổi nhỏ trong chiến lược backend của bạn. Dưới đây là ba mẹo thực tế mà tôi đã đúc kết được từ môi trường production.
1. Nhận diện yêu cầu từ HTMX
Server của bạn nên đủ thông minh để phân biệt giữa việc tải toàn bộ trang và yêu cầu một đoạn fragment. Hầu hết các backend sẽ kiểm tra header HX-Request. Nếu nó tồn tại, chỉ gửi lại đoạn mã HTML fragment. Nếu không, hãy gửi toàn bộ trang bao gồm cả phần điều hướng (navigation) và footer.
2. Đừng bỏ qua bảo mật
HTMX tích hợp hoàn hảo với các tiêu chuẩn bảo mật web như bảo vệ CSRF. Nếu bạn sử dụng Django hoặc Rails, bạn có thể truyền token CSRF của mình qua một header toàn cục. Điều này đảm bảo mọi yêu cầu AJAX đều được xác thực mà không cần viết các hàm wrapper tùy chỉnh cho mỗi lần gọi fetch.
3. Stack công nghệ “Càng ít càng tốt”
HTMX không phải là sự thay thế cho tất cả mọi thứ. Đối với logic UI phức tạp tại máy khách như giao diện kéo thả hoặc bản đồ dựa trên canvas, bạn vẫn cần JavaScript. Tôi thường kết hợp HTMX với Alpine.js. Alpine xử lý các tương tác nhỏ phía client (như mở menu trên thiết bị di động), trong khi HTMX xử lý tất cả việc giao tiếp với server. Sự kết hợp này giúp codebase luôn gọn nhẹ và dễ debug.
HTMX giúp chúng ta quay lại sự đơn giản của web thời kỳ đầu mà không làm mất đi cảm giác mượt mà của một ứng dụng hiện đại. Bằng cách đưa logic trở lại server, chúng ta giảm bớt gánh nặng tư duy cho đội ngũ phát triển và giảm tiêuhtu pin trên thiết bị của người dùng. Đã đến lúc ngừng xây dựng mọi trang web như thể nó là một hệ điều hành máy tính để bàn.

