Bitdoze logo

Streamlit vs. NiceGUI (2026): Best Python Web Framework

Streamlit vs NiceGUI in 2026: compare the two Python web frameworks on architecture, auth, real-time updates, self-hosting, and which one fits your project.

Dragos

25 min read

Streamlit vs NiceGUI comparison: two Python web frameworks for dashboards and app-style UIs

Streamlit vs NiceGUI is the comparison I see most often when someone wants a Python web framework and does not want to hand-write HTML. Both let you build interactive web apps in mostly Python, both are open source, and both run fine as a single Docker container on a small VPS. Both also changed enough in the last year that older comparison posts are now wrong.

Updated October 2026. Versions checked against PyPI and GitHub on 2026-10-07: Streamlit 1.65.0 (released 2026-10-02) and NiceGUI 3.17.1 (2026-09-18). If you read a guide that still calls NiceGUI a 2.0-era framework, close it.

The short positioning stays the same as in 2024. Streamlit targets data apps: dashboards, data exploration, ML demos. You write a Python script top to bottom and Streamlit renders it as a web app with built-in charts, tables, and filters. NiceGUI targets application-style UIs: forms, admin panels, monitoring tools, IoT controllers. It is event-driven, runs on FastAPI, and gives you far more control over layout, events, and state.

Quick comparison overview

Feature Streamlit 1.65 NiceGUI 3.17
Best for Data apps, dashboards, ML demos App-style UIs: forms, panels, monitoring
Authoring model Script reruns (with fragments for partial updates) Event-driven components over WebSockets
Underlying server Starlette/Uvicorn (ASGI, custom routes via st.App) FastAPI/Starlette (mount into your own app)
Customization Theming + custom components v2 Full layout control, Quasar/Vue, Tailwind 4
Built-in auth st.login() / st.logout() (OIDC) Middleware + app.storage.user pattern
Python >= 3.10 >= 3.10, < 4
License Apache-2.0 (Snowflake-backed) MIT (independent)

This table is orientation, not the verdict. The decision still comes down to the developer model: script reruns versus event callbacks. Get that right and the rest follows.

For more options beyond these two, see: Best Python Web Frameworks

Streamlit vs NiceGUI: main differences in 2026

The core positioning has not moved. What changed is the plumbing under both frameworks. Here is how they differ in 2026, with the dates so you can see how fresh the claims are.

Two execution models: Streamlit script rerun loop vs NiceGUI event/WebSocket loopStreamlit: top-to-bottom script rerunNiceGUI: event callback over WebSocketUser interactionScript rerunstop to bottomUI diff sentover WebSocket@st.fragmentreruns only one blockUser eventHandler runsonly the callbackElement updatesover WebSocketFull rerun by default since 1.0Fragments (1.37-1.63) scope the rerunCallback touches only its elementsState syncs over a persistent socket

Primary purpose

Streamlit is built for data science, ML, and analytics dashboards. Its widget set, DataFrame rendering, and chart helpers all assume that workflow. NiceGUI is built for general-purpose web applications with desktop-like interfaces: forms, admin panels, control screens.

Neither framework has crossed over in 2026. Streamlit still feels like a notebook that grew a web server. NiceGUI still feels like a GUI toolkit that happens to run in the browser. NiceGUI also ships the extras that make it an app framework rather than a dashboard tool: native/desktop windows (PySide6-based), run.cpu_bound and run.io_bound for background work, and ui.timer for live dashboards.

Development model: script reruns vs event-driven

This is the decision factor. Everything else is secondary.

Streamlit runs your script top to bottom on every interaction. That model is dead simple until your app grows heavy work at the top of the file. Since 1.37 and with steady additions through 1.63, @st.fragment scopes a rerun to one block of the page (1.58 added parallel=True; 1.63 added event-scoped reruns with @st.fragment(key=...) and st.rerun("name")). Fragments fix the worst of the full-rerun jank, but the mental model is still “the script runs again”.

NiceGUI calls your handler and updates the elements you touch. UI state syncs over a persistent WebSocket. Nothing reruns from the top.

If your mental model is a notebook, Streamlit wins. If it is a GUI with event handlers, NiceGUI wins. Look back at the diagram above.

