Một năm dùng Dioxus fullstack trong môi trường production: Điều hay và điều đau đầu

Công nghệ27 tháng 10, 2026·9 phút đọc

Sau một năm đưa Dioxus fullstack vào vận hành thực tế, tác giả nhận ra phần code Rust lại là phần nhàm chán nhất, còn CI mới là nơi khiến anh mất nhiều thời gian và tiền bạc nhất. Bài viết phân tích những lợi ích của việc dùng một ngôn ngữ duy nhất cho cả frontend lẫn backend, đồng thời chia sẻ thẳng thắn về bài toán thời gian biên dịch và hướng đi tiếp theo với framework Topcoat.

Một năm dùng Dioxus fullstack trong môi trường production: Điều hay và điều đau đầu

Một năm dùng Dioxus fullstack trong production: Điều hay và điều đau đầu

Sau một năm đưa Dioxus fullstack vào vận hành thực tế, tác giả nhận ra phần code Rust lại là phần "nhàm chán" nhất trong công việc kinh doanh của mình. Còn CI mới là nơi "kịch tính" nhất — theo nghĩa tệ nhất: một bản build release mất tới 90 phút trên runner do GitHub cung cấp.

Bài viết là bản tổng kết trung thực về cái giá phải trả khi chọn Rust cho một sản phẩm phần mềm nhỏ, và những gì đáng giá cùng những gì khiến người viết phải suy nghĩ lại.

Bối cảnh: một codebase Rust làm ba việc

SeggWat là công cụ thu thập phản hồi người dùng (in-app feedback) cho các sản phẩm SaaS. Bảng điều khiển của nó là một ứng dụng Dioxus 0.7 fullstack nằm trong một Cargo workspace: một crate backend chứa các server function, một crate xác thực, và khoảng mười crate giao diện cho insights, feedback, ideas, boards, widgets và settings. Tổng cộng có mười hai crate phụ thuộc vào Dioxus, toàn bộ chạy trên PostgreSQL. Sản phẩm infra.page cũng dùng chung ngăn xếp công nghệ này.

Khái niệm "fullstack" ở đây nghĩa là một codebase Rust duy nhất đảm nhận ba vai trò: render phía server, chạy các server function như backend, và biên dịch client sang WebAssembly để trang trở nên tương tác được.

Những điều hay

Một ngôn ngữ xuyên suốt

Truy vấn cơ sở dữ liệu, quy tắc nghiệp vụ, server function, nút bấm — tất cả đều là Rust. Một toolchain, một trình định dạng code, một bộ thói quen, một chỗ để tìm khi có sự cố.

Nghe có vẻ nhỏ nhặt cho đến khi bạn là người sáng lập đơn độc phải xoay xở với nhiều sản phẩm cùng lúc. Tác giả không cần "bộ não frontend" và "bộ não backend" riêng biệt phải khởi động lại mỗi lần chuyển việc. Mở workspace lên và làm tiếp.

Dùng chung kiểu dữ liệu ở cả hai phía

Đây là điều mà tác giả cho biết sẽ nhớ nhất nếu phải bỏ.

Một struct được định nghĩa một lần. Tầng cơ sở dữ liệu trả về nó, server function gửi nó đi, component render nó. Khi đổi tên một trường, cargo check sẽ liệt kê ra mọi chỗ bị hỏng, kể cả giao diện. Không cần tạo lại đặc tả OpenAPI, không có interface TypeScript nào bị lệch pha với backend.

Lợi ích thật sự không nằm ở việc viết code nhanh hơn, mà ở chỗ có thể refactor mà không sợ hãi, vào đêm muộn, khi không có ai review giúp.

Cánh cửa sang nền tảng khác vẫn mở

Dioxus có thể nhắm tới desktop và mobile, bao gồm cả iOS, từ cùng một mô hình component. Hiện tại chưa sản phẩm nào cần ứng dụng native, nhưng việc biết mình có thể phát hành một ứng dụng như vậy mà không phải viết lại từ đầu là yếu tố quan trọng khi quyết định dồn một năm công sức vào đâu.

