Cloudflare:R2
2026年2月21日無料の容量が多い:読み取り/書き込み
人気ストレージである「Amazon-S3」と操作が近い
npm i @aws-sdk/client-s3 uuid
npm i sharp環境変数
R2_ACCESS_KEY_ID=""
R2_SECRET_ACCESS_KEY=""
R2_BUCKET_NAME=""
R2_ENDPOINT=""
R2_ACCOUNT_ID=""
R2_PUBLIC_DOMAIN="" 上2つ:(ダッシュボード/ R2 オブジェクト ストレージ / APIトークンの管理 / アカウント API トークンの作成)
APIトークンを作成する時に確認できる。*作成後は確認できない
真ん中:バケット名
下3つ:( ダッシュボード/ R2 オブジェクト ストレージ / 設定 )
R2_ENDPOINT:
https://{ R2_ACCOUNT_ID }.r2.cloudflarestorage.com*バケット名は含まないR2_ACCOUNT_ID:
{ R2_ACCOUNT_ID }R2_PUBLIC_DOMAIN:
開発:「パブリック開発URL」を有効にすると表示されるもの( https://pub-xxx.r2.dev )
本番:「カスタムドメイン」から追加するもの(例:cdn.xxx.com)
R2ダッシュボード
CORS設定
開発用に
http://localhost:3000/を追加本番用にドメインを追加する際は 以下のJSON形式で入力
{
"AllowedOrigins": [
"本番ドメイン"
],
"AllowedMethods": [
"GET",
"POST",
"DELETE"
]
}カスタムドメイン
cloudflareにドメインを登録しているならすぐにセットできる
cdn.xxx.comとしてカスタムドメインを追加すると、R2用のDNSレコードが作成されるxxx.com= 登録しているドメイン
環境変数:
R2_PUBLIC_DOMAIN.env.development: パブリック開発URL.env.production:作成したcdn.xxx.com
1対1の関係
バケットはアプリ毎に作成し、1つのR2用カスタムドメイン(cdn.example.com )を使いまわすことは出来ない。「既に紐付いているバケットがある」旨のメッセージが出て作成できない。
// X
cdn.example.com → bucket1
cdn.example.com → bucket2素直にプロジェクト毎にカスタムドメインを増やしアプリ・カスタムドメイン・バケットは1対1で管理した方が良い。Cloudflareでドメインを管理しているならカスタムドメイン(
cdn2.example.com)の追加時に自動で作成してくれる。
// O
cdn1.example.com → bucket1
cdn2.example.com → bucket2*非推奨:
1つのカスタムドメイン、バケットでも複数のアプリで運用はできる。
やり方は1つのバケットに階層を設ける、つまり画像をバケットに保存するときに
/webのようにプロジェクト毎にユニークな接頭辞をつけて保存する。ややハックっぽい感じがするのでドメイン数の制限?みたいなものに引っかかったらこのやり方を使う。
cdn.example.com/blog/xxx.webp
cdn.example.com/web/yyy.webp画像のupload
r2/client.ts:
import { S3Client } from "@aws-sdk/client-s3";
export const s3 = new S3Client({
region: "auto",
endpoint: process.env.R2_ENDPOINT,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
});action:
if (file) {
const ext = "webp";
const key = `folder-image-${folderId}.${ext}`;
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const webpBuffer = await sharp(buffer).webp().toBuffer();
await s3.send(
new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME!,
Key: key,
Body: webpBuffer,
ContentType: "image/webp",
})
);
iconUrl = `${process.env.R2_PUBLIC_DOMAIN}/${key}`;
}inputタグで取得したfileを受取りR2にuploadする
webpはpngの1/10くらいのサイズなので、webpに変換する
Vercel:画像サイズ
1MB以上の画像をserverActionで処理しようとすると画像のUploadが出来ない。Vercel-Logを見るとエラーが出ている。
Uncaught Exception: Error: Body exceeded 1 MB limit.
クライアント側でfile.sizeが1MBを超えたらtoastを出すようにする
const MAX_IMAGE_SIZE = 900000;
const handleUpload = async () => {
if (!file) return;
if (file.size > MAX_IMAGE_SIZE) {
toast.error("Image size must be less than 1MB.", {
style: {
background: "red",
color: "#fff",
},
});
return;
}
setLoading(true);
try {
await uploadImage(id, file);
setFile(null);
} finally {
setLoading(false);
}
};1MBは1,000,000 bytesなので少し余裕をもたせる