
Streamlitを卒業した話:LLMが育った今、Pythonフレームワークを選び直す
この記事は、AIにコードを書いてもらいながらWebアプリを作っている自分が、Streamlitから別のフレームワークに移った経緯を書いたものです。
「Streamlitはダメだ」という話ではありません。当時の自分にとって最善の選択だったし、今でも良いツールだと思っています。ただ、自分の開発スタイルやLLMの進化に合わせて、道具を選び直すタイミングが来たという話です。
同じようなことを感じている方の参考になればうれしいです。
AIにコードを書かせるということ
2025年頃のことです。LLMにコードを書かせる開発スタイルが広まり始めていました。
自分も試してみました。ただ、当時のLLMが生成するコードは正直なところ不安定でした。動いたと思っても意図と違う。直したら別の場所が壊れる。そんなことの繰り返しでした。
この状況で自分が意識するようになったのは「LLMが失敗しにくい構造を選ぶ」ということでした。フレームワークが複雑になるほど壊れるポイントが増える。だったら、できるだけシンプルなものを選びたい。そう考えていました。
消去法で選んだStreamlit
FlaskやDjangoも考えました。でも構成ファイルが多く、LLMがどこかでミスを起こしやすいと感じていました。
Streamlitは1ファイルで完結します。UIの記述も関数を呼ぶだけです。壊れる場所が少ない。LLMに書かせても、そこそこ動くものができる。
正直に言うと「Streamlitが最高のフレームワークだから」選んだわけではありませんでした。「LLMの失敗を最小化できるから」選んだのです。
妥協だったと思います。でも、当時の自分にとっては最善の妥協でした。
成功体験が足枷になる
Streamlitでアプリをいくつか作りました。安定して動くものができるようになりました。成功体験が積み重なっていきました。
でも、使い続けるうちに小さな不満が溜まっていきました。
起動のたびに待たされる。
毎回のことだから、地味にストレスが積み重なります。開発のテンポが悪くなる感覚がありました。
カスタマイズがしにくい。
「ボタンの配置をもう少し変えたい」「レイアウトを細かく調整したい」。そういう要望が出てくるたびに、Streamlitの壁にぶつかりました。できなくはないけど、無理やりやっている感じがする。
デモ止まりになってしまう。
プロトタイプとしては十分。でも、もう少し本格的に使おうとした瞬間に詰まる。「デモ用」と「本番用」の間に大きな溝があると感じるようになりました。
不満はありました。でも「Streamlit以外だとLLMがまともなコードを書いてくれない」という記憶があったので、なかなか離れられませんでした。
移行の前にやったこと:Streamlitのチューニング
フレームワークを変える前に、まずStreamlitのまま改善できないか試しました。
画面いっぱいスペースを使う。
Streamlitは標準状態だと、画面右上にローディングのアニメーションやハンバーガーメニューが表示されるため、画面上部にスペースが生じます。CSSを注入して非表示にしました。
st.markdown("""
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
header {visibility: hidden;}
</style>
""", unsafe_allow_html=True)これで画面を広く使えるようになりました。ただ、こういうハックを入れないと自分のアプリらしい見た目にならない、というのがStreamlitの悩ましいところです。
キャッシュの活用。
`@st.cache_data` や `@st.cache_resource` を使って、重い処理の結果をキャッシュするようにしました。データの読み込みやAPIの呼び出しが繰り返されなくなり、体感速度はかなり改善しました。
フラグメント(`@st.fragment`)の導入。
Streamlitは通常、ボタンを押すたびにスクリプト全体が再実行されます。`@st.fragment` を使うと、特定のUI部分だけを再実行できるようになります。全体の再実行を避けられるので、操作のたびに画面がちらつく問題が軽減しました。
マルチページ構成への分割。
1ファイルに詰め込んでいたアプリを、`pages/` ディレクトリを使って複数ページに分割しました。ページごとの起動が軽くなり、管理もしやすくなりました。
これらのチューニングで、Streamlitアプリはだいぶ快適になりました。正直、ここで満足してもおかしくなかったと思います。
でも、根本的な部分——再実行駆動のモデル、UIの自由度、Streamlitの主張が強い見た目——は変わりませんでした。チューニングで改善できるのは「速さ」であって「構造」ではない。そう気づいたとき、フレームワークを変える決心がつきました。
もっと軽くて、自由なUIがほしい
チューニングとは別に、使うたびに気になっていたことがありました。
Streamlitの主張が強すぎる。
起動するとブラウザのタブにStreamlitの名前が出る。右上には「Deploy」ボタンが常に表示されている。自分が作ったアプリなのに、Streamlitのアプリを借りているような見た目になる。ちょっとしたことだけど、使うたびに気になっていました。
動作が軽くて、自分のアプリとして見える。そういうフレームワークがほしい。でもReactやVueを書く気にはなれない。Pythonで完結させたい。
代わりに何を使えばいいのか分からない。そんな状態がしばらく続いていました。
「あれ、普通に動く」
いつ頃だったかは正確に覚えていません。
ある日、試しにFastHTMLのコードをLLMに書かせてみました。`pip install python-fasthtml` だけで準備完了。Pythonの関数でHTMLを組み立てる、テンプレート不要のスタイル。それが動きました。
少し驚きました。以前は複雑なフレームワークをLLMに任せると必ずどこかが壊れていたのに、整合性のあるコードが出てくる。ルーティング、HTMXによる部分更新、レスポンス——複数の要素があっても、ちゃんと全体がつながっている。
LLMが変わっていたのです。
そして気づきました。Streamlitを選んだ最大の理由——「LLMが壊しにくいから」——が、いつの間にかなくなっていたことに。
フレームワークを選び直す
制約がなくなった今、純粋に「何で作りたいか」で選べるようになりました。
改めてStreamlit・Flask・FastHTML・Reflex・NiceGUIを並べて比較してみます。