Production nhàm chán — theo nghĩa tốt

Một khi đã biên dịch được, phần lớn thời gian nó chỉ chạy. Cả một nhóm lỗi kiểu "undefined is not a function" không bao giờ tới tay người dùng, nên những lỗi lọt qua được thường là lỗi logic — và đó là nơi tác giả muốn dành sự tập trung của mình.

Những điều đau đầu

Thời gian biên dịch

Đây là phần mà những người cuồng nhiệt với Rust thường bỏ qua.

Bảng điều khiển phải biên dịch hai lần: một lần cho server và một lần sang wasm32 cho trình duyệt. Mười hai crate Dioxus cộng thêm một giao diện nặng về macro cộng lại thành một con số lớn. Trên máy cá nhân thì vẫn chấp nhận được, nhưng CI là câu chuyện khác:

  • Riêng bản build WASM mất khoảng 22 phút trên 6 nhân — đó chỉ là một crate lá: bảng điều khiển biên dịch cho trình duyệt.
  • Một image release mất khoảng 90 phút trên runner 2 nhân do GitHub cung cấp. Trên VPS runner tự dựng thì khoảng 40 phút — và đó đã là bản "tốt".
  • Clippy bị OOM-kill: lệnh cargo clippy --workspace --all-targets trên runner 7 GB bị giết với mã thoát 143 và không có chẩn đoán nào. Cách khắc phục là thêm swapfile 10 GB kèm CARGO_BUILD_JOBS=1 để giảm đỉnh bộ nhớ. Nói cách khác, trình linter cần swap mới chạy nổi.

Đến một lúc, tác giả quyết định không build image sau mỗi lần push lên nhánh main nữa. Giờ việc build chỉ diễn ra khi gắn tag release hoặc kích hoạt thủ công, vì 90 phút bị tính phí cho mỗi lần push là cách tiêu tiền quá ngớ ngẩn.

Những gì đã thử

  • Cache với Swatinem/rust-cache: có ích, nhưng có bẫy. Một lần chạy workflow chỉ khôi phục được cache từ chính nhánh của nó hoặc nhánh mặc định, và GitHub xóa cache không dùng sau 7 ngày. Khi ngừng build trên main, các bản build release cứ khởi động từ con số không. Cách xử lý gần như buồn cười: sau mỗi lần nâng cấp dependency, phải kích hoạt build thủ công trên main chỉ để giữ cache luôn "nóng".
  • Build và test trong cùng một lần chạy để các bài test dùng lại artifact đã biên dịch thay vì biên dịch lại toàn bộ lần hai.
  • Linker mold: tốt, nhưng khâu liên kết chưa bao giờ là nút thắt. Biên dịch mới là.
  • Runner tự dựng cho các bản build nặng: đây là thắng lợi lớn nhất, giảm từ 90 phút xuống 40 phút. Đổi lại, phải tự duy trì một máy chủ build.

Tất cả đều có ích. Nhưng không cái nào thay đổi được phép toán cơ bản: hai mục tiêu biên dịch, một cây phụ thuộc đồ sộ và rất nhiều macro đồng nghĩa với rất nhiều thời gian biên dịch.

"Toàn bộ bằng Rust" đi kèm dấu sao

Logic nghiệp vụ là Rust hết. Nhưng phần rìa thì không.

Trang insights của SeggWat hiển thị biểu đồ, và tác giả không tìm được thư viện vẽ biểu đồ Rust nào đủ tin cậy cho production. Vì vậy biểu đồ dùng Chart.js, tải từ CDN và bọc trong khoảng 340 dòng JavaScript mà Dioxus gọi qua dioxus-use-js. Phần styling dùng Tailwind, nên CI còn phải cài Bun và chạy Tailwind CLI trước cả khi Rust bắt đầu biên dịch.