Customization

The old “Streamlit is limited” line needs an asterisk now. Streamlit 1.51 (2025-10-29) shipped custom components v2 (frameless, bidirectional), custom light and dark themes, and reusable theme files. You can build a branded internal dashboard without forking the framework.

It is still not close to NiceGUI. NiceGUI sits on Quasar/Vue with Tailwind 4 (upgraded in 3.0), and you can drop to raw HTML/CSS/JS whenever the component library runs out. Layouts are explicit: rows, columns, grids, tabs, dialogs, drawers. Multi-page apps are a first-class pattern via @ui.page (see how to add multiple pages to NiceGUI).

Streamlit has multi-page apps too, but navigation is file-based and the page chrome is mostly fixed. If you need pixel-level control or a layout that does not look like a Streamlit app, NiceGUI is the pick.

Backend architecture

Both frameworks are ASGI apps in 2026. The old “Streamlit has a toy server, NiceGUI has FastAPI” framing is dead.

Streamlit replaced Tornado with Starlette/Uvicorn in 1.57 (2026-04-29). st.App (experimental in 1.53, available with the Starlette switch) is an ASGI entry point that accepts custom routes, middleware, and lifecycle hooks, and plays with FastAPI or Starlette apps. Async/await in app code landed in 1.64 (2026-09-15), cached functions included.

NiceGUI has been FastAPI/Starlette/Uvicorn all along and can mount into an existing FastAPI app so the API and the UI share one process. The documented integration point is ui.run_with(app) (check the Configuration and deployment section of the NiceGUI docs for the exact signature on your version).

Both frameworks run on ASGI in 2026

The old “Streamlit has no real backend” argument is outdated. The differentiator is the authoring model, not the server underneath. Pick on how you want to write the app, not on which HTTP stack it ships with.

Real-time features

Streamlit updates on interaction: a widget change reruns the script (or the fragment). Since 2026 you can target reruns more precisely, but the model is still pull-on-interaction. Good enough for filters, drilldowns, and dashboards that refresh when a user clicks.

NiceGUI is the natural fit for monitoring and control panels. ui.timer polls or pushes, each client holds its own state, and handlers update individual elements. Live counters, log tails, IoT controls, and “the page updates without me touching it” UIs are the default behavior, not a workaround.

Learning curve

Streamlit is still the friendliest way into Python web apps. If you can write a script with pandas, you can ship a dashboard in an afternoon. You do not write HTML or callbacks, and there is no state machine to manage.

NiceGUI is slightly steeper. You need to understand event handlers, when to use ui.clear(), and how per-client state works. Basic HTML/CSS knowledge helps with layout. It pays that cost back the moment you build something with more than a few interactions.

Both now require Python >= 3.10 (PyPI requires_python, checked 2026-10-07). On an old base image, install fails before you write a line of code.

Community & resources

GitHub stars as of 2026-10-07: Streamlit 45,909, Gradio 43,677, Reflex 28,937, Dash 24,442, Flet 17,263, NiceGUI 16,269. Download counts and stars are not quality, but they do predict how fast you find a Stack Overflow answer.

Streamlit releases every 4 to 6 weeks and has a large example ecosystem, plus Streamlit Community Cloud for free publishing. NiceGUI is active (58 open issues at last check) with the 3.x line maturing toward a 4.0 that is already flagged in release notes. Its community is smaller and self-hosting-focused. You will read the docs more and Stack Overflow less.

License & cost

Streamlit is Apache-2.0 and has been owned by Snowflake since 2022. The OSS package stays Apache; “Streamlit in Snowflake” is the commercial path. NiceGUI is MIT, maintained by Zauberzeug (independent), with no paid tier.

Streamlit Community Cloud hibernates apps

Free Streamlit Community Cloud apps hibernate after 12 hours without traffic and wake on the next visit (official docs, retrieved 2026-10-07; older “7 days of inactivity” wording floating around the web is stale). The free tier wants a public repo and is resource-capped. That is fine for a demo and wrong for an internal tool with an SLA. I would not put anything a colleague depends on behind Community Cloud.

Authentication in 2026

