Organizing large apps
Goal
Keep Streamlit reruns predictable as the element tree grows: component boundaries, stable keys, memoized subtrees, and clear module layout.
File & module layout
| Layer | Responsibility |
|---|---|
app.py / pages/*.py |
Entrypoints: build App or call render(Page(…)) |
components/ |
@component definitions grouped by domain (billing, settings, …) |
trees/ or shell.py |
Compose Page, Sidebar, Routes, portals |
state/ helpers |
Pure functions that map session/query to view models (optional) |
Avoid cyclic imports: elements should not import entry scripts.
Key discipline
- Every dynamic list item subtree should carry a stable
key=on the outer element. - Prefer explicit
state(..., key=...)when the same component appears in loops/tabs. - Use
memo_subtreefor expensive element construction, not for hiding Streamlit side effects.
When to split components
Split when:
- Body exceeds ~80–120 lines or mixes unrelated concerns.
- You need isolated testing via
render_to_treeon a subtree. - You want a
ErrorBoundaryaround a risky region without wrapping the whole page.
Keep thin Page composers and fat domain components.
Multipage vs in-script routing
Document the team choice in README/internal docs. Mixing pages/ primary nav with a
second parallel Routes model confuses analytics and deep links—pick one (Roadmap).
See also
- Performance
- Phase 2 tail
- Testing & debugging
- Examples —
phase2_composite_demo.py