Sajiron
Frontend development has long been dominated by JavaScript-based frameworks like React, Vue, and Angular. But what if you could bring the safety, speed, and performance of Rust to the frontend? With Yew, you can.
Yew is a modern Rust framework that compiles to WebAssembly and enables building reliable and performant web applications. In this post, we’ll walk through the basics of Yew, how to set up a simple app, and what makes it an exciting choice for frontend developers—especially Rustaceans. If you're coming from a JavaScript background, you'll notice familiar concepts like components, props, and state, but with the strict guarantees and performance of Rust.
Yew is a component-based framework for building interactive and dynamic web applications using Rust. It leverages WebAssembly (Wasm) to run compiled Rust code in the browser, achieving performance close to native code.
Yew takes inspiration from React, providing:
Reactive Component Model: Build UI as a tree of components that respond to state changes.
The `` Macro: Write HTML-like syntax in Rust, similar to JSX in React.
Hooks Support: Manage state and side effects cleanly with use_state
, use_effect
, and more.
Concurrency Safety: Rust’s ownership model prevents common issues like race conditions and null pointer exceptions.
Interop with JavaScript: Call JavaScript functions and manipulate the DOM directly if needed.
While JavaScript has evolved significantly, it still lacks the type safety, memory safety, and compile-time guarantees that Rust offers. Here’s why Yew and Rust are worth considering:
Memory Safety: Rust’s borrow checker ensures no null dereferences or data races.
Performance: WebAssembly executes faster than JavaScript in many cases, making Yew ideal for performance-sensitive applications.
Tooling: Leverage Cargo, crates.io, and the robust Rust ecosystem.
Unified Stack: Share code between your frontend and backend (e.g., validation logic, types).
Better Developer Experience: Rust’s compiler helps catch bugs early in development.
Let’s walk through building a basic counter app using Yew.
Step 1: Install Requirements
You’ll need to install the WebAssembly target and Trunk, a tool for building and bundling Yew apps.
rustup target add wasm32-unknown-unknown
cargo install trunk
Step 2: Create a Project
cargo new yew-app
cd yew-app
Step 3: Add Dependencies
Edit your Cargo.toml
to include the following:
[dependencies]
yew = { version = "0.21", features = ["csr"] }
wasm-bindgen = "0.2"
Step 4: Create Entry Files
Create a basic index.html
in the ./
directory:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Yew App</title>
</head>
<body>
<div id="root"></div>
<script type="module">import init from './yew_app.js'; init();</script>
</body>
</html>
Now update src/main.rs
:
use yew::prelude::*;
#[function_component(App)]
fn app() -> Html {
let counter = use_state(|| 0);
let onclick = {
let counter = counter.clone();
Callback::from(move |_| counter.set(*counter + 1))
};
html! {
<div>
<h1>{ "Hello from Yew!" }</h1>
<p>{ format!("Counter: {}", *counter) }</p>
<button {onclick}>{ "Increment" }</button>
</div>
}
}
fn main() {
yew::Renderer::<App>::new().render();
}
Step 5: Run the App
Use Trunk to start a local development server:
trunk serve
This will automatically rebuild and reload the app as you make changes.
Let’s break down what’s happening in the Yew component:
use yew::prelude::*;
#[function_component(App)]
fn app() -> Html {
let counter = use_state(|| 0);
let onclick = {
let counter = counter.clone();
Callback::from(move |_| counter.set(*counter + 1))
};
html! {
<div>
<h1>{ "Hello from Yew!" }</h1>
<p>{ format!("Counter: {}", *counter) }</p>
<button {onclick}>{ "Increment" }</button>
</div>
}
}
fn main() {
yew::Renderer::<App>::new().render();
1. use yew::prelude::*;
This imports everything commonly needed from Yew, including types, macros, and traits.
2. #[function_component(App)]
This macro defines a function component called App
, which returns HTML. It’s a clean, functional way to write components.
3. let counter = use_state(|| 0);
Initializes a reactive state variable counter
with a default value of 0. Calling counter.set(new_value)
will re-render the component.
4. let onclick = ...
This defines an event handler that increments the counter. It clones the state handle so the closure can move it safely and sets the updated value using set()
.
5. html! { ... }
Yew’s macro to generate virtual DOM nodes. It allows embedding Rust expressions inside braces {}
similar to JSX.
6. fn main()
Bootstraps the Yew app by mounting the App
component to the DOM’s #root
element.
This counter example demonstrates how state, events, and rendering all work together in Yew.
Yew’s architecture is similar to React and Elm:
Virtual DOM: Yew uses a lightweight virtual DOM to track changes and update the UI efficiently.
Components: Each UI element is a component. Components can accept props and manage their own state.
Hooks: Yew offers use_state
, use_effect
, and others to manage component behavior.
Lifecycle: Like React, Yew components have lifecycles, including create
, update
, and destroy
phases.
Interop: Yew supports JavaScript interop through wasm-bindgen
and web-sys
crates.
Note on Multithreading: WebAssembly has experimental support for multithreading in the browser using wasm threads, but it requires enabling SharedArrayBuffer
in browsers and proper configuration (e.g., setting HTTP headers like Cross-Origin-Embedder-Policy
).
While Rust supports multithreading out of the box, Yew currently runs in a single-threaded environment due to browser limitations and compatibility. However, developers can offload heavy computations to Web Workers using crates like wasm-bindgen-rayon
or custom JavaScript bindings. These allow multithreaded computations outside the main UI thread and can be integrated with Yew through messages or agents.
✅ Pros
Rust’s type system and ownership model reduce bugs and improve reliability.
Wasm enables near-native execution speed.
Component model is familiar to React developers.
Full-stack Rust unification simplifies complex projects.
Active development and growing community.
⚠️ Cons
Bundle size: WebAssembly binaries can be large, though optimizations like wasm-opt
help.
Smaller ecosystem: Fewer UI libraries, components, and examples compared to JavaScript.
Learning curve: Requires knowledge of both Rust and frontend development.
Yew isn’t for every project, but it shines in:
Performance-critical apps: e.g., dashboards, data visualizations, real-time UIs.
Apps sharing logic across frontend/backend: e.g., form validation or domain types.
Rust-centric teams: Developers already using Rust in the backend.
Embedded Web UI: Apps that run on WASI or in restricted environments.
It may not be ideal for projects requiring extensive UI libraries or heavy 3rd-party integrations (yet).
Once you’ve got the basics down, here’s what you can explore next:
Routing: Use yew-router
to enable SPA-style navigation.
API Integration: Call REST APIs using gloo-net
or reqwest
.
Global State: Implement global state management with ContextProvider
.
Styling: Integrate with Tailwind CSS or use inline styles.
Advanced Patterns: Use agents for multi-component communication.
Testing: Use tools like wasm-bindgen-test
for writing unit tests in Rust.
With Yew, you can build modern web apps with all the benefits of Rust. It's not just about replacing JavaScript—it’s about giving Rust a place in the frontend world.
Yew is still evolving, but it already supports a robust set of features that make it more than capable of powering real-world applications. If you're building something that requires reliability, speed, and safety, give Yew a try.
Learn how to structure Rust projects with packages, crates, and modules for better maintainability, reusability, and scalability.
Learn Rust async/await with futures, concurrency, and Tokio runtime. Master non-blocking programming, async streams, and efficient task execution. 🚀
Learn Rust error handling with Result, Option, ?, and popular libraries like thiserror, anyhow, and color-eyre for robust error management.
Learn fearless concurrency in Rust with threads, mutexes, async programming, and message passing for safe, efficient, and race-condition-free code.