連載の現在地:Phase 1「公開」(1 / 4本目) — まずは小さく安全に世に出す
1. 公開 → 2. 費用と安定 → 3. 安全 → 4. 品質 → 5. 自動化
こんな悩みの人へ
手元ではAIアプリが動いた。でも、公開のためにサーバーを借りて設定するのは気が重い。公開後のOSの更新やセキュリティ対応まで面倒を見られる気がしないし、そもそもAPIキーをブラウザ側に置いてよいのかも分からない。そんな方に向けた記事です。
結論
LLMのAPIを呼び出して結果を返すタイプのAIアプリなら、Cloudflare Workersで公開するのがおすすめです。サーバーのOSやミドルウェアを管理する必要がなく、APIキーは「シークレット」としてサーバー側だけに持たせられます。無料プランで始められ、アクセスが増えたら月額5ドルからの有料プランに切り替えます。
ただし、PythonのライブラリやGPUに依存する処理を長時間動かすアプリには向きません。その場合は後述の別の方法を選びます。
データの流れは次のとおりです。APIキーは、利用者の手に渡らない場所にだけ置きます。
[ブラウザ] --質問だけ--> [Workers(あなたのコード)] --APIキー付き--> [OpenAI API]
APIキーはここ(シークレット)にだけ置くなぜ「公開」がつまずきやすいのか
公開でつまずく原因は、大きく3つあります。
まず、サーバーの面倒を見る仕事が、公開後もずっと続きます。VPSなどを借りると、OSの更新や証明書の更新、不正アクセスへの対応までが自分の仕事になり、アプリを作ることより維持することに時間を取られます。
次に、APIキーの置き場所に困ります。ブラウザだけで動くアプリにすると、APIキーを利用者に渡すのと同じことになります。キーをサーバー側に置く仕組みが、最低限必要です。
そして、アクセスの増減が読めません。最初はほとんど使われず、ある日急に使われる、という波があります。固定のサーバーは、どちらの状態でも費用がかかります。
Workersはリクエストが来たときだけコードを動かすので、この3つをまとめて小さくできます。
解決策の比較
方法 | 費用の目安 | 手間 | 向いている状況 |
|---|---|---|---|
Cloudflare Workers | 無料から。有料は月5ドル〜 | 少ない | API呼び出しが中心のアプリ。JavaScript/TypeScriptで書ける |
Vercel・Netlifyなど | 無料枠あり。商用利用の条件はサービスごとに異なる | 少ない | Next.jsなどフロントエンドのフレームワークが中心 |
Cloud Run・Amazon ECS Express Modeなど | 実行基盤や周辺リソースの利用量に応じた料金 | 中くらい | Pythonなど既存のコードをコンテナのまま動かしたい |
VPS・自前サーバー | 月数百円〜 | 多い | GPUや常駐プロセスが必要。OSまで自分で管理できる |
どれを選ぶか
APIを呼んで結果を返すだけで、TypeScriptで書けるなら、Workersが第一候補です。Next.jsで画面まで作り込んでいる場合は、Vercelなども有力です。Workersでも動かせますが、手間はフレームワーク次第です。
ネイティブ拡張を使うPythonライブラリや、既存のコンテナ環境が必要なら、Cloud Runなどのコンテナを選びます。処理に数分かかる場合は、通信を待っているだけなのか、CPUを使い続ける計算なのかを分けて判断してください。GPUで自前のモデルを動かすなら、GPU付きのクラウドやVPSになります。
手順
前提:Cloudflareのアカウント(無料で作成できます)と、Node.js 22.19以降が入ったパソコン。
- TypeScriptのWorkerプロジェクトを作ります。以下はテンプレートと言語を引数で指定する例です。途中でデプロイを尋ねられたら、コードを設定するまで行わずに進めます。
npm create cloudflare@latest -- my-ai-app --type=hello-world --lang=ts
cd my-ai-app- LLMのAPIキーを、シークレットとして登録します。コードにもGitにも書きません。
npx wrangler secret put OPENAI_API_KEYローカルで試すときは、プロジェクト直下の .dev.vars に同じ名前で書きます。このファイルは .gitignore に入れてください。
src/index.tsを書き換えます。ブラウザから受け取った質問を、サーバー側でLLMに渡して返す例です。モデルは軽い処理向けの GPT-6 Luna、接続方式は新規実装で推奨される Responses API を使います。
export interface Env {
OPENAI_API_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== "POST") {
return new Response("Method Not Allowed", { status: 405 });
}
const { question } = (await request.json()) as { question?: string };
if (typeof question !== "string" || !question.trim() || question.length > 2000) {
return new Response("Bad Request", { status: 400 });
}
const res = await fetch("https://api.openai.com/v1/responses", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-6-luna",
input: question,
reasoning: { effort: "none" },
max_output_tokens: 1024,
store: false,
}),
});
if (!res.ok) {
return new Response("Upstream error", { status: 502 });
}
const data = (await res.json()) as {
status: string;
output: Array<{
type: string;
content?: Array<{ type: string; text?: string }>;
}>;
};
const answer = data.output
.filter((item) => item.type === "message")
.flatMap((item) => item.content ?? [])
.filter((part) => part.type === "output_text")
.map((part) => part.text ?? "")
.join("");
if (data.status !== "completed" || !answer) {
return new Response("Upstream response incomplete", { status: 502 });
}
return Response.json({ answer });
},
};上記は2026-11-03時点のOpenAIのモデル仕様に基づく例です。Responses APIのHTTP応答では、回答を output 配列から取り出します。SDKを使う場合は response.output_text で取得できます。
このコードには、意図して入れている処理がいくつかあります。質問を2,000文字までに制限しているのは、長文を送り付けられても、1回あたりの入力トークンと費用が膨らまないようにするためです。料金は出力トークンにもかかるので、max_output_tokens で回答の長さにも上限を設けています。
status が completed 以外のときに回答を返さないのは、出力の上限に達して途中で終わった回答などを、完成したものとして見せないためです。また、失敗時にLLMの応答をそのまま返さないのは、上流のエラー内容に、利用者に見せる必要のない内部の情報が含まれることがあるからです。利用者には 502 だけを返し、詳細はログで確認します。
- 手元で動かして確認します。
npx wrangler dev
# 別のターミナルで
curl -X POST http://localhost:8787 -H "Content-Type: application/json" -d '{"question":"こんにちは"}'- 公開します。表示された
*.workers.devのURLで、すぐに使えます。
npx wrangler deploy画面(HTML)も一緒に出したい場合
ここまではcurlで確かめましたが、利用者にはブラウザの画面から使ってもらいます。プロジェクトに public/index.html を置き、wrangler.jsonc に次の1項目を足すと、同じURLから画面も配信できます(Static Assets)。静的ファイルへのリクエストは無料で、回数の制限もありません。
{
"assets": { "directory": "./public" }
}画面とAPIを同じWorkerで出すときは、APIのパスを /api/ask のように分けておくと役割がはっきりします。src/index.ts の fetch の先頭に次の判定を足し、手順4のcurlの宛先も http://localhost:8787/api/ask に変えます。
if (new URL(request.url).pathname !== "/api/ask") {
return new Response("Not Found", { status: 404 });
}画面側は、最小なら次の形です。
<!-- public/index.html(最小例) -->
<textarea id="q"></textarea>
<button id="send">送信</button>
<pre id="out"></pre>
<script>
document.getElementById("send").onclick = async () => {
const res = await fetch("/api/ask", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ question: document.getElementById("q").value }),
});
document.getElementById("out").textContent = res.ok
? (await res.json()).answer
: "エラーが発生しました。時間をおいてお試しください。";
};
</script>/ を開くと public/index.html が表示され、送信ボタンで /api/ask のWorkerが呼ばれます。画面を公開すると誰でも使える入口になるため、「つまずきやすい点」で触れている、公開したURLの扱いもあわせて確認してください。
更新日:2026-11-03
つまずきやすい点
Workers Freeでは、HTTPリクエスト1件あたりのCPU時間の上限が10ミリ秒です。外部LLMへの通信を待つ時間は含みませんが、送受信するJSONの解析や文字列の加工など、Workersの中でコードを実行している時間は含みます。重い文字列処理や画像処理をWorkersの中で行う場合は、有料プランを検討してください。
Node.jsのライブラリが、そのままでは動かないこともあります。設定ファイル(wrangler.jsonc など)の compatibility_flags に nodejs_compat を加えると、動く範囲が広がります。
特に注意したいのは、公開したURLは誰でも叩けるという点です。このままでは、他人にAPIの利用料を使われるおそれがあります。利用者を限定するか、ボット対策と回数制限を入れてください(方法は「次に出やすい悩み」で紹介します)。
なお、独自ドメインで公開するには、そのドメインをCloudflareで管理しているか、DNSの設定が必要です。