Streamlit
言わずと知れたデータアプリ向けフレームワーク。コード数行でUIが立ち上がる手軽さは今でも魅力的です。ただ、前述のとおり、本格的なアプリに育てようとすると限界を感じる場面がありました。
Flask
Pythonの定番Webフレームワーク。自由度は高いですが、HTMLテンプレートの管理が必要です。LLMにテンプレートとPythonコードの両方を整合性を持って書かせるのは、少し手間が増える印象でした。
FastHTML
Pythonの関数でHTMLを直接組み立てるフレームワーク。テンプレートファイルが不要で、PythonだけでUIが書けます。HTMXによるDOM部分更新に対応しており、ページ全体をリロードせずに必要な部分だけを更新できます。Streamlitの不満だった「フレームワークの主張が強い見た目」がなく、完全に自分のアプリとして作れます。`pip install python-fasthtml` だけで完結し、起動も速い。自分にはこれで十分でした。今回、移行先に選びました。
Reflex
PythonだけでReactベースのリッチなUIを作れるフレームワーク。ただし、裏側でNode.jsとBunが必要になります。Python完結で開発したかった自分にとっては、ここがネックでした。
NiceGUI
`pip install nicegui` だけで完結するUIフレームワーク。裏側ではVue/Quasarが動いていますが、開発者がNode.jsを意識する必要はありません。イベント駆動で動作が軽く、GitHub Stars 15K超とコミュニティも活発です。FastHTMLより高機能なUIが必要な場合は、こちらも良い選択肢だと思います。

移行してみた
自分の場合はStreamlitで作っていた小規模なツールをFastHTMLに移行しました。
楽だった点
ロジック部分はほぼそのまま使えた。
データの処理やAPI呼び出しなど、バックエンドのロジックはフレームワークに依存していない部分が多かったので、そのまま持っていけました。
LLMが移行を手伝ってくれた。
「このStreamlitのコードをFastHTMLに書き換えて」とプロンプトを投げるだけで、だいたい動くコードが返ってきました。これは当時のLLMでは考えられなかったことです。
Webの標準的な書き方に近い。
FastHTMLはPythonの関数でHTMLを組み立てるので、やっていることはWebの基本に忠実です。session_stateのようなフレームワーク独自の概念を覚える必要がなく、LLMに書かせても安定しやすいと感じました。
Before/After
Streamlit
import streamlit as st
st.title("カウンター")
if "count" not in st.session_state:
st.session_state.count = 0
if st.button("カウントアップ"):
st.session_state.count += 1
st.write(f"現在のカウント: {st.session_state.count}")FastHTML
from fasthtml.common import *
app, rt = fast_app()
count = 0
@rt("/")
def get():
return Titled("カウンター",
P(f"現在のカウント: {count}"),
Button("カウントアップ", hx_post="/increment", hx_target="body")
)
@rt("/increment")
def post():
global count
count += 1
return get()
serve()移行に使えるプロンプト
自分が実際にLLMに渡して使ったプロンプトを共有します。コピペして使ってください。
Streamlit → FastHTML
このワークスペースのWebフレームワークをStreamlitからFastHTMLに変更してください。
元のアプリの機能と見た目をできるだけ再現してください。まとめ
Streamlitは悪いフレームワークではありません。自分にとっても、AIコーディングの入り口として本当に助けられました。
ただ、LLMの能力が上がった今、「LLMが壊しにくいから」という理由だけでフレームワークを選ぶ必要はなくなりました。カスタマイズ性、動作の軽さ、UIの自由度——純粋に自分が作りたいものに合った道具を選べる時代になっています。
この記事が対象にしているのは、個人開発や小規模なツール開発です。大規模なプロダクションの話ではありません。
ちなみに、会社で使うアプリはFlaskやDjangoのような実績のあるフレームワークを採用しています。チームでの開発やメンテナンス性を考えると、無難な選択が正解になる場面もあります。個人開発だからこそ、自分の好みや体験を優先してフレームワークを選べるのだと思います。
もしStreamlitで「ちょっと物足りない」と感じているなら、一度別のフレームワークを試してみてください。LLMが移行を手伝ってくれるので、思ったより簡単に始められるはずです。