Documentation Index
Fetch the complete documentation index at: https://mintlify.com/DioxusLabs/dioxus/llms.txt
Use this file to discover all available pages before exploring further.
Dioxus is designed for high performance, but understanding its architecture and optimization techniques can help you build even faster applications.
Template System
Dioxus uses a template-based virtual DOM that pre-compiles static structure:
rsx! {
div { class: "container", // Static: compiled once
h1 { "Title" } // Static: compiled once
p { "{dynamic_text}" } // Dynamic: diffed on updates
}
}
Only dynamic parts are diffed during re-renders, making updates very fast.
Reference: packages/core/src/nodes.rs:40-89
Memoization
Components automatically memoize props to prevent unnecessary re-renders:
#[derive(Props, Clone, PartialEq)]
struct ItemProps {
id: usize,
name: String,
}
#[component]
fn Item(props: ItemProps) -> Element {
// Only re-renders if props.id or props.name changes
rsx! {
div { "{props.name}" }
}
}
Implement PartialEq on props to enable automatic memoization.
Keyed Lists
Use keys for efficient list diffing:
#[component]
fn TodoList(items: Vec<Todo>) -> Element {
rsx! {
for item in items {
// Key enables efficient reordering
div { key: "{item.id}",
"{item.text}"
}
}
}
}
Without keys, Dioxus must diff the entire list. With keys, it can efficiently detect moves and updates.
Reference: packages/core/tests/diff_keyed_list.rs
Signal Optimization
Granular Reactivity
Signals provide fine-grained reactivity:
#[component]
fn Counter() -> Element {
let count = use_signal(|| 0);
// Only this text node updates, not the entire component
rsx! {
button { onclick: move |_| count += 1,
"Count: {count}"
}
}
}
Signals track dependencies automatically and update only affected nodes.
Avoid Over-Subscription
Don’t read signals unnecessarily:
// Bad: Reads signal even when not needed
fn BadComponent(count: Signal<i32>) -> Element {
let value = count(); // Subscribes to updates
rsx! {
if some_condition {
div { "{value}" }
}
}
}
// Good: Only reads when needed
fn GoodComponent(count: Signal<i32>) -> Element {
rsx! {
if some_condition {
div { "{count}" } // Subscribes only inside condition
}
}
}
Computed Values
Use use_memo for expensive derived state:
#[component]
fn ExpensiveList(items: Signal<Vec<Item>>) -> Element {
// Memoized: only recomputes when items changes
let filtered = use_memo(move || {
items()
.iter()
.filter(|item| item.active)
.cloned()
.collect::<Vec<_>>()
});
rsx! {
for item in filtered() {
div { "{item.name}" }
}
}
}
Component Architecture
Scope Height Optimization
Dioxus renders scopes top-to-bottom by height. Keep frequently updated components shallow:
// Bad: Deep nesting causes cascading updates
fn App() -> Element {
rsx! {
Container {
Wrapper {
Content {
Counter {} // Height: 3, slower updates
}
}
}
}
}
// Good: Flat structure
fn App() -> Element {
rsx! {
Counter {} // Height: 1, faster updates
}
}
Reference: packages/core/src/virtual_dom.rs:379-404
Component Splitting
Split large components to minimize re-render scope:
// Bad: Entire component re-renders on any state change
#[component]
fn LargeComponent() -> Element {
let count = use_signal(|| 0);
let items = use_signal(|| vec![]);
rsx! {
div {
Counter { count } // Re-renders when items changes
ItemList { items } // Re-renders when count changes
}
}
}
// Good: Independent components
#[component]
fn OptimizedComponent() -> Element {
let count = use_signal(|| 0);
let items = use_signal(|| vec![]);
rsx! {
div {
Counter { count } // Only re-renders when count changes
ItemList { items } // Only re-renders when items changes
}
}
}
#[component]
fn Counter(count: Signal<i32>) -> Element {
rsx! { "Count: {count}" }
}
#[component]
fn ItemList(items: Signal<Vec<String>>) -> Element {
rsx! {
for item in items() {
div { "{item}" }
}
}
}
Task Management
Avoid spawning too many tasks:
// Bad: Creates many concurrent tasks
#[component]
fn DataLoader() -> Element {
let data = use_signal(|| vec![]);
for i in 0..100 {
spawn(async move {
let item = fetch_item(i).await;
data.write().push(item);
});
}
rsx! { /* ... */ }
}
// Good: Batch requests
#[component]
fn OptimizedDataLoader() -> Element {
let data = use_resource(move || async move {
fetch_all_items().await
});
rsx! { /* ... */ }
}
Suspense Boundaries
Use suspense to avoid blocking the UI:
#[component]
fn App() -> Element {
rsx! {
Suspense {
fallback: |_| rsx! { "Loading..." },
AsyncContent {} // Doesn't block other components
}
InteractiveContent {} // Renders immediately
}
}
Resource Deduplication
use std::sync::Arc;
use once_cell::sync::Lazy;
static API_CLIENT: Lazy<Arc<ApiClient>> = Lazy::new(|| {
Arc::new(ApiClient::new())
});
#[component]
fn DataComponent() -> Element {
let client = use_hook(|| API_CLIENT.clone());
// Reuses same client across component instances
let data = use_resource(move || async move {
client.fetch_data().await
});
rsx! { /* ... */ }
}
Memory Optimization
Avoid Large Clones
// Bad: Clones large data on every render
#[component]
fn BadList(items: Vec<LargeItem>) -> Element {
rsx! {
for item in items { // Clones entire vector
div { "{item.name}" }
}
}
}
// Good: Use references or Arc
#[component]
fn GoodList(items: Signal<Vec<LargeItem>>) -> Element {
rsx! {
for item in items.read().iter() { // Borrows, no clone
div { "{item.name}" }
}
}
}
Drop Unused Resources
#[component]
fn DataComponent() -> Element {
let data = use_resource(|| fetch_data());
use_effect(move || {
// Cleanup when component unmounts
move || {
data.cancel(); // Cancel pending requests
}
});
rsx! { /* ... */ }
}
Scope Context Sharing
Use context to avoid prop drilling without performance cost:
#[derive(Clone)]
struct AppState {
theme: Signal<Theme>,
user: Signal<Option<User>>,
}
#[component]
fn App() -> Element {
use_context_provider(|| AppState {
theme: Signal::new(Theme::Light),
user: Signal::new(None),
});
rsx! { /* ... */ }
}
#[component]
fn DeepChild() -> Element {
let state = use_context::<AppState>();
// Accesses state without prop drilling
rsx! { /* ... */ }
}
Batch Updates
#[component]
fn BatchedUpdates() -> Element {
let items = use_signal(|| vec![]);
let add_many = move |_| {
// Bad: Each push causes a re-render
// for i in 0..100 {
// items.write().push(i);
// }
// Good: Single update
items.write().extend(0..100);
};
rsx! {
button { onclick: add_many, "Add Items" }
for item in items() {
div { "{item}" }
}
}
}
use std::time::Duration;
#[component]
fn SearchBox() -> Element {
let query = use_signal(|| String::new());
let results = use_signal(|| vec![]);
use_effect(move || {
let q = query();
// Debounce search
spawn(async move {
tokio::time::sleep(Duration::from_millis(300)).await;
if query() == q { // Still same query?
let data = search(&q).await;
results.set(data);
}
});
});
rsx! {
input {
oninput: move |e| query.set(e.value()),
value: "{query}"
}
for result in results() {
div { "{result}" }
}
}
}
For large lists, render only visible items:
#[component]
fn VirtualList(items: Vec<String>) -> Element {
let scroll_offset = use_signal(|| 0);
let viewport_height = 600;
let item_height = 50;
let visible_start = scroll_offset() / item_height;
let visible_count = (viewport_height / item_height) + 2;
let visible_items = &items[visible_start..visible_start + visible_count];
rsx! {
div {
style: "height: {viewport_height}px; overflow-y: scroll",
onscroll: move |e| scroll_offset.set(e.scroll_top()),
div { style: "height: {items.len() * item_height}px",
for (i, item) in visible_items.iter().enumerate() {
div {
key: "{i}",
style: "position: absolute; top: {(visible_start + i) * item_height}px",
"{item}"
}
}
}
}
}
}
Build Optimization
Release Builds
Always use release mode for production:
Release mode enables:
- LLVM optimizations
- Dead code elimination
- Inlining
- LTO (Link Time Optimization)
WASM Optimization
For web builds, optimize WASM size:
# Cargo.toml
[profile.release]
opt-level = "z" # Optimize for size
lto = true
codegen-units = 1
panic = "abort"
strip = true
Asset Optimization
Use manganis for automatic asset optimization:
use manganis::{asset, Asset, AssetOptions, ImageFormat};
// Automatically optimized at build time
const HERO: Asset = asset!(
"/assets/hero.jpg",
AssetOptions::image()
.with_format(ImageFormat::Webp) // Better compression
);
Profiling
For web builds, use Chrome’s Performance profiler:
- Open DevTools (F12)
- Go to Performance tab
- Click Record
- Interact with your app
- Stop recording
- Analyze flame graph
Tracing
Enable tracing for detailed logs:
use tracing_subscriber;
fn main() {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE)
.init();
dioxus::launch(app);
}
This logs:
- Component renders
- Scope creation/destruction
- Task execution
- Event handling
Reference: packages/core/src/virtual_dom.rs:307-331
Custom Metrics
use std::time::Instant;
#[component]
fn MetricsComponent() -> Element {
use_effect(move || {
let start = Instant::now();
move || {
let duration = start.elapsed();
println!("Component lifetime: {:?}", duration);
}
});
rsx! { /* ... */ }
}
Benchmarking
Create benchmarks to measure performance:
// benches/rendering.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dioxus::prelude::*;
fn benchmark_render(c: &mut Criterion) {
c.bench_function("render 1000 items", |b| {
let mut dom = VirtualDom::new(|| rsx! {
for i in 0..1000 {
div { key: "{i}", "Item {i}" }
}
});
b.iter(|| {
dom.rebuild_in_place();
});
});
}
criterion_group!(benches, benchmark_render);
criterion_main!(benches);
Run with:
Best Practices
- Measure First: Profile before optimizing
- Use Keys: Always key dynamic lists
- Memoize Props: Implement
PartialEq on props
- Granular Signals: Use signals for fine-grained reactivity
- Batch Updates: Group multiple state changes
- Lazy Loading: Use suspense for async content
- Optimize Assets: Use manganis for asset optimization
- Release Builds: Always benchmark in release mode
Common Pitfalls
- Over-optimization: Don’t optimize without profiling
- Premature Memoization: Simple components don’t need memoization
- Too Many Signals: Signals have overhead, use sparingly
- Large Props: Avoid cloning large data structures
- Blocking Async: Don’t block in async tasks
Additional Resources
- Core architecture:
notes/architecture/01-CORE.md
- Signals architecture:
notes/architecture/04-SIGNALS.md
- VirtualDOM source:
packages/core/src/virtual_dom.rs
- Diff algorithm:
packages/core/src/diff/