次に出やすい悩み
公開ができると、次は「誰に見せるか」と「使われすぎたときの費用」が問題になります。まず関係者だけに触ってもらいたいなら「デモを社内や特定の人だけに見せたい」が役に立ちます。画像やデータベースも足したくなったら「個人開発のホスティング費用を抑えたい」を、公開したURLが叩かれ続けて請求が怖くなったら、Phase 2の「LLM APIの請求額が読めない」を読んでみてください。
「自分のアプリはWorkersで動くのか」「Pythonのままコンテナにすべきか」といった、公開方法を決める前の段階でもご相談いただけます。Cloudflareインフラ構築をご覧ください。Workersが合わない場合は、ほかの方法をご提案します。
よくある質問
Q. どのようなAIアプリでもWorkersで公開できますか?
本文が扱うのは、利用者の入力を受け、外部のLLM APIを呼んで結果を返すタイプのアプリです。利用するライブラリの互換性やCPU時間の制限は確認が必要です。Pythonの既存環境やGPU、重い計算への依存が強い場合は、コンテナなど別の実行基盤も比較します。
Q. LLMのAPIキーは、ブラウザの環境変数に入れてもよいですか?
ブラウザへ配信されるコードや公開用の環境変数には入れません。本文の構成では、APIキーをWorkerのシークレットとして登録し、LLMへの通信をサーバー側で行います。シークレットの扱いはWorkersの公式資料を確認してください。
Q. ホスティングを無料枠に収めれば、LLMの利用料も無料になりますか?
ホスティングと外部LLM APIの料金は別です。Workersの利用が無料枠内でも、選んだLLMの料金条件に応じて費用が発生します。利用回数、入力・出力の長さ、予算の上限を別に管理し、提供元の料金と利用量を確認します。
Q. 公開前に最低限、何を確認すればよいですか?
APIキーを画面へ渡していないこと、空入力や長文を処理できること、API失敗時に利用者へ状況を伝えられることを確認します。さらに、誰が利用できるかを決め、認証やボット対策、利用量の上限を設定します。URLで開けることと、業務で安全に提供できることは別々に確認してください。
執筆者情報
IT顧問、AI・業務アプリ開発、SESを通じて、企業のIT活用を支援しています。代表の藤澤 昌隆は、2007年からシステム開発・運用に携わり、アクセンチュアでの業務ITコンサルティングを経て、2019年にパブロフ株式会社を設立しました。
AI活用の支援事例として、営業工程を支える7つの業務別AIアプリの構築を公開しています。アプリの開発に加え、入力・出力の形式、評価基準、テストケースの整備にも取り組んでいます。
記事の内容に関するご質問や誤りのご指摘は、お問い合わせ窓口へお寄せください。
