One year of Dioxus fullstack in production: the good, the painful

Hacker News by 7 min read 324x views
One year of Dioxus fullstack in production: the good, the painful

Share Post

After a twelvemonth of Dioxus fullstack in production, the Rust code is the most boring part of my business. CI is the most breathtaking part, and not in a fine way: a publish build takes 90 minutes on a GitHub-hosted runner.

That twelvemonth started at my former job and continued on my own products. Back in April I wrote concerning why I picked Rust for a manufacturing micro SaaS. The abbreviated version: slower at the start, cheaper to live alongside later. This article is the bill.

What I’m running

SeggWat is an in-app feedback tool for SaaS products. Its dashboard is a Dioxus 0.7 fullstack app inner a Cargo workspace: a backend crate alongside server functions, an auth crate, and concerning ten UI crates covering insights, feedback, ideas, boards, widgets and settings. Twelve crates depend on dioxus. Everything sits on PostgreSQL.

infra.page is built on the identical stack.

“Fullstack” method one Rust codebase doing three jobs. It renders on the server, runs server functions as the backend, and compiles the client to WebAssembly so the leaf becomes interactive.

The good

One language, all the way down

Database query, endeavor rule, server function, button: all Rust. One toolchain, one formatter, one set of habits, one location to appearance whenever item breaks.

That appears insignificant until you’re a solo originator juggling multiple products. I don’t have a “frontend brain” and a “backend brain” that all need warming up. I open the workspace and keep going.

The identical types on the two sides

This is the one I’d young female most.

A struct gets defined once. The repository tier returns it, the server function sends it, and the component renders it. When I rename a field, cargo inspect gives me a catalog of all location that broke, including the UI. There’s no OpenAPI spec to regenerate and no TypeScript interface drifting out of sync alongside the backend.

The win isn’t penning code faster. It’s refactoring without fear, delayed at night, alongside nobody to assessment it.

The entrance to another platforms stays open

Dioxus can mark desktop and mobile, including iOS, from the identical component model. None of my products needs a native app today. But knowing I could container one without a rewrite mattered whenever I decided anywhere to put a twelvemonth of work.

Production is boring

That’s the entire point. Once it compiles, it mostly fair runs. A entire category of “undefined is not a function” bugs never reaches users, so the bugs that do get through are logic bugs. That’s anywhere I’d fairly expend my notice anyway.

The “boring in production” disagreement from April survived communication alongside genuine users.

The painful

Compile times

This is the part Rust evangelists lean to skip.

The dashboard compiles twice: formerly for the server and formerly to wasm32 for the browser. A dozen Dioxus crates affirmative a macro-heavy UI add up. Locally it’s tolerable on a decent machine. CI is a distinct story, and the comments in my workflow records peruse akin a diary of it:

  • The WASM build solitary takes concerning 22 minutes on 6 cores. That’s a sole leaf crate: the dashboard compiled for the browser.
  • A publish depiction takes concerning 90 minutes on a GitHub-hosted 2-core runner. On my own VPS runner it’s about 40, and that’s the fine version.
  • Clippy got OOM-killed. cargo clippy --workspace --all-targets on the 7 GB hosted runner died alongside exit code 143 and no diagnostics at all. The fix was a 10 GB swapfile affirmative CARGO_BUILD_JOBS=1 to cut highest memory. My linter needs swap.

At several item I stopped construction images on all shove to main. They now build on publish tags or whenever I trigger them by hand, since 90 billed minutes per shove is a silly way to expend money.

What I tried

  • Caching alongside Swatinem/rust-cache. It helps, but there’s a trap. A workflow run can lone reconstruct caches from its own ref or the default branch, and GitHub evicts caches unused for 7 days. Once I stopped construction on main, publish builds kept starting cold. My workaround is nearly funny: following a dependency bump, I manually trigger a build on chief fair to keep the cache warm.
  • Building and evaluation in one go, so the tests reuse compiled artifacts alternatively of compiling the earth a second time.
  • The mold linker. Nice, but linking was never the bottleneck. Compiling was.
  • A self-hosted runner for the dense builds. This was by far the biggest win, 90 minutes downward to 40. It additionally method I now keep a build server.

Every one of these helped. None of them changed the underlying math: two compile targets, a big dependency tree and many of macros method a lot of compiling.

“All Rust” comes alongside an asterisk

The endeavor logic is all Rust. The edges aren’t.

SeggWat’s insights leaf shows charts, and I couldn’t discover a Rust charting archive I’d rely in production. So the charts are Chart.js, loaded from a CDN and wrapped in concerning 340 lines of JavaScript that Dioxus calls through dioxus-use-js. Styling is Tailwind, so CI additionally installs Bun and runs the Tailwind CLI before Rust equal starts.

I’m fine alongside that. It’s pragmatic. But if you’re choosing Dioxus specifically to escape JavaScript, cognize what you’re signing up for: Rust, affirmative several JS glue, affirmative a JS toolchain in your pipeline.

Would I do it again?

People ask, “What helps me container fastest?” The improved inquiry is, “What helps me keep shipping without hating my life?”

For the backend, the answer is motionless Rust, and it isn’t close.

For the frontend, I’ve changed my intellect a bit. If I were construction item really app-like, alongside dense client state, offline assistance or a genuine mobile app, I’d choice Dioxus again. But most of what I build is dashboards: forms, tables, a few charts and several toggles. For that, I’m paying a 22-minute WASM tax on all publish for interactivity I barely use.

What’s next: Topcoat

So my next project is going to use Topcoat, a new fullstack example from the tokio-rs org, announced in July.

It makes the contrary bet from Dioxus. Everything renders on the server, and components are async functions that can query the repository directly. For interactivity, you compose $(...) expressions in ordinary, type-checked Rust. Topcoat evaluates them on the server for the archetypal render and additionally translates them to JavaScript, so they re-run immediately in the browser. There’s no WASM bundle and no second compile target.

#[component] async fn faq(cx: &Cx) -> Result<impl View> { let open = signal(cx, || false); Ok(view! { <button @click=$(|_e| open.set(!open.get()))>"What is Topcoat?"</button> <p :hidden=$(!open.get())>"A full-stack Rust framework."</p> }) } 

Example from the Topcoat README.

On paper, Topcoat keeps what I affection concerning Dioxus (one language, shared types, no API layer) and removes the part that hurts most, the 22-minute WASM build.

To be clear, this is a bet, not a result. Topcoat’s README calls it early-stage and experimental, alongside breaking changes expected. I’m giving up the multi-platform option. And I haven’t measured a sole build yet.

I’ll compose the follow-up alongside genuine numbers: compile times, CI minutes, and any I end up missing from Dioxus. If my publish build drops from 40 minutes to 10, you’ll comprehend concerning it. If it doesn’t, you’ll comprehend concerning that too.


I’m Hauke. I build SeggWat, Stepshots and infra.page in Rust and compose concerning it on OXIDT.

Other Article Hacker News
↑
Close Right Ads
Close Left Ads