The old advice was “Streamlit has no auth, NiceGUI needs FastAPI patterns”. Half of that is now wrong. Streamlit shipped first-party OIDC in 1.42 (2025-02-04) and st.user.tokens in 1.53 (2026-01-14). NiceGUI’s story is unchanged: you assemble auth yourself, and you own the session stack.

Streamlit’s built-in OIDC (st.login() / st.logout())

st.login() redirects to any OIDC provider you configure (Google, Okta, Auth0, Keycloak, whatever speaks OIDC). st.user gives you the claims after the redirect; st.user.tokens exposes the raw OIDC tokens if you need to call downstream APIs. Configuration lives in .streamlit/secrets.toml. The docs for the exact secrets shape have moved around, so copy from the current Streamlit auth page rather than from an older tutorial.

NiceGUI’s middleware + app.storage.user pattern

NiceGUI documents an Authentication pattern built on middleware, app.storage.user, and a storage_secret. You wire the login check yourself, store the user identity in per-user storage, and redirect unauthenticated clients. When you mount NiceGUI into your own FastAPI app, you can instead use whatever auth middleware or dependency injection you already run. More assembly required, but you own the session and auth stack end to end.

The two patterns as minimal code shapes:

Streamlit OIDC

python
import streamlit as st

if not st.user.is_logged_in:
    st.login()  # redirects to your OIDC provider
else:
    st.write(f"Hello, {st.user.name}")
    # st.user.tokens has the raw OIDC tokens (since 1.53)
    st.button("Log out", on_click=st.logout)

NiceGUI middleware

python
from nicegui import ui, app

# storage_secret comes from ui.run(storage_secret='change-me-to-a-long-random-string')
# and is required before app.storage.user works.

@ui.page('/private')
def private_page():
    if not app.storage.user.get('authenticated', False):
        ui.navigate.to('/login')
        return
    ui.label(f"Hello, {app.storage.user.get('username')}")

# Wire the actual login check in middleware (see NiceGUI docs,
# Authentication section) or use FastAPI auth when mounted via ui.run_with(app)

When to choose each framework

“Streamlit vs NiceGUI which is better” is the wrong question. Which fits the project is the right one. Here is the split I use.

Choose Streamlit if…

Streamlit is the fastest path from notebook to shared dashboard.

Example use cases: sales analytics dashboard, machine learning model demo, financial data explorer, research data visualization.

Choose NiceGUI if…

NiceGUI is the tool when the “dashboard” grows buttons, forms, and users.

  • You are building desktop-like applications in the browser (admin panels, tools, controllers)
  • Real-time interactions without reloads are a requirement, not a nice-to-have
  • You need custom layouts and components beyond a standard widget set
  • You want APIs and UI in the same app, with explicit control over routing and auth
  • Multiple users will operate the app, not just look at it
  • You want a walk-through first: NiceGUI tutorial for beginners

Example use cases: project management tools, IoT device controllers, interactive forms and surveys, real-time monitoring systems.

NiceGUI also runs as a native desktop window (PySide6), which is a nice trick when the “web app” turns out to be an internal desktop tool.

When neither framework fits

Honest limits

Long-lived multi-user CRUD with complex permissions, strict role systems, or heavy relational data modeling belongs in Django, FastAPI with templates, Reflex, or a full-stack JS framework. Both Streamlit and NiceGUI will fight you on that shape of problem. And if you need a mobile app, neither of these is the answer. Pick the boring option and move on.

Getting started: code examples

Pin your versions

Both frameworks require Python >= 3.10 (if yours is older, see how to install and upgrade Python on Mac). Pin versions in requirements.txt and rebuild deliberately. NiceGUI 3.0 (2025-10-03) was breaking and 4.0 deprecations are already announced. Streamlit removes deprecated APIs aggressively (1.58 dropped element.add_rows and the LangChain callback handler). Do not pip install -U blindly on a production container. I manage Python projects with uv; here is how to set up a Python project with uv if you want the same workflow.

Streamlit example: data dashboard

python
import streamlit as st
import pandas as pd

st.title("Sales Dashboard")

data = pd.DataFrame({
    "Month": ["Jan", "Feb", "Mar", "Apr"],
    "Sales": [100, 150, 120, 200],
})

