FastAPIのapp.frontend()入門:APIとSPAを同じアプリで配信する設計

FastAPIのAPI層と静的フロントエンド配信が同じ基盤で連携する構成を表した抽象図

FastAPIには、ReactやVueなどが出力した静的ファイルをAPIと同じアプリケーションから配信するapp.frontend()があります。
数行で導入できますが、単に配信先を一つにする機能ではありません。
APIルートの優先順位、SPAのフォールバック、認証、ビルド工程まで同じ構成の中で管理する仕組みです。

小規模な業務システムや管理画面では、フロントエンドとAPIの公開単位をそろえられます。
一方、SSRが必要なサイトや、静的ファイルをCDNから大規模配信するサービスでは、別の構成が適します。
この記事では、採用できる条件と見送る条件を実装例から確認します。

app.frontend()で変わった静的配信

app.frontend()は、フロントエンドのビルド成果物を配信するFastAPIの仕組みです。
FastAPI公式リリースノートによると、app.frontend()router.frontend()は0.138.0で追加されました。
0.139.0では依存関係が適用できるようになり、0.141.0では開発環境に応じたディレクトリ確認が加わっています。

同じリリースノートには、0.139.1でドットを含むパスのフォールバック、0.141.1で依存関係から設定するレスポンスヘッダーとバックグラウンドタスクの修正も記録されています。
依存関係やフォールバックを使う新規実装では、古い導入版に固定せず、少なくともこれらの修正を含む版以降で動作を検証するのが安全です。

最小構成でSPAを配信する

フロントエンド側でビルドを実行し、distディレクトリにindex.htmlとJavaScriptなどを出力したとします。
FastAPI側の最小設定は次のとおりです。

from fastapi import FastAPI

app = FastAPI()

@app.get("/api/health")
def health() -> dict[str, str]:
    return {"status": "ok"}

app.frontend("/", directory="dist", fallback="index.html")

FastAPIのFrontend公式ガイドでは、通常のパスオペレーションを先に照合し、一致しない場合だけフロントエンドのファイルを探すと説明しています。
この例では/api/healthがAPIとして処理され、/assets/app.jsdist/assets/app.jsから返されます。

fallback="index.html"は、/dashboard/settingsのようなクライアント側ルーティングのパスを直接開いたときに使います。
ただし、フォールバックの対象はHTMLを受け入れるGETまたはHEADです。
存在しないJavaScript、CSS、画像までindex.htmlに置き換わるわけではなく、それらは404になります。
POSTPUTも、通常ルートに一致しなければ404です。

認証をAPIRouterの依存関係で適用する

社内画面や会員画面では、静的ファイルにも認証条件を適用したい場合があります。
app.frontend()のレスポンスには、FastAPIアプリケーション、APIRouterinclude_router()で設定した依存関係が適用されます。
特定の画面群だけを保護するなら、専用のルーターへ依存関係を設定すると範囲を読み取りやすくできます。

from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request

app = FastAPI()

def require_session(request: Request) -> None:
    if request.cookies.get("session") is None:
        raise HTTPException(status_code=401)

frontend = APIRouter(dependencies=[Depends(require_session)])
frontend.frontend(
    "/",
    directory="dist",
    fallback="index.html",
)
app.include_router(frontend, prefix="/app")

この構成では、/app以下のフロントエンド配信にセッション確認を適用できます。
HTTPミドルウェアも通常のFastAPIレスポンスと同様に働くため、セキュリティヘッダーやアクセスログの方針をそろえられます。

ただし、HTMLを認証で保護しても、フロントエンドのJavaScriptに秘密情報を埋め込んではいけません。
ブラウザへ配信するファイルは利用者が取得できるため、APIキーやサーバー用資格情報はバックエンドだけで管理します。
Cookie認証で更新系APIを提供する場合は、SameSite属性やCSRF対策も別途設計します。

StaticFilesとの使い分け

FastAPIのStatic Files公式ガイドは、フロントエンドをホストする用途にはapp.frontend()を使うよう案内しています。
app.frontend()は内部でStaticFilesを利用しつつ、クライアント側ルーティングなど、フロントエンド配信に必要な挙動を追加しています。

