mcpField
V
vercel-blob

vercel-blob

github.com/jackspaceクラウド・DevOps1644 回インストール12 週間前に更新
リポジトリ

vercel-blob について

vercel-blob は、@vercel/blob でファイルを保存するためのスキルです。アップロードやファイル管理機能を開発する人向けに、サーバー・クライアントからのアップロード、一覧表示、ダウンロード、削除、大容量ファイルのマルチパート処理を扱います。

主な機能

  1. 01サーバー・クライアントからのアップロード
  2. 02ファイルの一覧表示・ダウンロード・削除
  3. 03大容量ファイルのマルチパートアップロード
  4. 04公開・非公開アクセスの設定
  5. 05アップロード検証とセキュリティの指針

活用シーン

  1. 01ユーザーのアバターや文書のアップロード
  2. 02ページネーション付き画像ギャラリーの管理
  3. 03クライアントアップロードトークンでのファイル受付
  4. 04500 MB を超えるファイルのアップロード

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jackspace/claudeskillz --skill vercel-blob

インストール後、Claude Code で/vercel-blobを入力して読み込まれたか確認します。

同梱ファイルREADME.mdSKILL.json

詳細

Vercel Blob(オブジェクトストレージ)

ステータス:本番環境対応 最終更新日:2025-10-29 依存関係:なし 最新バージョン:@vercel/[email protected]


クイックスタート(3 分)

1. Blob ストアを作成
# In Vercel dashboard: Storage → Create Database → Blob
vercel env pull .env.local

作成されるもの:BLOB_READ_WRITE_TOKEN

2. インストール
npm install @vercel/blob
3. ファイルをアップロード(サーバーアクション)
'use server';

import { put } from '@vercel/blob';

export async function uploadFile(formData: FormData) {
  const file = formData.get('file') as File;

  const blob = await put(file.name, file, {
    access: 'public' // or 'private'
  });

  return blob.url; // https://xyz.public.blob.vercel-storage.com/file.jpg
}

重要:

  • クライアントから直接アップロードする場合は、クライアントアップロードトークンを使用します(BLOB_READ_WRITE_TOKEN を公開しないでください)
  • 適切な access レベル(public または private)を設定します
  • ファイルは CDN を通じて自動的に配信されます

5 ステップのセットアップ手順

ステップ 1:Blob ストアを作成

Vercel ダッシュボード:

  1. プロジェクト → Storage → データベースを作成 → Blob
  2. BLOB_READ_WRITE_TOKEN をコピーします

ローカル開発:

vercel env pull .env.local

.env.local が作成されます:

BLOB_READ_WRITE_TOKEN="vercel_blob_rw_xxx"

要点:

  • 無料枠:月間 100GB の帯域幅
  • ファイルサイズ上限:1 ファイルあたり 500MB
  • CDN による自動配信
  • 公開ファイルはグローバルにキャッシュされます

ステップ 2:サーバーサイドアップロード

Next.js サーバーアクション:

'use server';

import { put } from '@vercel/blob';

export async function uploadAvatar(formData: FormData) {
  const file = formData.get('avatar') as File;

  // Validate file
  if (!file.type.startsWith('image/')) {
    throw new Error('Only images allowed');
  }

  if (file.size > 5 * 1024 * 1024) {
    throw new Error('Max file size: 5MB');
  }

  // Upload
  const blob = await put(`avatars/${Date.now()}-${file.name}`, file, {
    access: 'public',
    addRandomSuffix: false
  });

  return {
    url: blob.url,
    pathname: blob.pathname
  };
}

API ルート(Edge Runtime):

import { put } from '@vercel/blob';

export const runtime = 'edge';

export async function POST(request: Request) {
  const formData = await request.formData();
  const file = formData.get('file') as File;

  const blob = await put(file.name, file, { access: 'public' });

  return Response.json(blob);
}

ステップ 3:クライアントサイドアップロード(署名付き URL)

アップロードトークンを作成(サーバーアクション):

'use server';

import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';

