【React不要】Pythonだけで爆速SPAが作れる『FastHTML』が革命的すぎる 〜Streamlit卒業式〜

本ページはプロモーションが含まれています
国内のAI狂い

やっほー!国内のAI狂いだよ!✨

ついに人類は「JavaScript」という呪縛から解き放たれました。Pythonだけでスマホアプリみたいにサクサク動くWebツール、作っちゃおう!

目次

Streamlit卒業式会場はこちらです🌸

みんな~!Webアプリ作ってる~!?(挨拶)

PythonでWebアプリといえば、今までは「Streamlit」一択だったよね。
確かに簡単。データ可視化なら最強。でもさ…心のどこかでモヤモヤしてなかった?

  • 「ボタン押すたびに画面全体が再読み込みされて、動作が重い…」
  • 「デザインが全部同じような見た目になっちゃう…」
  • 「結局、自分だけのオリジナルツール感が出せない…」

わかる。首がもげるほどわかる。🦒
かといって、今からReactとかNext.jsみたいな「Web業界の難しい技術」を勉強したくないじゃん?
環境構築でnpm installのエラー見て絶望したくないじゃん?

そんな全Python民に朗報です。

『FastHTML』が、その悩み全部吹き飛ばしてくれたよ!!🚀

今回作ったもの:ドラッグ&ドロップで背景が消える魔法のツール

論より証拠!
今回、JavaScriptを1行も書かずに作ったWebアプリがこちら!👇

FastHTMLで作ったAI背景削除アプリのGUI
これがPythonファイル1つで動いてるなんて信じられる?

画像をドラッグ&ドロップすると、サーバー側のPython(rembgというAIライブラリ)が動いて、一瞬で背景を消し去ってくれます。
この「モダンな見た目」も「画面がチラつかずに結果が出る動き(非同期通信)」も、全部Pythonコードの中に書いただけ!!🤯

ビフォーアフターがこれ✨

背景削除のビフォーアフター
精度も爆高!しかも処理が爆速!

開発ログ:踏み抜いた地雷たち💣(ここが重要!)

「すごい!簡単そう!」って思った?
甘いよ!!🍫
最新技術すぎてネットに日本語の情報が全然なくて、地雷原をタップダンスする羽目になったからね!😭

みんなが爆死しないように、私が実際に遭遇したエラーと解決策を共有しておくよ!これが一次情報の価値だ!!

地雷1:インストールの名前が違う罠 🕳️

素直にpip install fasthtmlって打つと、全く関係ない別のライブラリがインストールされて、プログラムが動きません。
正解は「python-」を付けること!👇

pip install python-fasthtml

これのせいで最初の30分溶かした。紛らわしいよ!

地雷2:Containerクラスの反乱 ⚔️

画面をいい感じに中央寄せしてくれるContainer()っていう便利機能があるんだけど、これに自分でデザイン(CSSクラス)を追加しようとすると…
「TypeError: … got multiple values for keyword argument ‘cls’」
みたいなエラーが出て怒られます。

これは「Container君が勝手にクラス名を決めてるのに、人間が別のクラス名を指定したから喧嘩した」状態。
解決策は、Containerを使わずに普通のMainDivを使って、自分でcls="container ..."って書くこと。優等生タイプは融通がきかないね!

地雷3:沈黙のファイル入力 🤐

これが一番のハマりポイント!!
普通のテキスト入力欄は、文字を打てばFastHTMLが気づいてくれるんだけど、input type="file"(ファイル選択ボタン)だけは、ファイルを選んでも無視されます。

Webブラウザの仕様で「ファイルを選んだだけで勝手に送信するのは危険かも?」みたいな配慮があるせいなんだけど、ここでは邪魔!
なので、明示的に「変更されたら(change)、すぐに送信して(trigger)!」という命令を書かないと動きません。

# これがないと永遠にアップロードされない魔法の言葉
hx_trigger="change"

全コード公開(コピペで動くよ!)🐍

はい、お待たせしました。
地雷をすべて除去し、洗練された「完全版コード」を配布します🎁
これをmain.pyって名前で保存して実行するだけで、あなたのPCが「AI画像加工スタジオ」になるよ!

必要なライブラリ:

pip install python-fasthtml rembg python-multipart

main.py:

# 必要なライブラリ: python-fasthtml, rembg, python-multipart
# インストール: pip install python-fasthtml rembg python-multipart

print("🔄 ライブラリを読み込み中...(ここが長いかも?)")
from fasthtml.common import *
print("✅ FastHTML 読み込み完了!")

print("🔄 AIモデル(rembg)を準備中...")
from rembg import remove
from PIL import Image
import io
import base64
import webbrowser
print("✅ rembg 読み込み完了!")

# アプリ初期化
# live=True にすると、ファイルを保存するたびに自動で更新されるよ!
app, rt = fast_app(live=True)

# --- スタイル定義 ---
# ここにCSSを書くと、アプリ全体に適用されるよ
css = Style("""
    /* 通信中のローディング表示制御 */
    .htmx-indicator { display: none; }
    .htmx-request .htmx-indicator { display: inline-block; }
    .htmx-request.htmx-indicator { display: inline-block; }
    
    /* ドラッグ&ドロップエリアの見た目 */
    .drop-area {
        border: 3px dashed #cbd5e1;
        transition: all 0.3s ease;
    }
    .drop-area:hover {
        border-color: #ec4899;
        background-color: #fff1f2;
    }
""")

