ファイルを置く第10章 ・ 読むのにかかる時間 約9分

置く:アップロードのしくみ

初心者が必ず踏む落とし穴つき

倉庫に荷物を入れます。入れ方は2つあって、手で1枚だけ置く方法と、フォームから受け取って自動で置く方法です。順番にやります。そして、この章には初心者が100%踏む落とし穴が1つあります。

この章でできるようになること

  • 手元の画像を1枚、R2に置いて確認する
  • 「本物のR2に入ったのか、練習用の場所に入ったのか」を見分ける
  • フォームから送られた画像を、Workerが受け取ってR2に保存する

まず、手で1枚置いてみる

プロジェクトのフォルダに、適当な画像を1枚置いてください。名前は sample.png にします。そのうえで、こう頼みます。

Claude Codeにこう頼みます

フォルダにある sample.png を、my-photos バケットに images/sample.png というキーで保存してください。 必ず本物のR2(リモート)に保存してください。ローカルの模擬ストレージではありません。保存できたら、リモートから取得し直して、ちゃんと入っているか確認してから教えてください。

この依頼文には、わざと同じことを2回書いてあります。理由は次のとおりです。

落とし穴:黙っていると、本物には入りません

R2にファイルを入れる操作は、何も指定しないと、あなたのパソコンの中の「練習用の模擬ストレージ」に保存されます。画面には「保存しました」と出ます。エラーも出ません。それなのに、Cloudflareの管理画面を見ると空っぽです。

実際、画面にはこう表示されます。

Resource location: local
Use --remote if you want to access the remote instance.

local と書いてあったら、本物ではありません。この1行を見落とすと、「保存されているはずなのに表示されない」という迷宮に入ります。僕はここで時間を溶かしました。

本物に入れると、同じ場所の表示が変わります。

Resource location: remote

ローカルの模擬ストレージ

練習用の、パソコンの中だけにある偽のR2です。本物を汚さずに試せる、便利な仕組みではあります。ただ、初めての人には「保存できたのに見えない」という混乱の原因にしかなりません。この教科書では、依頼文に必ず「本物のR2に」と書くことで避けます。

うまくいったか確認する

  • 実行結果に Resource location: remote と出ている
  • Cloudflareの管理画面でR2を開き、my-photos の中に images/sample.png が見える
  • 管理画面でファイルサイズが0バイトになっていない

本命:フォームから受け取って保存する

毎回コマンドで入れるのでは、自分しか使えません。ブラウザから選んだ画像が、そのままR2に入るようにします。

横にスクロールできます

アップロードの流れ ブラウザで選んだ画像がWorkerに届き、Workerが名前を決めてR2に保存し、その名前をブラウザに返します。 ブラウザ 画像を選んで 送信 WORKER 受け取って確認して 重複しない名前を決める 種類の情報も一緒に持たせる R2(倉庫) images/ランダム文字- sample.png
Workerは、ただ通すだけではありません。名前を決めて、種類を覚えさせてから預けます。
Claude Codeにこう頼みます

画像アップロードの仕組みを作ってください。 ・トップページに、画像を1枚選んで送信できるフォームを置く ・Workerが /upload でそれを受け取り、BUCKETバインディングを使ってR2に保存する ・キーは images/ で始め、重複しないようにランダムな文字列を混ぜる ・画像の種類(Content-Type)も一緒に保存する ・画像以外のファイルや、5MBを超えるファイルは断って、日本語のメッセージを返す ・保存できたら、保存したキーをJSONで返す 作り終わったら、それぞれの部分が何をしているかを日本語で説明してください。

できあがるプログラムの中心は、この3行です。

const key = `images/${crypto.randomUUID()}-${file.name}`;
await env.BUCKET.put(key, file.stream(), {
  httpMetadata: { contentType: file.type },
});

1行ずつ読むと、こう書いてあります。

  • 1行目:重複しない名前を作る。crypto.randomUUID() は、毎回違うでたらめな文字列を作る道具です
  • 2行目env.BUCKETput(置く)。第9章で決めた呼び名がここに出てきます
  • 3行目:ファイルの種類を一緒に覚えさせる。これが次の章で効いてきます

Content-Type(コンテントタイプ)

「これはPNG画像です」「これはPDFです」という、中身の種類を表す札です。R2は中身を見ないので、この札を貼っておかないと、あとで取り出したときにブラウザが画像として表示してくれません。保存するときに一緒に持たせるのが鉄則です。忘れると、次の章で「画像がダウンロードされてしまう」という症状になります。

アップロード前の確認(バリデーション)

送られてきたものが本当に画像か、大きすぎないかを、保存する前に調べることです。誰でも送れる窓口を開ける以上、ここは必ず入れます。無制限に受け取ると、無料枠を一晩で食いつぶされることもあり得ます。

動かして確かめる

Claude Codeにこう頼みます

アップロードを試したいです。本物のR2につながる形でローカル起動して、開くURLを教えてください。 起動できたら、私がブラウザで画像を送ったあとに、R2の中身を一覧して、本当に増えているかを確認してください。

フォームから画像を送ると、キーが返ってきます。images/ で始まる長い文字列です。R2の一覧にも、それが増えているはずです。

よくあるつまずき

Resource location: local
この章の落とし穴です。本物ではなく練習用に保存されています。「本物のR2(リモート)に保存し直してください」と頼んでください。
TypeError: env.BUCKET is undefined
第9章のbindingが効いていません。wrangler.jsoncr2_buckets があるか、呼び名が BUCKET になっているかを確認してもらってください。
アップロードは成功するのに、管理画面のファイルが0バイト
中身が空のまま保存されています。ファイルの読み取り方に問題があるので、「保存したファイルのサイズが0になっています。原因を調べて直してください」と伝えてください。
同じ画像を2回上げたら、1個しか増えない
同じキーになっていて、上書きされています。ランダムな文字列がキーに入っているか確認してください。
大きな画像を送ると失敗する
制限に当たっています。まずは自分で決めた上限(5MBなど)を確認してください。それを超えていないのに失敗する場合は、エラーの文面をそのままClaude Codeに貼って聞くのが最短です。

この章のまとめ

手で1枚置き、フォームからも置けるようにしました。キーは自動で重複しない形にして、種類の札も貼りました。そしてlocal と remote の違いという、この分野で一番の初見殺しを覚えました。

ただ、いま入れた画像は、まだ誰にも見えません。次の章で外に出します。

章末チェックリスト