export async function getUploadToken(filename: string) {
  const jsonResponse = await handleUpload({
    body: {
      type: 'blob.generate-client-token',
      payload: {
        pathname: `uploads/${filename}`,
        access: 'public',
        onUploadCompleted: {
          callbackUrl: `${process.env.NEXT_PUBLIC_URL}/api/upload-complete`
        }
      }
    },
    request: new Request('https://dummy'),
    onBeforeGenerateToken: async (pathname) => {
      // Optional: validate user permissions
      return {
        allowedContentTypes: ['image/jpeg', 'image/png', 'image/webp'],
        maximumSizeInBytes: 5 * 1024 * 1024 // 5MB
      };
    },
    onUploadCompleted: async ({ blob, tokenPayload }) => {
      console.log('Upload completed:', blob.url);
    }
  });

  return jsonResponse;
}

クライアントからのアップロード:

'use client';

import { upload } from '@vercel/blob/client';
import { getUploadToken } from './actions';

export function UploadForm() {
  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const form = e.currentTarget;
    const file = (form.elements.namedItem('file') as HTMLInputElement).files?.[0];

    if (!file) return;

    const tokenResponse = await getUploadToken(file.name);

    const blob = await upload(file.name, file, {
      access: 'public',
      handleUploadUrl: tokenResponse.url
    });

    console.log('Uploaded:', blob.url);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="file" name="file" required />
      <button type="submit">Upload</button>
    </form>
  );
}

ステップ 4:一覧表示、ダウンロード、削除

ファイルを一覧表示:

import { list } from '@vercel/blob';

const { blobs } = await list({
  prefix: 'avatars/',
  limit: 100
});

// Returns: { url, pathname, size, uploadedAt, ... }[]

ページネーション付きで一覧表示:

let cursor: string | undefined;
const allBlobs = [];

do {
  const { blobs, cursor: nextCursor } = await list({
    prefix: 'uploads/',
    cursor
  });
  allBlobs.push(...blobs);
  cursor = nextCursor;
} while (cursor);

ファイルをダウンロード:

// Files are publicly accessible if access: 'public'
const url = 'https://xyz.public.blob.vercel-storage.com/file.pdf';
const response = await fetch(url);
const blob = await response.blob();

ファイルを削除:

import { del } from '@vercel/blob';

await del('https://xyz.public.blob.vercel-storage.com/file.jpg');

// Or delete multiple
await del([url1, url2, url3]);

ステップ 5:ストリーミングとマルチパート

ストリーミングアップロード:

import { put } from '@vercel/blob';
import { createReadStream } from 'fs';

const stream = createReadStream('./large-file.mp4');

const blob = await put('videos/large-file.mp4', stream, {
  access: 'public',
  contentType: 'video/mp4'
});

マルチパートアップロード(500MB を超える大容量ファイル):

import { createMultipartUpload, uploadPart, completeMultipartUpload } from '@vercel/blob';

// 1. Start multipart upload
const upload = await createMultipartUpload('large-video.mp4', {
  access: 'public'
});

// 2. Upload parts (chunks)
const partSize = 100 * 1024 * 1024; // 100MB chunks
const parts = [];

for (let i = 0; i < totalParts; i++) {
  const chunk = getChunk(i, partSize);
  const part = await uploadPart(chunk, {
    uploadId: upload.uploadId,
    partNumber: i + 1
  });
  parts.push(part);
}

// 3. Complete upload
const blob = await completeMultipartUpload({
  uploadId: upload.uploadId,
  parts
});

重要なルール

必ず行うこと

✅ クライアントサイドのアップロードにはクライアントアップロードトークンを使用する - クライアントに BLOB_READ_WRITE_TOKEN を絶対に公開しないでください

✅ 適切なアクセスレベルを設定する - public(CDN)または private(認証済みアクセス)

✅ ファイルの種類とサイズを検証する - アップロード前に MIME タイプとサイズを確認します

✅ パス名で整理する - avatars/、uploads/、documents/ を使用して構造化します

✅ アップロードエラーに対処する - ネットワーク障害、サイズ制限、トークンの有効期限切れなどに対応します

✅ 古いファイルを整理する - ストレージコストを管理するため、不要なファイルを削除します

✅ content-type を明示的に設定する - ブラウザーで正しく処理されるようにします(動画、PDF など)

絶対に行わないこと

❌ BLOB_READ_WRITE_TOKEN をクライアントに絶対に公開しない - クライアントからのアップロードには handleUpload() を使用します

❌ ファイル検証を絶対に省略しない - アップロード前に必ず種類、サイズ、内容を検証します

