Claude Code for Leptos Rust — Workflow Guide
The Setup
You are building a full-stack web application with Leptos, the Rust framework for building reactive web UIs with fine-grained reactivity. Leptos compiles to WebAssembly for the client and native code for the server, enabling true isomorphic Rust. Claude Code can write Leptos components, but it generates React JSX or Yew syntax instead.
What Claude Code Gets Wrong By Default
-
Writes React JSX syntax. Claude generates
<div className="...">{items.map(i => <Item />)}</div>. Leptos uses RSX withview! { <div class="...">{items.iter().map(|i| view! { <Item/> }).collect_view()}</div> }. -
Uses React hooks for state. Claude writes
const [count, setCount] = useState(0). Leptos uses signals:let (count, set_count) = signal(0)with fine-grained reactivity, not virtual DOM diffing. -
Creates separate client and server projects. Claude scaffolds separate frontend and backend repos. Leptos has built-in server functions with
#[server]attribute — client and server code live in the same project. -
Uses JavaScript for client-side logic. Claude adds
<script>tags for interactivity. Leptos compiles Rust to WebAssembly — all client logic is Rust. No JavaScript needed (though JS interop is available).
The CLAUDE.md Configuration
# Leptos Full-Stack Rust Project
## Framework
- UI: Leptos (Rust, fine-grained reactivity, WASM)
- Build: cargo-leptos (handles client + server builds)
- Server: Actix or Axum backend
- Styling: Tailwind CSS with trunk or cargo-leptos
## Leptos Rules
- Components: #[component] fn MyComponent() -> impl IntoView { view! { } }
- Signals: let (value, set_value) = signal(initial)
- Derived: let doubled = move || count() * 2
- Effects: Effect::new(move |_| { log!("{}", count()) })
- Server functions: #[server] async fn get_data() -> Result<T, ServerFnError>
- Events: on:click=move |_| set_count(count() + 1)
- Conditional: <Show when=move || count() > 5>...</Show>
- Lists: <For each=move || items() key=|i| i.id let:item>...</For>
## Conventions
- Components in src/components/ directory
- Server functions in src/server/ directory
- Shared types in src/models/ (used by both client and server)
- Use cargo-leptos for development: cargo leptos watch
- Tailwind: configured in Cargo.toml [package.metadata.leptos]
- No JavaScript — all logic in Rust compiled to WASM
- Error handling with Result and ServerFnError
Workflow Example
You want to create a todo list with server persistence. Prompt Claude Code:
“Create a Leptos todo list with add and toggle functionality. Use server functions to save todos to a database. Implement optimistic updates so the UI responds immediately while the server operation completes.”
Claude Code should create a #[component] with signal-based state, #[server] functions for CRUD operations, create_action() for optimistic updates, and view! macro RSX with <For> iteration and on:click event handlers.
Common Pitfalls
-
Forgetting the
movekeyword on closures. Claude writes event handlers withoutmove. Leptos closures that access signals needmoveto capture them:on:click=move |_| set_count.update(|c| *c += 1). Missingmovecauses borrow checker errors. -
Server function serialization issues. Claude uses complex Rust types in server function arguments. Server functions serialize arguments across the WASM-server boundary — types must implement
Serialize + Deserialize. Keep server function signatures simple. -
WASM binary size. Claude does not configure release optimizations. Leptos WASM bundles can be large. Configure
wasm-opt,opt-level = 'z', andlto = trueinCargo.tomlrelease profile for production builds.