Performance¶
Rezi’s performance model is built around bounded work and deterministic scheduling. You get the best results when you keep views pure, keep identities stable, and use virtualization for large datasets.
Reconciliation keys¶
When rendering lists, provide a stable key so Rezi can reconcile efficiently:
import { ui } from "@rezi-ui/core";
ui.column({ gap: 1 }, items.map((it) => ui.text(it.name, { key: it.id })));
Guidelines:
keymust be unique among siblings.- Prefer real IDs over array indices.
- Changing keys forces unmount/mount and can invalidate local state for complex widgets.
Avoiding re-renders¶
Views are re-run when committed state changes. Keep view(state):
- pure (no side effects)
- cheap (avoid large allocations each frame)
- stable (avoid rebuilding huge derived structures inline)
Patterns:
- Precompute derived data in state updates instead of inside
view. - Keep large static arrays/constants outside the view closure.
- Prefer event handlers that call
app.update(...)without doing heavy work in render.
Virtual lists¶
For large datasets, use ui.virtualList to window the rendered items:
import { ui } from "@rezi-ui/core";
ui.virtualList({
id: "results",
items: state.rows,
itemHeight: 1,
overscan: 3,
renderItem: (row, index, focused) =>
ui.text(`${focused ? "> " : " "}${row.name}`, { key: row.id }),
});
This keeps render + layout work proportional to the visible window rather than the full dataset size.
Caps and limits¶
The runtime enforces hard limits (drawlist sizes, event batch validation, etc.). These failures are deterministic and surfaced as fatal errors in development to prevent “slow corruption” or non-deterministic behavior.
If you hit limits:
- reduce per-frame draw commands (simplify UI or virtualize)
- avoid rendering off-screen content
- ensure your view isn’t duplicating large subtrees unnecessarily
Profiling¶
Use the debug trace system to inspect frame timing and hotspots:
import { createDebugController, categoriesToMask } from "@rezi-ui/core";
const debug = createDebugController({ maxFrames: 300 });
await debug.enable({ categoryMask: categoriesToMask(["perf", "frame"]) });
See: Debugging.
Common pitfalls¶
- Missing
keyin dynamic lists → high churn, lost local state - Using array index as
key→ reordering causes remounts - Allocating large arrays/objects inside
viewevery frame - Calling
update()during render → throws deterministically (ZRUI_UPDATE_DURING_RENDER) - Rendering large tables without
ui.table/ui.virtualList→ unbounded work
Related¶
- Lifecycle & Updates - Commit points and frame coalescing
- Layout - How size constraints and clipping affect work
Next: Debugging.