Rust has quickly become a favorite among developers for its performance, safety, and modern tooling. But did you know that Rust can also be used to build web applications? Thanks to WebAssembly (WASM) and the Yew framework, it's now possible to write frontend code in Rust that runs in the browser—offering near-native speed and robust type safety.
In this tutorial, you’ll learn how to build a simple yet powerful web app entirely in Rust using Yew, a modern framework inspired by React. Yew leverages WebAssembly to compile Rust to the web, making it ideal for applications where performance and correctness are critical.
We'll walk through everything step-by-step—from setting up the toolchain and writing components to bundling and deploying your app. By the end, you'll have a fully functional Rust-based web app running in the browser.
What You’ll Build
To keep things beginner-friendly and focused, we'll create a basic counter web app that demonstrates:
-
Setting up a Yew project with Trunk
-
Using components and state in Yew
-
Handling events (e.g., button clicks)
-
Compiling and deploying to the web
Let’s get started by setting up the environment.
Prerequisites
Before we begin building our Rust + WebAssembly web app with Yew, make sure you have the following tools and dependencies installed on your system:
1. Rust and Cargo
Install Rust using rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
After installation, verify:
rustc --version
cargo --version
2. WebAssembly Target for Rust
Add the WebAssembly compilation target:
rustup target add wasm32-unknown-unknown
This allows Rust to compile code that runs in the browser via WASM.
3. Trunk (Build Tool for Yew)
Yew recommends using Trunk to build, serve, and bundle your app easily. Install it via Cargo:
cargo install trunk
Make sure it’s installed:
trunk --version
4. Node.js and npm
Trunk uses tailwindcss
and other JS-based tooling internally, so make sure Node.js is available:
node -v
npm -v
You can install it from nodejs.org if needed.
With the environment ready, let’s move on to creating our Yew project and writing the first lines of Rust-powered frontend code.
Create a Yew Project
With the environment set up, it’s time to create your first Yew app. We’ll start by creating a new Rust library project and configuring it to work with Yew and Trunk.
Step 1: Create the Project Directory
Run the following command to create a new directory and initialize a Rust library project:
cargo new yew-web-app --lib
cd yew-web-app
We use --lib
because Yew apps typically expose a lib.rs
rather than a main.rs
.
Step 2: Add Dependencies
Edit the Cargo.toml
file and add the following dependencies:
[package]
name = "yew-web-app"
version = "0.1.0"
edition = "2021"
[dependencies]
yew = { version = "0.21", features = ["csr"] }
wasm-bindgen = "0.2"
Note:
crate-type = ["cdylib"]
is necessary to compile to WebAssembly.
Step 3: Create src/main.rs
Replace the contents of src/main.rs
with a minimal Yew app:
use yew::prelude::*;
#[function_component(App)]
fn app() -> Html {
let count = use_state(|| 0);
let onclick = {
let count = count.clone();
Callback::from(move |_| count.set(*count + 1))
};
html! {
<div style="text-align: center; margin-top: 50px;">
<h1>{ "Yew Counter App" }</h1>
<p>{ format!("Count: {}", *count) }</p>
<button onclick={onclick}>{ "Click me" }</button>
</div>
}
}
fn main() {
yew::Renderer::<App>::new().render();
}
Step 4: Add index.html
Create a new index.html
in the root directory (same level as Cargo.toml
):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Yew Web App</title>
</head>
<body>
<main></main>
</body>
</html>
Step 5: Create a Trunk.toml
(Optional but Recommended)
In the project root, add a Trunk.toml
file:
[build]
target = "index.html"
You’re now ready to run your Yew app using Trunk!
Run and Test the Yew App with Trunk
With your Yew app and project structure updated, it’s time to build and run it locally using Trunk, the recommended bundler and dev server for Yew.
Start the Development Server
From the project root, run:
trunk serve --open
This command will:
-
Compile your Rust code to WebAssembly
-
Serve
index.html
on a local dev server -
Watch for code changes and rebuild automatically
-
Open the app in your default browser
You should see something like this in your terminal:
🔧 compiling...
🚀 serving at http://127.0.0.1:8080
🌍 opening browser
Test the Counter App
Once it opens in the browser, you should see:
-
A title: Yew Counter App
-
A counter starting at
0
-
A button labeled Click me
Clicking the button will increment the count—this demonstrates Yew’s reactive state management and component rendering in action.
Trunk File Serving Notes
Trunk automatically looks for an index.html
in the root folder and handles script injection for your compiled .wasm
and .js
output. No need to manage wasm-bindgen
or JS glue code manually.
You can customize this behavior in Trunk.toml
if needed:
[build]
target = "index.html"
dist = "dist"
With the app running successfully, let’s move on to expanding the app structure and learning how to build more complex UIs.
Build a Functional Yew Web App
Now that you’ve got a basic counter working, let’s dive deeper into how Yew handles components, state, and events. We'll walk through building a more refined version of the app to better understand how Yew applications are structured.
Project Structure
To keep things clean, organize the app into components:
src/
├── components/
│ └── counter.rs
└── main.rs
Create the folder:
mkdir src/components
touch src/components/counter.rs
counter.rs
: A Yew Function Component
In src/components/counter.rs
, add:
use yew::prelude::*;
#[function_component(Counter)]
pub fn counter() -> Html {
let count = use_state(|| 0);
let increment = {
let count = count.clone();
Callback::from(move |_| count.set(*count + 1))
};
let decrement = {
let count = count.clone();
Callback::from(move |_| count.set(*count - 1))
};
html! {
<div style="text-align: center; margin-top: 40px;">
<h2>{ "Rust + Yew Counter" }</h2>
<p>{ format!("Current count: {}", *count) }</p>
<button onclick={decrement}>{ "-1" }</button>
<button onclick={increment} style="margin-left: 10px;">{ "+1" }</button>
</div>
}
}
Create a file src/components/mod.rs
:
pub mod counter;
Update main.rs
to Use the Component
In src/main.rs
, import and use the Counter
component:
mod components;
use components::counter::Counter;
use yew::prelude::*;
#[function_component(App)]
fn app() -> Html {
html! {
<div>
<h1 style="text-align: center;">{ "Welcome to Yew WebAssembly App" }</h1>
<Counter />
</div>
}
}
fn main() {
yew::Renderer::<App>::new().render();
}
Re-run the App
If Trunk isn't already running, restart it:
trunk serve --open
Your app now has a fully modular structure with reusable components—a key strength of Yew.
Compile and Deploy the Yew App
Once your Rust + Yew web app is working locally, it’s time to prepare it for deployment to the web. We’ll show how to build the app for production and deploy it using GitHub Pages and Netlify—both free and popular hosting options.
Step 1: Build the App for Production
Run the following command to generate optimized WebAssembly output:
trunk build --release
This creates a dist/
folder containing the compiled .wasm
, JavaScript, and HTML files ready to deploy.
You can test it locally by serving it with a static file server:
npx serve dist
Deployment Options
Option 1: Deploy to GitHub Pages
-
Push your project to GitHub
Initialize git and push to a repo:git init git remote add origin https://github.com/yourusername/yew-web-app.git git add . git commit -m "Initial commit" git push -u origin main
-
Install GitHub Pages CLI (optional)
npm install -g gh-pages
-
Deploy the
dist/
directorynpx gh-pages -d dist
-
Configure GitHub repo settings
Go to your repository → Settings → Pages → Select branch:gh-pages
→ Save
Your app will be live at:
https://yourusername.github.io/yew-web-app/
💡 Tip: Update your
<script>
or Trunk config to set a public URL base if using subpaths.
Option 2: Deploy to Netlify
-
Log in and create a new site from GitHub or manually drag-and-drop the
dist/
folder into Netlify -
Set the build command and publish directory in Netlify settings:
-
Build Command:
trunk build --release
-
Publish directory:
dist
-
-
Deploy!
(Optional) Trunk.toml for Public URL
If deploying to a subpath (like GitHub Pages), add this to Trunk.toml
:
public_url = "/yew-web-app/"
Now your app is live on the web—blazing fast, compiled from Rust, and fully reactive thanks to Yew and WebAssembly.
Final Testing & Conclusion
Final Testing
Before considering your deployment complete, it’s a good idea to do a final round of testing:
-
✅ Load the app in different browsers (Chrome, Firefox, Safari)
-
✅ Test both desktop and mobile views
-
✅ Ensure that button clicks and state updates work as expected
-
✅ Check console logs for any warnings or errors
-
✅ Verify that assets load correctly from the correct URL path (especially if hosted under a subdirectory like GitHub Pages)
Conclusion
In this tutorial, you built a complete web app using Rust, WebAssembly, and the Yew framework—all without writing a single line of JavaScript.
You’ve learned how to:
-
Set up a development environment for Rust + WASM
-
Create a reactive UI using Yew components and state hooks
-
Build and serve the app using Trunk
-
Organize code into reusable modules
-
Deploy your app to GitHub Pages or Netlify
Yew brings the safety and speed of Rust to the frontend, opening up a new world of possibilities for high-performance, maintainable web applications.
You can get the full source code on our GitHub.
That is just the basics. If you need more deep learning about the Rust language and frameworks, you can take the following cheap course:
- Rust Programming Language: The Complete Course
- Rust Crash Course for Absolute Beginners 2025
- Hands-On Data Structures and Algorithms in Rust
- Master Rust: Ownership, Traits & Memory Safety in 8 Hours
- Web3 Academy Masterclass: Rust
- Creating Botnet in Rust
- Rust Backend Development INTERMEDIATE to ADVANCED [2024]
Thanks!