month = st.selectbox("Select month", data["Month"])
filtered = data[data["Month"] == month]

st.bar_chart(data.set_index("Month"))
st.metric(f"Sales for {month}", f"${int(filtered['Sales'].iloc[0])}")

@st.fragment  # reruns only this block, not the whole script
def notes():
    if st.button("Add note"):
        st.session_state.notes = st.session_state.get("notes", []) + [month]

notes()

Verify: install and run it, then check the app serves.

bash
pip install streamlit pandas
streamlit run app.py

Open http://localhost:8501. As a sanity check before you write anything, streamlit hello runs the built-in demo app. If curl -s http://localhost:8501/_stcore/health prints ok, the server is up.

Failure mode: Port 8501 is already in use means something else owns the port. Either stop it or run streamlit run app.py --server.port 8502.

NiceGUI example: interactive form

python
from nicegui import ui

def handle_submit():
    ui.notify(f"Hello {name.value}! You are {age.value} years old.")

ui.label("User Information Form")

with ui.row():
    name = ui.input("Name", placeholder="Enter your name")
    age = ui.number("Age", value=25, min=0, max=120)

ui.button("Submit", on_click=handle_submit)

ui.run(title="bitdoze demo")  # NiceGUI 3.x, Uvicorn under the hood, WebSocket UI sync

Verify:

bash
pip install nicegui
python main.py

Open http://localhost:8080, submit the form, and confirm the notification toast appears in the bottom-right corner. The terminal shows the Uvicorn startup line; that is your hint the server is listening.

Failure mode: the most common install error is Requires-Python >=3.10 (or a resolution error mentioning it). The fix is a newer Python, not a newer pip.

Which is easier to learn?

Streamlit wins for absolute beginners. Linear script in, web app out. NiceGUI is accessible once the callback plus ui.clear() mental model clicks, and it keeps paying off as the app grows. Both need Python >= 3.10. If you have never built a web app, start with Streamlit for a weekend, then look at NiceGUI when the rerun model starts annoying you.

Self-hosting Streamlit and NiceGUI on a VPS

Both frameworks self-host the same boring way: one Docker container behind a reverse proxy on a small VPS. Neither needs a database to run. This is the section the other comparison posts skip, and it is where both frameworks fail in production.

VPS deployment topology: browser through reverse proxy to Streamlit and NiceGUI containersBrowserHTTPS + WebSocketReverse proxyCaddy / Traefik / NginxUpgrade headers requiredStreamlit container :8501GET /_stcore/health -> okNiceGUI container :8080GET /health via FastAPI mountWS upgradeHTTP + WSHTTP + WS

Prerequisites

Before you start, have all of this in place:

  • A Linux VPS with 2 vCPU and 2-4 GB RAM. Something like a Hetzner Cloud VPS (CX22 class, around EUR 5/month) runs both apps comfortably
  • Docker and Docker Compose installed on the server
  • A domain or LAN hostname pointing at the VPS
  • Python >= 3.10 in the image you build from (python:3.12-slim is a safe default)
  • A reverse proxy (Caddy, Traefik, or Nginx) or Dokploy with its bundled Traefik
  • Knowledge that both apps are WebSocket-heavy: the proxy must forward the Upgrade and Connection headers

Docker setup and healthchecks

A container without a healthcheck is a container you cannot restart safely. Streamlit has a built-in endpoint. NiceGUI does not, so you expose one.

Streamlit healthcheck

dockerfile
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8501
HEALTHCHECK CMD curl -f http://localhost:8501/_stcore/health || exit 1
CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]

NiceGUI healthcheck

python
# main.py
from fastapi import FastAPI
from nicegui import ui

app = FastAPI()

@app.get('/health')
def health():
    return {'ok': True}

@ui.page('/')
def index():
    ui.label('Hello from NiceGUI')

# Mount NiceGUI into the FastAPI app so /health and the UI share one process.
# Check the exact run_with signature in the NiceGUI docs for your version.
ui.run_with(app)
dockerfile
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]

Streamlit has a real health endpoint

