I’ve been working on a project over the past few months called Dividend Simulator — a Streamlit web app that lets users simulate dividend reinvestment and track portfolio growth over time.
It’s built with:
Streamlit (multi-page app)
MySQL for portfolio persistence
Marketstack API for real dividend + price data (When I decide to pull it)
Plotly charts for visualizations
Email auth, password reset, and Stripe upgrade path
Plus SEO polish (meta tags, static pages, sitemap/robots)
I used Streamlit for the entire frontend — no JS, no frameworks — and I’m honestly impressed by how far it can be pushed for a full app.
Would love feedback from other Streamlit devs — especially on performance and structure.
Thanks to the Streamlit community for making it this easy to build real tools
I’ve used this web app, and it looks very good and it’s impressively smooth — no lag at all!
I’m really curious how you achieved this level of performance with Streamlit. I’ve built a multi-page Streamlit app myself, but it tends to get a bit laggy, especially with heavier components.
Would love to know what kind of optimizations you applied (like caching, session/state handling, or data loading strategies), what backend setup you’re using, and which platform you’ve hosted it on.
Thanks a lot for the kind words — really glad you found it smooth!
Performance-wise, I focused mainly on caching and lightweight data handling:
Used @st.cache_data and @st.cache_resource wherever possible (e.g., for CSV/JSON loads, DB connections, and service singletons).
Minimized recomputation between reruns by structuring logic into small cached helpers.
Most UI updates just read from memory — no heavy recalculations or large DataFrame merges each run.
Hosting-wise, it’s running on DigitalOcean, and all data comes from a mix of CSV / JSON files and a small database. No real-time API calls inside user interactions — everything is preloaded or cached.
Streamlit’s reactive model can feel laggy if you rebuild too much on every rerun, so I made sure all “slow” steps are cached or done once per session. That alone made a huge difference.
Thanks so much for the kind words — really appreciate it!
I put quite a bit of focus on performance early on because I wanted the app to feel responsive even with multiple pages and data-heavy interactions. A few things that helped a lot:
Caching & data handling:
I rely heavily on @st.cache_data and @st.cache_resource for things like CSV/JSON loads, database connections, and service singletons. This means data and objects are only fetched or initialized once per session — subsequent reruns just read from memory. That alone eliminates most of the lag.
Lightweight UI reruns:
I structured the code so that expensive logic (like portfolio simulations or price lookups) happens only when needed, not every time the page refreshes. Streamlit reruns the script on any input change, so I made sure only the small reactive parts rebuild, while cached layers handle everything else.
Local and incremental data sources:
Most data is loaded from local CSV/JSON files or a small database on startup, rather than pulling from APIs on every request. It’s basically “read once, reuse often”.
Hosting setup:
The app runs on DigitalOcean, which has been stable and plenty fast for this workload — no fancy scaling needed. The combination of good caching and preloaded data keeps server usage low.
Small UX details:
I also used minimal Streamlit components and avoided unnecessary re-renders (for example, grouping inputs and charts logically, and keeping large tables cached separately).
All of that together keeps it feeling “instant” even as the data grows. Streamlit’s caching system has really come a long way — once you structure things around it, you can get surprisingly smooth performance.
Nice! I’m curious, did you use (a forked version of) streamlit-authenticator to handle the authentication? Or did you roll your own from scratch (using extra components for cookie handling)?
Thanks! For this app I ended up rolling my own auth instead of using streamlit-authenticator.
Under the hood it’s pretty simple:
Users live in a users table in MySQL (username, email, hashed_password, plus a permission field for feature flags).
Passwords are hashed with bcrypt.
The login form calls a small helper that lets you log in with either username or email and checks the bcrypt hash.
For persistence I use streamlit-cookies-controller – I store a username cookie and mirror that into st.session_state["authenticated"] / st.session_state["username"].
Each “protected” page starts by initializing the cookie/session state and then calling a require_login(...) helper that shows a “please log in” message and st.stop() if you’re not authenticated.
On top of that I have a small registration helper that creates the user (with bcrypt-hashed password) and sends a welcome email, plus a permissions bitmask (UNLOCK_UNLIMITED_STOCKS, etc.) to gate some features.
If you’re curious I’m happy to share some snippets of the auth helpers I’m using.