Tác giả thấy điều này hợp lý và thực dụng. Nhưng nếu bạn chọn Dioxus với mục đích thoát khỏi JavaScript, hãy biết mình đang ký vào điều gì: Rust, cộng một ít keo JS, cộng một toolchain JS trong pipeline.

Có làm lại không?

Mọi người thường hỏi: "Cái gì giúp tôi ship nhanh nhất?". Câu hỏi hay hơn là: "Cái gì giúp tôi tiếp tục ship mà không ghét cuộc đời mình?".

Với backend, câu trả lời vẫn là Rust, và không có đối thủ gần.

Với frontend, tác giả đã đổi ý đôi chút. Nếu xây dựng thứ gì đó thực sự giống một ứng dụng — nhiều trạng thái phía client, hỗ trợ offline hay một ứng dụng mobile thật sự — anh vẫn sẽ chọn Dioxus. Nhưng phần lớn sản phẩm anh làm chỉ là bảng điều khiển: biểu mẫu, bảng, vài biểu đồ và vài nút bật tắt. Với loại đó, anh đang phải trả "thuế WASM" 22 phút mỗi bản release cho khả năng tương tác mà mình hiếm khi dùng tới.

Bước tiếp theo: Topcoat

Dự án tới sẽ dùng Topcoat, một framework fullstack mới của tổ chức tokio-rs, được công bố vào tháng 7.

Topcoat đặt cược theo hướng ngược lại với Dioxus. Mọi thứ render ở server, và component là các hàm bất đồng bộ có thể truy vấn cơ sở dữ liệu trực tiếp. Để có tương tác, bạn viết các biểu thức $(...) bằng Rust thông thường, vẫn được kiểm tra kiểu. Topcoat đánh giá chúng ở server cho lần render đầu tiên và đồng thời dịch chúng sang JavaScript để chạy lại tức thì trên trình duyệt. Không có gói WASM, không có mục tiêu biên dịch thứ hai.

Trên lý thuyết, Topcoat giữ được những gì tác giả yêu ở Dioxus (một ngôn ngữ, kiểu dùng chung, không có lớp API) và loại bỏ phần đau nhất — bản build WASM 22 phút.

Cần nói rõ: đây là một canh bạc, không phải kết quả đã kiểm chứng. README của Topcoat tự nhận là giai đoạn đầu và còn thử nghiệm, với các thay đổi phá vỡ tương thích được dự báo trước. Tác giả chấp nhận từ bỏ lựa chọn đa nền tảng, và chưa đo được bản build nào.

Nếu bản build release giảm từ 40 phút xuống 10 phút, bạn sẽ được nghe về điều đó. Nếu không, bạn cũng sẽ được nghe.

Góc nhìn cho lập trình viên Việt Nam

Câu chuyện này khá gần gũi với các nhóm phát triển phần mềm tại Việt Nam, đặc biệt là các startup nhỏ hoặc đội ngũ vài người phải tự lo cả hạ tầng.

  • Chi phí CI là chi phí thật. Với các đội ở Việt Nam thường dùng runner miễn phí hoặc hạn chế ngân sách, 90 phút bị tính phí mỗi lần push là con số đáng cân nhắc. Dựng runner tự host trên VPS trong nước hoặc khu vực gần có thể vừa giảm thời gian vừa giảm chi phí.
  • Rust đáng giá ở backend, nhưng frontend cần cân nhắc theo nhu cầu. Nếu sản phẩm chủ yếu là dashboard nội bộ, chi phí biên dịch WASM có thể không đáng so với lợi ích tương tác.
  • Không có ngăn xếp nào "toàn bộ" theo nghĩa tuyệt đối. Việc chấp nhận một ít JavaScript ở phần rìa là lựa chọn thực dụng, miễn là bạn biết trước và tính nó vào pipeline.

Bài học lớn nhất có thể tóm gọn: chọn công nghệ dựa trên câu hỏi "mình có thể duy trì và tiếp tục phát triển nó trong nhiều năm không", chứ không phải "công nghệ này nghe có vẻ thuần khiết nhất không".

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