動いている Workers + D1 のリポジトリをテンプレートとして複製し、新しいアプリを作る手順です。2026年8月8日にこの手順で実際にデプロイまで通しています。
順番に依存関係があります。入れ替えると失敗するか、事故ります。
理由は3つが重なるためです。
| # | 理由 | 効果 |
|---|---|---|
| 1 | Workers Builds は接続した時点で main をビルドしてデプロイする | 作業ブランチに正しい値を置いていても、main が複製直後のままならその main が公開される |
| 2 | D1 のバインディングは database_name ではなく database_id で解決される | database_name を新しい名前に直しても、database_id が古ければ古い DB に繋がる |
| 3 | その状態でエラーは出ない | アプリは完全に正常動作する。書き込みも通る |
2026年8月8日に、この組み合わせで実際にデプロイまで通しています。
| 項目 | バージョン | 確認したこと |
|---|---|---|
| Node | 22(.nvmrc) | 20 だとデプロイ段で落ちる(→ 落とし穴2) |
| wrangler | 4.120.0 | deploy --dry-run が認証情報なしで完走する |
| @cloudflare/workers-types | 5.x | ^4 だと wrangler 4系の peer 依存と衝突して npm ci が落ちる |
| typescript | 5.x | tsc --noEmit が通る |
bin/reset-to-template.mjs です。
各ステップの「場所」は、ブラウザでやるか、Claude Code に頼むか、人が決めるかの区別です。
GitHub で Use this template(テンプレート設定済みの場合)または Fork / 複製して、新しいリポジトリを作る。
Template repository にチェックを入れておく。以後は Use this template から履歴なしの新規リポジトリを作れる。
Claude Code に推測させない項目です。先に決めて渡します。
| 項目 | 用途 | 制約 |
|---|---|---|
| アプリ名 | wrangler.jsonc の name、*.workers.dev のサブドメイン | 小文字の英数字とハイフンのみ。大文字を含むと wrangler が弾く |
| 何をするアプリか | README.md / 画面の見出し / src/index.ts の書き換え方針 | — |
| D1 データベース名 | wrangler.jsonc の database_name | 手順4で作る名前と一致させる |
| 公開ドメイン | routes。指定なしなら <アプリ名>.<サブドメイン>.workers.dev | 独自ドメインは Cloudflare のゾーン配下に置く必要あり |
複製先のリポジトリで Claude Code に 「テンプレートをリセットして」 と伝える。
node bin/reset-to-template.mjs # 何が変わるかと残存を表示するだけ
node bin/reset-to-template.mjs --write # 実際に書き換える
機械が戻すもの
| ファイル | 戻る値 |
|---|---|
wrangler.jsonc | name / database_name / database_id → change-me |
package.json | name / description → 雛形の値 |
docs/SETUP.md | 削除(複製元の環境の記録なので) |
機械が「報告するだけ」のもの
スクリプトは書き換え前の値を覚えておき、リポジトリ全体を走査して残っている箇所を file:line で報告します。設定ファイルは直せても、文章の中の値は機械に判断できないためです。残存があると終了コード 1 を返します。
実際に残るのは、たとえば次のような箇所です。すべて潰してから次に進んでください。
README.md の設定表 … 複製元の Worker 名・D1 名・database_id が実値で残るREADME.md から、削除された docs/SETUP.md へのリンクCLAUDE.md の中の、複製元リポジトリ名への言及人が決めて Claude Code が書き換えるもの
README.md … このアプリの説明にsrc/index.ts … 雛形の items アプリを、作るものに合わせてschema.sql … テーブル定義を作るものに合わせてschema.sql は次の手順4で実行するので、ここで確定させておきます。
schema.sql を実行する — ブラウザschema.sql の中身を貼って実行wrangler deploy は実行時に database_id を Cloudflare に問い合わせて解決するので、D1 が存在しないと最初のデプロイが成功せず、Worker 自体が作られません。wrangler.jsonc に実値を入れる — Claude Code手順2と手順4で決まった値を渡す。change-me は3か所。
database_id について必ず理解しておくこと』database_name ではなく database_id で解決されます。database_name はダッシュボード表示用のラベルにすぎません。database_name を新しい名前に直しても、database_id が複製元のままなら複製元の DB に繋がります。しかもエラーは一切出ません。書き込み経路のあるアプリなので、気づかないまま別アプリの本番 DB にデータを書き込めてしまいます。database_id を書き換えたことを、値を目で見て確認してください。
grep -rn 'change-me' wrangler.jsonc # 何も出ないこと
npm install # package-lock.json の name を同期
npm run check # 通ること
wrangler.jsonc の name は、手順7でダッシュボードに入力する Worker 名と一致させてください。食い違った状態でも動いてしまうことがありますが、どちらが優先されるかは検証していません(→8章)。一致していないと「どの設定でデプロイされたのか」の切り分けが極端に難しくなります。
main にマージする — Claude Code作業ブランチで開発 → npm run check が通ることを確認 → main へマージ。
main が新しいアプリの値になっていることを確認するmain をビルドしてデプロイします。main が複製直後のままだと、複製元の database_id がそのままバインドされ、複製元の本番 D1 に接続された Worker が公開されます。エラーは出ません。画面も正常に動きます。main へマージしておくこと(手順6)。
コンピュート → Workers & Pages からリポジトリをインポートする。
| 項目 | 値 |
|---|---|
| Worker 名 | wrangler.jsonc の name と同じ値にする |
| Build command | npm ci |
| Deploy command | npx wrangler deploy |
| ルート ディレクトリ | / |
| Production branch | main |
| ビルド出力ディレクトリ | 空のまま(静的アセット用の項目。Worker アプリでは使わない) |
GitHub App の認可では Only select repositories を選び、対象リポジトリだけに絞る。
Workers & Pages → 対象の Worker → 「バインディング」タブを開き、DB の値が手順4で作った新しい D1 の名前になっていることを確認する。
| 表示 | 意味 |
|---|---|
| 新しく作った D1 の名前 | 正しい |
| 複製元の D1 の名前 | main の database_id が古い。直して再デプロイし、この画面が変わるまで確認する |
https://<アプリ名>.<サブドメイン>.workers.dev を開く。
GET / と D1 の読み取りが通っているschema.sql 実行を飛ばしてテーブルが無い可能性が高い。
main を保護する — ブラウザGitHub の Settings → Branches で main を保護し、build チェックの通過を必須にする。
実際に踏んだものだけを挙げます。どれもログや画面を素直に読むと原因が分かりにくい失敗です。
main が複製直後のまま Workers Builds に接続した。接続した瞬間にその main がビルド・デプロイされ、複製元の database_id がバインドされた。database_name を新しい名前に直してあっても防げません。バインディングは database_id で解決されるためです。main へマージ)と手順8(バインディングの目視確認)。npm warn EBADENGINE package: 'wrangler@4.120.0',
npm warn EBADENGINE required: { node: '>=22.0.0' },
npm warn EBADENGINE current: { node: 'v20.20.2' }
症状:「ビルド中」まで緑で、「デプロイ中」だけが数秒で赤くなる。
原因:wrangler 4.120.0 は Node 22 以上を要求する。.nvmrc が 20 だと Cloudflare のビルド環境が Node 20 を入れる。インストールは EBADENGINE 警告で通ってしまうので、ビルドが成功しているように見える。
対処:.nvmrc を 22、package.json の engines.node を >=22 に。このリポジトリでは修正済みなので、複製すれば踏みません。
npm run check が通ってもビルド環境の Node が違えば意味がない。
assets.directory エラー[ERROR] The directory specified by the "assets.directory" field in your ...
/opt/buildhome/repo/dist
症状:クローン・インストール・ビルドは緑なのに、デプロイだけ失敗する。
原因:assets.directory は静的サイト版の wrangler.jsonc にしかない設定。main が古い構成のままの状態で接続すると、そちらを読んでしまう。
対処:main に正しい構成をマージする(手順6)。落とし穴1と同じ根っこです。
refusing to merge unrelated historiesClaude Code のクラウド実行環境で、ローカルの main が origin/main と無関係な履歴になっていることがある。
git checkout -B main origin/main
git merge --ff-only <作業ブランチ>
@cloudflare/workers-types のメジャー^4 を指定すると wrangler 4系の peer 依存と衝突して npm ci が落ちます。^5 のままにしてください。
ビルドログは5段階(初期化 / クローン / インストール / ビルド / デプロイ)に分かれます。どこが赤いかで原因の範囲が決まります。
| 赤い段階 | 疑うもの |
|---|---|
| インストール | package-lock.json の不整合、依存の解決、workers-types のメジャー |
| ビルド | Build command そのもの |
| デプロイ | wrangler.jsonc の内容、Node のバージョン、D1 の実在 |
npm run check は tsc --noEmit と wrangler deploy --dry-run です。--dry-run は認証情報なしで動く代わりに、Cloudflare に問い合わせません。
npm run check で分かる | 分からない |
|---|---|
| 型エラー | database_id が実在するか |
wrangler.jsonc の書式(Worker 名の規則違反など) | その database_id が正しい DB のものか |
main が指すファイルの実在 | D1 にテーブルがあるか |
| バインディングの「形」が解決すること | ビルド環境の Node で動くか |
wrangler.jsonc の name が食い違った場合にどちらが優先されるか。食い違いが起きうることは確認しましたが、優先順位は検証していません。src/index.ts には POST /api/items があり、これは誰でも書き込めます。経路確認の段階では問題ありませんが、URL が知られた状態で放置するとスパムを入れられます。
アプリの中身を書き換えるときに、必ず一緒に決めてください。
「とりあえず公開して後で塞ぐ」はしない、というのが CLAUDE.md の約束です。
あわせて CLAUDE.md の「セキュリティ上の約束」も読んでください。要点は3つです。
.bind() —— 文字列連結で SQL を組み立てないesc() —— テンプレートリテラルへの直挿しは XSSawait req.json() の戻り値は unknown として扱う
雛形の schema.sql は created_at の既定値に datetime('now') を使っています。SQLite の datetime('now') は UTC を返すので、保存される時刻は UTC です。日本時間の表示と9時間ずれますが、これは仕様であってバグではありません。
| ファイル | 役割 |
|---|---|
CLAUDE.md | Claude Code への常設指示。運用ルールとセキュリティの約束 |
docs/TEMPLATE.md | 複製して新しいアプリを作る手順(本記事の元) |
docs/SETUP.md | その環境の実績記録(複製時は削除される) |
bin/reset-to-template.mjs | 複製直後に固有値を change-me に戻し、残存を報告する |
src/index.ts | Worker の入口 |
schema.sql | D1 のテーブル定義。ダッシュボードのコンソールで実行する |
wrangler.jsonc | Workers の設定(name / main / d1_databases / routes) |
.nvmrc | Node のバージョン。22 以上が必要 |
.github/workflows/ | CI(型チェック+バンドル検証) |