❌ マルチパートを使用せずに 500MB を超えるファイルを絶対にアップロードしない - 大容量ファイルにはマルチパートアップロードを使用します

❌ 汎用的なファイル名を絶対に使用しない - file.jpg は重複するため、${timestamp}-${name} または UUID を使用します

❌ アップロードの成功を絶対に前提としない - 必ずエラー(ネットワーク、クォータなど)に対処します

❌ 機密データを暗号化せずに絶対に保存しない - 必要に応じて、アップロード前に暗号化します

❌ 一時ファイルの削除を絶対に忘れない - 古いアップロードファイルはクォータを消費します


既知の問題の予防

このスキルは、記録されている 10 件の問題を防ぎます:

問題 #1:環境変数がない

エラー:Error: BLOB_READ_WRITE_TOKEN is not defined 参照元:https://vercel.com/docs/storage/vercel-blob 原因:トークンが環境に設定されていません 予防策:vercel env pull .env.local を実行し、.env.local が .gitignore に含まれていることを確認します。

問題 #2:クライアントアップロードトークンの公開

エラー:セキュリティ上の脆弱性、不正なアップロード 参照元:https://vercel.com/docs/storage/vercel-blob/client-upload 原因:クライアントコードで BLOB_READ_WRITE_TOKEN を直接使用しています 予防策:handleUpload() を使用して、制約付きのクライアント専用トークンを生成します。

問題 #3:ファイルサイズ上限の超過

エラー:Error: File size exceeds limit(500MB) 参照元:https://vercel.com/docs/storage/vercel-blob/limits 原因:マルチパートアップロードを使用せずに 500MB を超えるファイルをアップロードしています 予防策:アップロード前にファイルサイズを検証し、大容量ファイルにはマルチパートアップロードを使用します。

Issue #4:Content-Type が正しくない

エラー:ブラウザーでファイルが表示されず、ダウンロードされます(例:PDF がテキストとして開く) 発生元:本番環境でのデバッグ 原因:contentType オプションが設定されておらず、Blob が誤って推測します 予防策:常に contentType: file.type または明示的な MIME タイプを設定します。

Issue #5:公開ファイルがキャッシュされない

エラー:ファイル配信が遅く、エグレスコストが高くなります 発生元:Vercel Blob のベストプラクティス 原因:公開すべきファイルに access: 'private' を使用しています 予防策:一般公開するファイルには access: 'public' を使用します(CDN キャッシュ)。

Issue #6:リストのページネーションが処理されていない

エラー:最初の 1000 件のファイルしか返されず、ファイルが不足します 発生元:https://vercel.com/docs/storage/vercel-blob/using-blob-sdk#list 原因:大量のファイルリストでカーソルを使った反復処理を行っていません 予防策:cursor が undefined になるまで、ループ内でカーソルベースのページネーションを使用します。

Issue #7:削除が失敗しても通知されない

エラー:ファイルが削除されず、ストレージクォータを使い切ります 発生元:https://github.com/vercel/storage/issues/150 原因:誤った URL 形式を使用しており、Blob が見つかりません 予防策:put() の応答に含まれる完全な Blob URL を使用し、削除結果を確認します。

Issue #8:アップロードのタイムアウト(大容量ファイル)

エラー:100MB を超えるファイルで Error: Request timeout が発生します 発生元:Vercel 関数のタイムアウト制限 原因:サーバーレス関数のタイムアウト(無料プランでは 10 秒、Pro では 60 秒) 予防策:大容量ファイルには handleUpload() を使ったクライアント側アップロードを使用します。

Issue #9:ファイル名の衝突

エラー:ファイルが上書きされ、データが失われます 発生元:本番環境でのデバッグ 原因:複数のアップロードで同じファイル名を使用しています 予防策:タイムスタンプ/UUID を追加します:`uploads/${Date.now()}-${file.name}` または addRandomSuffix: true。

Issue #10:アップロードコールバックがない

エラー:アップロードは完了しますが、アプリの状態が更新されません 発生元:https://vercel.com/docs/storage/vercel-blob/client-upload#callback-after-upload 原因:onUploadCompleted コールバックを実装していません 予防策:handleUpload() で onUploadCompleted を使用し、データベース/状態を更新します。


設定ファイルのリファレンス

package.json
{
  "dependencies": {
    "@vercel/blob": "^2.0.0"
  }
}
.env.local
BLOB_READ_WRITE_TOKEN="vercel_blob_rw_xxxxx"