# --- ヘルパー関数 ---

def image_to_base64(img_bytes):
    """画像のバイナリデータを、HTMLで表示できる文字データに変換する魔法"""
    return base64.b64encode(img_bytes).decode('utf-8')

# --- コンポーネント(画面の部品) ---

def result_card(original_b64, processed_b64, filename):
    """結果を表示するカード型の部品"""
    return Div(
        H3(f"✨ 完了: {filename}", cls="text-lg font-bold text-gray-700 mb-2"),
        Grid(
            Div(
                P("Before", cls="text-center font-bold text-gray-500"),
                Img(src=f"data:image/png;base64,{original_b64}", cls="w-full rounded shadow-sm"),
            ),
            Div(
                P("After", cls="text-center font-bold text-pink-500"),
                Img(src=f"data:image/png;base64,{processed_b64}", cls="w-full rounded shadow-lg bg-checkered"),
            )
        ),
        # ダウンロードボタン
        A(
            Button("💾 保存する", cls="w-full mt-4 bg-pink-500 hover:bg-pink-600 text-white font-bold py-2 px-4 rounded"),
            href=f"data:image/png;base64,{processed_b64}",
            download=f"bg_removed_{filename}",
            cls="block mt-2"
        ),
        cls="p-6 bg-white rounded-xl shadow-md border border-gray-100 mb-6 fade-in"
    )

# --- ルーティング(画面の設計図) ---

@rt('/')
def get():
    return Titled("🐹 AI画像背景削除スタジオ",
        css,
        Main(
            Div(
                H1("🚀 AI Background Remover", cls="text-4xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-pink-500 to-violet-500 mb-2"),
                P("画像をアップロードすると、AIが一瞬で背景を消し去ります。", cls="text-gray-600"),
                cls="text-center py-10"
            ),
            Form(
                Div(
                    Div(
                        I(cls="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"),
                        P("ここに画像をドラッグ&ドロップ", cls="text-lg font-medium text-gray-700"),
                        P("またはクリックしてファイルを選択", cls="text-sm text-gray-500"),
                        cls="text-center pointer-events-none"
                    ),
                    # ★ここが重要!ファイル入力の設定
                    Input(
                        type="file", 
                        name="file", 
                        accept="image/*",
                        # z-50で一番手前に表示して、クリックしやすくする
                        cls="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-50",
                        hx_post="/upload",        # アップロード先のURL
                        hx_target="#results",     # 結果を表示する場所
                        hx_swap="afterbegin",     # 新しい結果を上に追加
                        hx_indicator="#loading",  # 通信中に出すマーク
                        hx_encoding="multipart/form-data",
                        # ★ファイル変更時に送信するためのトリガー
                        hx_trigger="change"
                    ),
                    cls="drop-area relative w-full py-16 bg-gray-50 rounded-2xl cursor-pointer"
                ),
                cls="max-w-xl mx-auto mb-10"
            ),
            # ローディング中のぐるぐるマーク
            Div(
                Div(cls="animate-spin rounded-full h-10 w-10 border-b-2 border-pink-500 mx-auto"),
                P("AIが画像を認識中... 🐹💦", cls="text-center text-pink-500 mt-2"),
                id="loading",
                cls="htmx-indicator mb-10"
            ),
            # 結果が表示される空箱
            Div(id="results", cls="max-w-4xl mx-auto"),
            
            cls="container max-w-5xl mx-auto px-4 font-sans"
        )
    )

@rt('/upload')
async def post(file: UploadFile):
    """画像が送られてきたときに動く関数"""
    if not file: return
    print(f"📸 画像を受信しました: {file.filename}")
    
    content = await file.read()
    filename = file.filename
    
    # ここでAIが背景を削除!
    processed_data = remove(content)
    print("✨ 背景削除完了!")
    
    # HTMLで表示できるように変換
    original_b64 = image_to_base64(content)
    processed_b64 = image_to_base64(processed_data)
    
    # 結果カードを作って返す
    return result_card(original_b64, processed_b64, filename)

if __name__ == "__main__":
    PORT = 8000
    print(f"🚀 サーバーを起動します: http://localhost:{PORT}")
    try:
        webbrowser.open(f"http://localhost:{PORT}")
    except:
        pass
    serve(port=PORT)

まとめ:Pythonユーザーは今すぐFastHTMLを触るべき!🏃‍♀️💨

使ってみてわかったけど、FastHTMLは「ただのWebフレームワーク」じゃないね。
Pythonユーザーが長年求めていた「フロントエンドの民主化」だよこれは!🗽

HTML/CSS/JSの複雑なパズルを解かなくても、Div()とかButton()ってPythonのコードで書くだけで、オシャレな画面ができちゃう。
しかも裏側で勝手に賢い通信(HTMX)をしてくれるから、「スマホアプリみたいな滑らかな動き」が自動で完成してるの。

これからは「便利ツール作ったら、FastHTMLで画面をつけて配布」が当たり前になる予感しかない!
みんなも今のうちに触っておいて、時代の波に乗っちゃおうね~!🏄‍♀️🌊

それじゃ、また面白いエラー踏み抜いたら報告するね!
バイバイ~!👋🐹

よかったらシェアしてね!
  • URLをコピーしました!
目次