GET /_stcore/health returns ok and is the standard container healthcheck for Streamlit (Streamlit Knowledge Base). NiceGUI ships no built-in health route, which is why the FastAPI mount pattern above is worth the five extra lines. Double-check the run_with API name against the Configuration and deployment section of the NiceGUI docs for your pinned version before you bake it into an image.

A minimal compose file shape (trimmed to the moving parts):

yaml
services:
  streamlit:
    build: ./streamlit-app
    ports:
      - "8501:8501"
    restart: unless-stopped
  nicegui:
    build: ./nicegui-app
    ports:
      - "8080:8080"
    restart: unless-stopped

Verify:

bash
curl -f http://localhost:8501/_stcore/health   # -> ok
curl -f http://localhost:8080/health          # -> {"ok": true}
docker ps                                     # STATUS column shows (healthy)

Failure modes: curl: (7) Connection refused inside the container usually means the app is still starting or bound to 127.0.0.1 instead of 0.0.0.0. curl: (22) from the healthcheck means the endpoint 404’d, which for NiceGUI means the FastAPI route is not mounted. unhealthy in docker ps with an app that works in the browser is almost always a health URL typo.

For a longer treatment of running Python apps in containers, see run any Python app in Docker with Docker Compose.

Reverse proxy and WebSocket notes

The number one real-world failure for both frameworks is a blank page or an endless spinner behind a reverse proxy. Both symptoms mean the same thing: the WebSocket upgrade is not being forwarded.

Nginx

nginx
location / {
    proxy_pass http://streamlit:8501;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 86400;
}

Traefik

yaml
labels:
  - traefik.http.routers.streamlit.rule=Host(`app.example.com`)
  - traefik.http.services.streamlit.loadbalancer.server.port=8501
  # Traefik forwards WebSocket upgrades by default.
  # Raise forwardAuth/response timeouts if you see idle sockets drop.

Caddy

text
app.example.com {
    reverse_proxy streamlit:8501
    # Caddy forwards WebSocket upgrades without extra config
}

Verify: open the app through the proxy URL, not the container port. Browser dev tools, Network tab: the WebSocket request should show status 101 (Switching Protocols). A status 200 or 400 on that request is your upgrade failing.

Failure modes:

  • Blank page, no errors in the app log: missing Upgrade/Connection headers (Nginx without the proxy_http_version 1.1 block above).
  • App loads, then spinner never resolves: an idle timeout is killing the socket. Raise proxy_read_timeout or the Traefik equivalent.
  • Streamlit 1.60 (2026-07-21) shipped breaking security hardening: origin and host-message checks (CWE-346), query-string caps at 512 KiB / 1,000 fields, and server.maxWidgetStateSize (default 25 MB). If you run Streamlit behind a proxy with non-standard Host or X-Forwarded-Host handling, read that release note before upgrading and test on your own stack. A standard Caddy or Traefik setup should be unaffected, but unusual header rewriting is exactly what those checks look for.

Do not run old NiceGUI 3.x on a public endpoint

NiceGUI 3.16 and 3.17 shipped security fixes you want: a memory-exhaustion bug on socket disconnect (GHSA-46f2-xhpw-8vh3, Aug 2026), an XSS via prop injection (GHSA-955g-h32v-mvrr), and unguessable single-use ui.download routes (GHSA-p92q-2755-mhgh, Sep 2026). Upgrade to at least 3.17.1 before the app is reachable from the internet. This is also the argument for pinning versions: pin, then bump deliberately.

Cost and resource notes

Order-of-magnitude numbers, because this is where people get surprised:

  • Both apps self-host on one small VPS (2 vCPU, 2-4 GB RAM, roughly EUR 5/month). Neither needs a database. A Hostinger VPS is a budget alternative with NVMe storage if you want more disk for data workloads.
  • Streamlit holds state per session process. RAM scales with concurrent sessions, not just with the code. If twenty people open your dashboard, budget for twenty sessions. This is the real VPS sizing consideration.
  • NiceGUI also holds per-client state in the worker. Multi-worker deployments have caveats (documented in the NiceGUI docs), so read that before scaling horizontally.
  • Streamlit Community Cloud: free, but 12-hour hibernation plus resource caps, as covered above. Fine for demos. For always-on internal tools, self-host.
  • Pin your image tags and package versions. NiceGUI 3.0 was breaking and 4.0 deprecations are already announced; Streamlit removes deprecated APIs every few releases.
  • Rollback path: keep the previous image tag. If a bump breaks the app, docker compose up the old tag. That is the whole undo plan, and it works.