app.frontend()とStaticFilesの主な使い分け
確認項目 app.frontend() StaticFiles
主な用途 ビルド済みのSPAや静的サイト 画像、CSS、配布ファイルなどの静的資産
クライアント側ルーティング index.htmlへのフォールバックを扱える 用途に応じて別の実装が必要
FastAPIルートとの関係 通常ルートが優先される 指定パスへ独立したASGIアプリケーションとしてマウントする
向いている例 管理画面とAPIを一つの公開単位にする /static以下の共通資産を配信する

既存のStaticFiles構成が正常に動いているなら、機械的な置き換えは不要です。
SPAの直リンクで404になる、認証やミドルウェアの境界が複雑になっているといった課題がある場合に、移行の効果を評価します。

同じアプリで配信するかを決める基準

配信構成は、コード量の少なさだけでは決められません。
フロントエンドとAPIの更新周期、アクセス量、キャッシュ、障害の分離を比べます。

構成を選ぶときの目安
要件 判断の目安
小規模な社内ツールや管理画面 同じアプリでの配信が候補になる。認証とリリースをまとめやすい
APIと画面を常に同時公開する 一つの成果物にまとめる利点が出やすい
フロントエンドだけを頻繁に更新する 別ホスティングのほうが独立して公開しやすい
静的資産の配信量が多い オブジェクトストレージとCDNを含む分離構成を検討する
リクエストごとのSSRが必要 app.frontend()の対象外。SSRを実行できる基盤を選ぶ

公式ガイドが示すとおり、app.frontend()は生成済みの静的ファイルを配信する機能であり、サーバーサイドレンダリングを実行しません。
検索流入を重視する公開サイトでは、静的生成で要件を満たせるか、リクエストごとのSSRが必要かを先に決めます。

API契約そのものの設計は、FastAPIでAPIを作るときの設計ポイントで確認できます。
ファイル分割や責務の置き場所は、FastAPIの保守しやすいプロジェクト構成が参考になります。

本番公開前に確認する項目

app.frontend()を採用したら、次の確認を自動テストとデプロイ手順へ組み込みます。

  • フロントエンドのビルドがFastAPIの起動前に完了し、指定したディレクトリが成果物へ含まれている
  • APIの既知パスがJSONを返し、未知のAPIパスがHTMLへ誤ってフォールバックしない
  • SPAの画面URLを直接開いたときにindex.htmlが返る
  • 存在しないJavaScript、CSS、画像が404を返す
  • 未認証時と認証済み時の画面配信が設計どおりに分かれる
  • HTMLとハッシュ付き静的資産に、更新方針に合うキャッシュ設定が適用される
  • プロキシ配下で公開する場合、パスの接頭辞とアセットURLが一致する

check_dir="auto"では、fastapi devが設定する開発環境でディレクトリがなくても警告にとどまり、それ以外の環境ではアプリケーション作成時にエラーになります。
開発中にフロントエンドのビルド前でもバックエンドを起動できる一方、本番では成果物の欠落を早く検出できます。
CIでは本番と同じ環境条件でも起動テストを行い、開発時の警告だけで見逃さないようにします。

よくある質問

app.frontend()を使えばCORS設定は不要ですか

画面とAPIを同じオリジンから配信し、ブラウザが同一オリジンへだけアクセスする構成なら、通常はCORSによる許可は不要です。
別ドメインのAPIや外部サービスへブラウザから接続する場合は、その通信先との関係に応じてCORSを設定します。

APIの404がindex.htmlに置き換わりませんか

フォールバックはHTMLを受け入れるブラウザナビゲーション向けに限定されています。
それでも、APIクライアントのAcceptヘッダーとパス設計を含め、未知のAPIパスが期待する404を返すかテストします。
APIを/api以下へまとめると、監視やプロキシ設定でも境界を示しやすくなります。

フロントエンドを別サーバーへ分ける必要はなくなりますか

更新周期、負荷、キャッシュ、障害範囲を分けたいサービスでは、別配信の利点が残ります。
app.frontend()は選択肢を増やす機能であり、CDNや専用フロントエンド基盤を一律に置き換えるものではありません。

参照した一次資料

この記事に関連する株式会社greedenの取り組み

FastAPIで画面とAPIをまとめる判断には、認証、配信経路、保守まで含めた設計が必要です。
株式会社greedenは、要件定義からフロントエンド、バックエンド、テスト、運用までWebシステム開発を一気通貫で支援しています。

投稿者 greeden Inc.

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)