一般的なパターン

パターン 1:アバターのアップロード
'use server';

import { put, del } from '@vercel/blob';

export async function updateAvatar(userId: string, formData: FormData) {
  const file = formData.get('avatar') as File;

  // Validate
  if (!file.type.startsWith('image/')) {
    throw new Error('Only images allowed');
  }

  // Delete old avatar
  const user = await db.query.users.findFirst({ where: eq(users.id, userId) });
  if (user?.avatarUrl) {
    await del(user.avatarUrl);
  }

  // Upload new
  const blob = await put(`avatars/${userId}.jpg`, file, {
    access: 'public',
    contentType: file.type
  });

  // Update database
  await db.update(users).set({ avatarUrl: blob.url }).where(eq(users.id, userId));

  return blob.url;
}
パターン 2:保護されたファイルのアップロード
'use server';

import { put } from '@vercel/blob';
import { auth } from '@/lib/auth';

export async function uploadDocument(formData: FormData) {
  const session = await auth();
  if (!session) throw new Error('Unauthorized');

  const file = formData.get('document') as File;

  // Upload as private
  const blob = await put(`documents/${session.user.id}/${file.name}`, file, {
    access: 'private' // Requires authentication to access
  });

  // Store in database with user reference
  await db.insert(documents).values({
    userId: session.user.id,
    url: blob.url,
    filename: file.name,
    size: file.size
  });

  return blob;
}
パターン 3:ページネーション付き画像ギャラリー
import { list } from '@vercel/blob';

export async function getGalleryImages(cursor?: string) {
  const { blobs, cursor: nextCursor } = await list({
    prefix: 'gallery/',
    limit: 20,
    cursor
  });

  const images = blobs.map(blob => ({
    url: blob.url,
    uploadedAt: blob.uploadedAt,
    size: blob.size
  }));

  return { images, nextCursor };
}

依存関係

必須:

  • @vercel/blob@^2.0.0 - Vercel Blob SDK

任意:

  • sharp@^0.33.0 - アップロード前の画像処理
  • zod@^3.24.0 - ファイル検証スキーマ

公式ドキュメント


パッケージバージョン(2025-10-29 確認済み)

{
  "dependencies": {
    "@vercel/blob": "^2.0.0"
  }
}

本番環境での例

  • E コマース:商品画像、ユーザーによるアップロード(50 万件以上のファイル)
  • ブログプラットフォーム:注目画像、著者のアバター
  • SaaS:ドキュメントのアップロード、PDF 生成、CSV エクスポート
  • エラー:0 件(既知の問題 10 件をすべて予防)

トラブルシューティング

問題:BLOB_READ_WRITE_TOKEN is not defined

解決策:vercel env pull .env.local を実行し、.env.local が .gitignore に含まれていることを確認します。

問題:ファイルサイズが上限を超えた(>500MB)

解決策:createMultipartUpload() API を使ったマルチパートアップロードを使用します。

問題:クライアントアップロードがトークンエラーで失敗する

解決策:サーバー側で handleUpload() を使用していることを確認し、読み書きトークンをクライアントに公開しないでください。

問題:ファイルが削除されない

解決策:put() の応答に含まれる正確な URL を使用し、del() の戻り値を確認します。


完全セットアップチェックリスト

  • Vercel ダッシュボードで Blob ストアを作成済み
  • BLOB_READ_WRITE_TOKEN 環境変数を設定済み
  • @vercel/blob パッケージをインストール済み
  • ファイルの検証(種類、サイズ)を実装済み
  • クライアント側のアップロードで(トークンを直接使用せず)handleUpload() を使用
  • アップロード時に Content-Type を設定
  • アクセスレベルを適切に設定(public と private)
  • 古いファイルの削除を実装済み
  • リストのページネーションでカーソルを処理
  • ローカル環境と本番環境でファイルのアップロード/ダウンロード/削除をテスト済み

ご質問や問題はありますか?

  1. 公式ドキュメントを確認してください:https://vercel.com/docs/storage/vercel-blob
  2. 環境変数が設定されていることを確認してください
  3. クライアント側のアップロードには、クライアントアップロードトークンを使用してください
  4. Vercel ダッシュボードでストレージ使用量を監視してください

コメント