If you want the PaaS-shaped route instead of raw Compose, deploy a Python uv project with Dokploy. For the full Streamlit runbook including TLS and tunnels, deploy Streamlit on a VPS with Cloudflare Tunnels.

Full Streamlit VPS deployment guide

Alternatives in 2026

If neither framework fits, the current Streamlit alternatives (versions and stars checked 2026-10-07) look like this:

  • Gradio 6 (6.29.1, 43.7k stars): ML demos and Hugging Face Spaces. If the app is “model in, sample out”, Gradio is still the shortest path.
  • Dash 4.4 (24.4k stars): the OG Python analytics framework from Plotly, now on the 4.x line. Heavy-duty dashboards with callbacks.
  • Reflex 0.9 (28.9k stars): full-stack Python that compiles to Next.js. More like building a traditional web app, just without leaving Python.
  • Flet (17.3k stars): Flutter-based, ships desktop and web from one codebase.
  • Taipy: pipeline plus GUI for data apps. We compared it directly in the Streamlit vs Taipy comparison.
  • FastHTML: lightweight Python UI for people who want HTML-shaped control without a big framework. See FastHTML for beginners.

One cautionary tale: Google’s Mesop showed up on every “worth watching” list in 2024, including this article’s earlier version. The repo is archived in 2026. Experimental frameworks from big vendors can disappear. Favor projects with a release history you can audit.

The wider field is in best Python web frameworks.

Which should you choose?

To answer “Streamlit vs NiceGUI which is better” directly: neither is objectively better. Pick for the project, not for the benchmark chart. Both are actively maintained and fine for production in 2026.

The two one-liners

Streamlit is the fastest path from notebook to shared dashboard. NiceGUI is the tool when the “dashboard” grows buttons, forms, and users. If you can say which of those your project is, you already have your answer.

Start with the framework that fits the project in front of you. You can switch later if the project changes shape; both APIs are small enough that a rewrite after a prototype is cheap.

Streamlit vs NiceGUI FAQ

Is NiceGUI faster than Streamlit?

It is not a yes/no speed question, because the execution models differ. NiceGUI updates only the elements touched by the event handler. Streamlit reruns the script (or a fragment) and diffs the UI, so heavy top-of-file work hurts until you cache or fragment it. Measure on your workload, but for interaction-heavy UIs NiceGUI’s model does less redundant work by default.

Can Streamlit handle multi-page apps and forms?

Yes. Streamlit has multi-page apps, fragments, and session state, and you can build real forms with them. Where it strains is app-style UIs with many buttons, forms, and concurrent users: that is where NiceGUI’s event-driven model keeps the code simpler and the browser responsive.

Does NiceGUI work with FastAPI?

Yes. NiceGUI is built on FastAPI and can mount into an existing FastAPI app so your API routes and UI share one process. The documented integration point is ui.run_with(app); check the exact signature in the NiceGUI docs for your pinned version. You can also keep them separate and run NiceGUI standalone with ui.run().

Do I need Docker to self-host either framework?

No. streamlit run app.py and python main.py work fine on a bare VPS behind Nginx or Caddy. But one container plus a reverse proxy is the boring reliable path: reproducible builds, healthchecks, restart policies, and a one-line rollback to the previous image tag. If you want the container approach, see how to run any Python app in Docker with Docker Compose.

Is Streamlit free for commercial use?

Yes. Streamlit is Apache-2.0; you can build and sell commercial apps on it. The paid path is Streamlit in Snowflake, not the OSS package. Streamlit Community Cloud has a free tier with hibernation after 12 hours and resource caps. Self-hosting has no license cost for either Streamlit or NiceGUI (NiceGUI is MIT), just your server bill.

Quick start resources

Streamlit:

NiceGUI:

Deploy Streamlit on a VPS