wrangler 使い方 - Cloudflare Workers のローカル開発とデプロイ
要約: wranglerのインストールと型設定、wrangler devでworkerdを立ち上げる方法、そしてデプロイ。wrangler devがフロントエンドの開発サーバーではないという点から。

概要
この記事は Cloudflare Workers 静的サイトガイド - デプロイからSEOまで シリーズの一部です。
Cloudflare Workersを扱う仕事は、実質的にwrangler を扱う仕事です。ローカルで立ち上げるのも、デプロイするのも同じCLIです。
ところが名前のせいで誤解が一つ生まれます。wrangler dev はフロントエンドの開発サーバーではありません。 筆者も最初はここでしばらく悩みました。
この記事で扱うこと
- 何をインストールすべきか — そしてランタイム依存は増えないということ
- Workerコードの型設定を分離すべき理由
wrangler devが実際に何をするのか ·vite devとの違い- なぜ静的サイトのビルドが先でなければならないのか
- Workerがレスポンスを直したか確認する方法
- デプロイ — 環境ごとの値の注入 · CIで動かすときの注意点
扱わないこと
Workers Static Assetsの構造とリクエストの流れは Cloudflare Workers 静的サイトホスティング - リクエストの流れと課金基準 にあります。
前提
Cloudflareのアカウントがあり、静的サイトをビルドできること(npm run build → dist/)以外に必要な事前知識はありません。
wrangler のインストール
CloudflareのCLIです。ローカル実行とデプロイの両方を担当します。コマンド一覧は Commands ドキュメント にあります。
インストールしないという選択肢
wrangler はデプロイツールであってアプリケーションコードではありません。 そのため依存に入れず、必要なときだけダウンロードして使うこともできます。
1# As a devDependency
2npm i -D wrangler
3npx wrangler dev
4
5# Or without installing - pin the major version
6npx wrangler@4 dev
7pnpm dlx wrangler@4 dev
後者の利点は、ローカルとCIが同じバージョンを使い、アプリの package.json にデプロイツールが入らないことです。欠点は毎回ダウンロードの時間が少しかかることです。
どちらにせよランタイム依存は増えません。 WorkerはCloudflareが実行します。
型は別パッケージです
Fetcher・HTMLRewriter・ExecutionContext・caches のようなWorkersのグローバルは型パッケージから来ます。
1npm i -D @cloudflare/workers-types
型設定を分離すべき理由
一つのプロジェクトの中に実行環境が2つあることで生じる問題です。
1src/ runs in the browser has document · no HTMLRewriter
2cloudflare/workers/ runs on workerd no document · has HTMLRewriter
使えるグローバルが互いに正反対なのに、TypeScriptはファイルを見ただけではどちらなのか分かりません。tsconfigに書いたものしか知りません。そのため「どのフォルダがどの環境なのか」を教える必要があり、そのためには設定が2つ必要です。
ちなみにWorkersはNodeではありません。
fs・processのようなNode APIもなく、ブラウザのdocumentもない第三のランタイムです。そのため既存のNode用設定をそのまま使うこともできません。
設定が一つだけなら、たいていはブラウザ基準です。その設定でWorkerコードまで検査すると、以下のコードがコンパイルを通ってしまいます。
1export default {
2 async fetch(request: Request): Promise<Response> {
3 // There is no document in a Worker. This still type-checks.
4 const el = document.getElementById('root');
5 return new Response(el?.textContent ?? '');
6 },
7};
デプロイすると document is not defined で落ちます。型検査が捕まえるべきだったものを、ランタイムまで先送りしたことになります。
逆方向もあります。@cloudflare/workers-types を入れなければ、HTMLRewriter・ExecutionContext・caches がすべて「名前が見つかりません」になります。
一つの設定に両方を詰め込むのも答えではありません。DOMとWorkersの型を一緒に入れると、Request・Response・caches のように両側に同じ名前が違う形で存在するものが混ざり、見当違いの型が当たってしまいます。
そこで設定を分け、project referenceで束ねます。各設定が決めるのは3つです。
| 決めるもの | |
|---|---|
include | このルールをどのフォルダに適用するか |
lib | 標準環境に何があるとみなすか("DOM" → window・document) |
types | 追加のグローバルパッケージ(@cloudflare/workers-types → HTMLRewriter・caches) |
1// tsconfig.worker.json
2{
3 "extends": "./tsconfig.node.json",
4 "compilerOptions": {
5 // No DOM. Workers globals instead.
6 "types": ["@cloudflare/workers-types"]
7 },
8 "include": ["src/worker"]
9}
1// tsconfig.json
2{
3 "files": [],
4 "references": [
5 { "path": "./tsconfig.app.json" },
6 { "path": "./tsconfig.worker.json" }
7 ]
8}
ローカルで立ち上げる — 最も紛らわしい部分
wrangler dev はフロントエンドの開発サーバーではありません
名前が dev なので vite dev のようなものと誤解しやすいのですが、やることがまったく違います。
wrangler dev はCloudflareのエッジとまったく同じランタイム(workerd)をローカルにWebサーバーとして立ち上げます。 実際にデプロイされればCloudflareのデータセンターで動くそのプログラムを、自分のコンピューターでそのまま動かすのです。HTMLRewriter・caches・env.ASSETS がすべてプロダクションと同じ実装です。
つまり**「プロダクションの縮小版をローカルに立てること」**であって、ソースを監視して変換してくれるツールではありません。
vite dev | wrangler dev | |
|---|---|---|
| 何なのか | フロントの**開発サーバー | プロダクションランタイムの複製** |
| 入力 | src/ のソース | ビルド成果物(dist/) |
| ソースの変更 | HMRで即時反映 | 反映されない — もう一度ビルドする必要がある |
| Worker | 存在しない | 動く |
だから静的サイトのビルドが先です
env.ASSETS は**「デプロイされた静的アセットの束」**を指します。その束がなければWorkerが取り出すものがありません。
ローカルでその束をどこから読むかを教えるのが --assets です。
1npx wrangler@4 dev --assets dist
2# ^^^^ build output, not src/
順序は常にこうです。
1# 1. Build the static site first -> dist/
2npm run build
3
4# 2. Boot workerd with dist/ as its asset store
5npx wrangler@4 dev --assets dist
1⛅️ wrangler 4.x
2Ready on http://localhost:8787
画面のコードを直したなら1番からやり直す必要があります。 wrangler dev は dist/ だけを見ているので、src/ を直しても気づきません。「確かに直したのになぜそのままなのか?」のほとんどがこれです。
スクリプトにまとめておくと便利です。
1// package.json
2{
3 "scripts": {
4 // cf- prefix: plain "worker" collides with Web Worker / Service Worker / worker_threads
5 "preview:cf-worker": "npm run build && wrangler dev --assets dist"
6 }
7}
ポートは設定に固定できます。
1// wrangler.jsonc
2{ "dev": { "port": 6173 } }
作業の流れ
2つのサーバーを両方立ち上げたまま使うわけではありません。
- 画面を作っている間は
vite dev。Workerは動きません。 - Workerを触ったときだけビルドしてから
wrangler devで確認します。
確認は「生のレスポンス」で行います
Workerがレスポンスを直すコードなら、レンダリングされた画面を見てはいけません。 画面はJSが作った結果なので、Workerがやったこととブラウザがやったことが混ざります。Workerをまったく通さなくても、画面は同じようにきちんと見えます。
ブラウザのソース表示
1view-source:http://localhost:6173/products/1234
開発者ツールのElementsパネルはここでは使えません — それは現在のDOMであり、すでにJSが実行された後だからです。
開発者ツールのNetworkタブ — ドキュメントのリクエストを選んでResponseを見ると、受け取った本文がそのまま出ます。レスポンスヘッダーまで見られるので最も正確です。
curl — 欲しいものだけ抜き出したり、スクリプトで回したりするときに便利です。
1curl -s http://localhost:6173/products/1234 | head -20
2curl -s -D - -o /dev/null http://localhost:6173/products/1234 # headers only
値が変わっていなければWorkerを通っていないということです。run_worker_first にそのパスが入っているかをまず確認しましょう。
デプロイ
ローカルで確認できたら、同じCLIでアップロードします。
1npx wrangler@4 deploy --assets dist
環境ごとに変わる値
サイトのアドレス・APIのアドレス・キーのように環境によって変わる値は、設定ファイルに埋め込まずCLIで渡す方がよいです(環境変数ドキュメント)。ファイルに書くと環境ごとに分かれ、食い違っても静かに誤動作します。
1npx wrangler@4 deploy --assets dist \
2 --name "prod-my-site" \
3 --var SITE_URL:"https://example.com" \
4 --var API_BASE_URL:"https://api.example.com"
CLIの値が設定ファイルに勝つので、同じ値を2か所に書いておく必要がありません。
1export interface Env {
2 ASSETS: Fetcher;
3 SITE_URL: string;
4 API_BASE_URL: string;
5}
デプロイ履歴を残す
--tag と --message はWorkerのバージョンに付くラベルです。ダッシュボードで「今上がっているのはどのコミットなのか」に答えられるようにしてくれます。
1npx wrangler@4 deploy --assets dist \
2 --tag "$GIT_SHA" \
3 --message "ref: $GIT_BRANCH"
なければロールバックのときに目視で突き合わせることになります。
CIで動かすとき
wrangler はデプロイの最後にあれこれ尋ねてきます(テレメトリーの同意など)。CIには答える人がいないので、そこで止まります。
1CI=true WRANGLER_SEND_METRICS=false npx wrangler@4 deploy --assets dist
認証は環境変数で渡します。wranglerがこの名前を直接読むので、別途exportする必要はありません。
1CLOUDFLARE_API_TOKEN
2CLOUDFLARE_ACCOUNT_ID
存在しないWorkerにデプロイしない
wrangler deploy --name X はXがなければ作り、あれば上書きします。便利に見えますが、名前を間違えたデプロイが静かに「成功」します。 見当違いのWorkerが新しく作られ、肝心の見ているサイトは変わりません。
CIならデプロイ前に存在確認を一度しておく方が安全です。APIでその名前を照会してステータスコードだけを見れば十分です。
1# -o /dev/null discard the body - we only want the status
2# -w print just the status code
3code=$(curl -s -o /dev/null -w '%{http_code}' \
4 -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
5 "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/scripts/$NAME")
6
7case "$code" in
8 2[0-9][0-9]) ;; # exists - go ahead
9 404) echo "no such Worker: $NAME" >&2; exit 1 ;; # typo, or first-ever deploy
10 401|403) echo "token lacks Workers permission" >&2; exit 1 ;;
11 *) echo "check failed (HTTP $code)" >&2; exit 1 ;;
12esac
2xxを丸ごと受け取る必要があります。 このエンドポイントはスクリプトの本文を返しますが、静的アセットだけを含むWorkerは本文が空なので204が返ります。200 だけを確認すると、2回目以降のデプロイがすべてブロックされます。
そして**「存在しない」(404)と「権限がない」(401・403)を必ず分ける必要があります。** 両者をひとまとめにすると、トークンの権限が足りない状況で「初めて作るのだから作成を許可せよ」という見当違いの案内をすることになり、その言葉に従うとデプロイが認証で再び落ちます。
まとめ
- ランタイム依存は増えません。 WorkerはCloudflareが実行します。開発用に
@cloudflare/workers-typesとwranglerだけあれば十分です。 wranglerはインストールせずnpx wrangler@4で使うこともできます。バージョンを固定すれば、ローカルとCIが同じものを使います。- Workerコードは型設定を分離します。一つのtsconfigで検査すると、Workerで
documentを使っても通ってしまい、デプロイ後にはじめて壊れます。 wrangler devはプロダクションランタイムのローカル複製であって、フロントエンドの開発サーバーではありません。dist/を先にビルドする必要があり、画面のコードを直したならビルドし直す必要があります。- Workerがレスポンスを直したかどうかは生のレスポンスで確認します。レンダリングされた画面やDevToolsのElementsパネルでは区別がつきません。
- 環境ごとの値はCLIの
--varで渡します。設定ファイルに埋め込むと同じ値が2か所にでき、環境ごとに分かれます。 - 存在しないWorkerにデプロイすると静かに成功します。CIなら存在確認を一度しましょう。