mcpField
F
firebase-storage

firebase-storage

github.com/jezwebクラウド・DevOps1.1k296 回インストール3 週間前に更新
リポジトリ

firebase-storage について

firebase-storage は、Firebase Cloud Storage をクライアントとサーバーに組み込む開発者向けのガイドです。ファイル操作に加え、設定やセキュリティルール、一般的なエラーの対策を扱います。

主な機能

  1. 01クライアントとサーバーの初期化
  2. 02進捗表示付きアップロード
  3. 03ダウンロードとメタデータ取得
  4. 04ファイルの削除と一覧表示
  5. 05セキュリティルールと CORS 設定
  6. 06エラー処理とトラブルシューティング

活用シーン

  1. 01Web アプリでファイルを受け付ける
  2. 02大容量アップロードの進捗を表示する
  3. 03一時的な署名付きダウンロード URL を作成する
  4. 04ストレージのセキュリティルールをデプロイする

インストール

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

$npx skills add https://github.com/jezweb/claude-skills --skill firebase-storage

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
.claude-plugin/
plugin.json
rules/
firebase-storage.md
templates/
cors.json

詳細

Firebase Cloud Storage

ステータス:本番環境で利用可能 最終更新日:2026-01-25 依存関係:なし(スタンドアロンのスキル) 最新バージョン:[email protected]、[email protected]


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

1. Firebase Storage を初期化する(クライアント)
// src/lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getStorage } from 'firebase/storage';

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  // ... other config
};

const app = initializeApp(firebaseConfig);
export const storage = getStorage(app);
2. Firebase Admin Storage を初期化する(サーバー)
// src/lib/firebase-admin.ts
import { initializeApp, cert, getApps } from 'firebase-admin/app';
import { getStorage } from 'firebase-admin/storage';

if (!getApps().length) {
  initializeApp({
    credential: cert({
      projectId: process.env.FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
    }),
    storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
  });
}

export const adminStorage = getStorage().bucket();

ファイルのアップロード(クライアント SDK)

基本的なアップロード
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
import { storage } from './firebase';

async function uploadFile(file: File, path: string): Promise<string> {
  const storageRef = ref(storage, path);

  // Upload file
  const snapshot = await uploadBytes(storageRef, file);

  // Get download URL
  const downloadURL = await getDownloadURL(snapshot.ref);

  return downloadURL;
}

// Usage
const url = await uploadFile(file, `uploads/${userId}/${file.name}`);
進捗状況を表示してアップロードする
import { ref, uploadBytesResumable, getDownloadURL, UploadTask } from 'firebase/storage';
import { storage } from './firebase';

function uploadFileWithProgress(
  file: File,
  path: string,
  onProgress: (progress: number) => void
): Promise<string> {
  return new Promise((resolve, reject) => {
    const storageRef = ref(storage, path);
    const uploadTask = uploadBytesResumable(storageRef, file);

    uploadTask.on(
      'state_changed',
      (snapshot) => {
        const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        onProgress(progress);
      },
      (error) => {
        reject(error);
      },
      async () => {
        const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
        resolve(downloadURL);
      }
    );
  });
}

// Usage with React state
const [progress, setProgress] = useState(0);
const url = await uploadFileWithProgress(file, path, setProgress);
メタデータを指定してアップロードする
import { ref, uploadBytes, getDownloadURL, UploadMetadata } from 'firebase/storage';
import { storage } from './firebase';

async function uploadWithMetadata(file: File, path: string) {
  const storageRef = ref(storage, path);

  const metadata: UploadMetadata = {
    contentType: file.type,
    customMetadata: {
      uploadedBy: userId,
      originalName: file.name,
      uploadTime: new Date().toISOString(),
    },
  };

  const snapshot = await uploadBytes(storageRef, file, metadata);
  const downloadURL = await getDownloadURL(snapshot.ref);

  return { downloadURL, metadata: snapshot.metadata };
}
データ URL/Base64 からアップロードする
import { ref, uploadString, getDownloadURL } from 'firebase/storage';
import { storage } from './firebase';

// Upload base64 string
async function uploadBase64(base64String: string, path: string) {
  const storageRef = ref(storage, path);

  // For data URL (includes prefix like "data:image/png;base64,")
  const snapshot = await uploadString(storageRef, base64String, 'data_url');

  // For raw base64 (no prefix)
  // const snapshot = await uploadString(storageRef, base64String, 'base64');

  const downloadURL = await getDownloadURL(snapshot.ref);
  return downloadURL;
}

ファイルのダウンロード

ダウンロード URL を取得する
import { ref, getDownloadURL } from 'firebase/storage';
import { storage } from './firebase';

async function getFileURL(path: string): Promise<string> {
  const storageRef = ref(storage, path);
  const downloadURL = await getDownloadURL(storageRef);
  return downloadURL;
}
ファイルを Blob としてダウンロードする
import { ref, getBlob } from 'firebase/storage';
import { storage } from './firebase';

async function downloadFile(path: string): Promise<Blob> {
  const storageRef = ref(storage, path);
  const blob = await getBlob(storageRef);
  return blob;
}

// Trigger browser download
async function downloadAndSave(path: string, filename: string) {
  const blob = await downloadFile(path);
  const url = URL.createObjectURL(blob);

  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();

  URL.revokeObjectURL(url);
}
ファイルのメタデータを取得する
import { ref, getMetadata } from 'firebase/storage';
import { storage } from './firebase';

async function getFileMetadata(path: string) {
  const storageRef = ref(storage, path);
  const metadata = await getMetadata(storageRef);

  return {
    name: metadata.name,
    size: metadata.size,
    contentType: metadata.contentType,
    created: metadata.timeCreated,
    updated: metadata.updated,
    customMetadata: metadata.customMetadata,
  };
}

ファイル管理

ファイルを削除する
import { ref, deleteObject } from 'firebase/storage';
import { storage } from './firebase';

async function deleteFile(path: string): Promise<void> {
  const storageRef = ref(storage, path);
  await deleteObject(storageRef);
}
ディレクトリ内のファイルを一覧表示する
import { ref, listAll, list, getDownloadURL } from 'firebase/storage';
import { storage } from './firebase';

// List all files in a directory
async function listAllFiles(directoryPath: string) {
  const storageRef = ref(storage, directoryPath);
  const result = await listAll(storageRef);

  const files = await Promise.all(
    result.items.map(async (itemRef) => ({
      name: itemRef.name,
      fullPath: itemRef.fullPath,
      downloadURL: await getDownloadURL(itemRef),
    }))
  );

  const folders = result.prefixes.map((folderRef) => ({
    name: folderRef.name,
    fullPath: folderRef.fullPath,
  }));

  return { files, folders };
}

// Paginated list (for large directories)
async function listFilesPaginated(directoryPath: string, pageSize = 100) {
  const storageRef = ref(storage, directoryPath);
  const result = await list(storageRef, { maxResults: pageSize });

  // Get next page
  if (result.nextPageToken) {
    const nextPage = await list(storageRef, {
      maxResults: pageSize,
      pageToken: result.nextPageToken,
    });
  }

  return result;
}
メタデータを更新する
import { ref, updateMetadata } from 'firebase/storage';
import { storage } from './firebase';

async function updateFileMetadata(path: string, newMetadata: object) {
  const storageRef = ref(storage, path);
  const updatedMetadata = await updateMetadata(storageRef, {
    customMetadata: newMetadata,
  });
  return updatedMetadata;
}

サーバーサイドの操作(Admin SDK)

サーバーからアップロードする
import { adminStorage } from './firebase-admin';

async function uploadFromServer(
  buffer: Buffer,
  destination: string,
  contentType: string
) {
  const file = adminStorage.file(destination);

  await file.save(buffer, {
    contentType,
    metadata: {
      metadata: {
        uploadedBy: 'server',
        uploadTime: new Date().toISOString(),
      },
    },
  });

  // Make file publicly accessible (if needed)
  await file.makePublic();

  // Get public URL
  const publicUrl = `https://storage.googleapis.com/${adminStorage.name}/${destination}`;

  return publicUrl;
}
署名付き URL を生成する
import { adminStorage } from './firebase-admin';

async function generateSignedUrl(
  path: string,
  expiresInMinutes = 60
): Promise<string> {
  const file = adminStorage.file(path);

  const [signedUrl] = await file.getSignedUrl({
    action: 'read',
    expires: Date.now() + expiresInMinutes * 60 * 1000,
  });

  return signedUrl;
}

// For uploads (write access)
async function generateUploadUrl(path: string): Promise<string> {
  const file = adminStorage.file(path);

  const [signedUrl] = await file.getSignedUrl({
    action: 'write',
    expires: Date.now() + 15 * 60 * 1000, // 15 minutes
    contentType: 'application/octet-stream',
  });

  return signedUrl;
}
サーバーから削除する
import { adminStorage } from './firebase-admin';

async function deleteFromServer(path: string): Promise<void> {
  const file = adminStorage.file(path);
  await file.delete();
}

// Delete entire directory
async function deleteDirectory(directoryPath: string): Promise<void> {
  await adminStorage.deleteFiles({
    prefix: directoryPath,
  });
}

セキュリティルール

基本的なルールの構成
// storage.rules
rules_version = '2';

service firebase.storage {
  match /b/{bucket}/o {

    // Helper functions
    function isAuthenticated() {
      return request.auth != null;
    }

    function isOwner(userId) {
      return request.auth.uid == userId;
    }

    function isValidImage() {
      return request.resource.contentType.matches('image/.*')
        && request.resource.size < 5 * 1024 * 1024; // 5MB
    }

    function isValidDocument() {
      return request.resource.contentType.matches('application/pdf')
        && request.resource.size < 10 * 1024 * 1024; // 10MB
    }

    // User uploads (private to user)
    match /users/{userId}/{allPaths=**} {
      allow read: if isOwner(userId);
      allow write: if isOwner(userId)
        && (isValidImage() || isValidDocument());
    }

    // Public uploads (anyone can read)
    match /public/{allPaths=**} {
      allow read: if true;
      allow write: if isAuthenticated() && isValidImage();
    }

    // Profile pictures
    match /profiles/{userId}/avatar.{ext} {
      allow read: if true;
      allow write: if isOwner(userId)
        && request.resource.contentType.matches('image/.*')
        && request.resource.size < 2 * 1024 * 1024; // 2MB
    }

    // Deny all other access
    match /{allPaths=**} {
      allow read, write: if false;
    }
  }
}
ルールをデプロイする
firebase deploy --only storage

CORS の設定

CORS を設定する(Web では必須)

cors.json を作成します:

[
  {
    "origin": ["https://your-domain.com", "http://localhost:3000"],
    "method": ["GET", "PUT", "POST", "DELETE"],
    "maxAgeSeconds": 3600
  }
]

CORS 設定を適用します:

gsutil cors set cors.json gs://your-bucket-name.appspot.com

重要: CORS を設定しないと、ブラウザーからのアップロードは CORS エラーで失敗します。


React コンポーネント

ファイルアップロードコンポーネント
// components/FileUpload.tsx
'use client';

import { useState, useRef } from 'react';
import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';
import { storage } from '@/lib/firebase';

interface FileUploadProps {
  path: string;
  onUploadComplete: (url: string) => void;
  accept?: string;
  maxSize?: number; // in bytes
}

export function FileUpload({
  path,
  onUploadComplete,
  accept = 'image/*',
  maxSize = 5 * 1024 * 1024, // 5MB
}: FileUploadProps) {
  const [uploading, setUploading] = useState(false);
  const [progress, setProgress] = useState(0);
  const [error, setError] = useState<string | null>(null);
  const inputRef = useRef<HTMLInputElement>(null);

  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    // Validate file size
    if (file.size > maxSize) {
      setError(`File size must be less than ${maxSize / 1024 / 1024}MB`);
      return;
    }

    setError(null);
    setUploading(true);
    setProgress(0);

    try {
      const storageRef = ref(storage, `${path}/${Date.now()}-${file.name}`);
      const uploadTask = uploadBytesResumable(storageRef, file);

      uploadTask.on(
        'state_changed',
        (snapshot) => {
          const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
          setProgress(progress);
        },
        (error) => {
          setError(error.message);
          setUploading(false);
        },
        async () => {
          const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
          onUploadComplete(downloadURL);
          setUploading(false);
          setProgress(100);
        }
      );
    } catch (err: any) {
      setError(err.message);
      setUploading(false);
    }
  };

  return (
    <div>
      <input
        ref={inputRef}
        type="file"
        accept={accept}
        onChange={handleFileChange}
        disabled={uploading}
        className="hidden"
      />
      <button
        onClick={() => inputRef.current?.click()}
        disabled={uploading}
        className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
      >
        {uploading ? `Uploading... ${Math.round(progress)}%` : 'Upload File'}
      </button>
      {error && <p className="text-red-500 mt-2">{error}</p>}
    </div>
  );
}
アップロード機能付き画像プレビュー
// components/ImageUpload.tsx
'use client';

import { useState } from 'react';
import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';
import { storage } from '@/lib/firebase';
import Image from 'next/image';

interface ImageUploadProps {
  currentImage?: string;
  path: string;
  onUploadComplete: (url: string) => void;
}

export function ImageUpload({
  currentImage,
  path,
  onUploadComplete,
}: ImageUploadProps) {
  const [preview, setPreview] = useState<string | null>(currentImage || null);
  const [uploading, setUploading] = useState(false);
  const [progress, setProgress] = useState(0);

  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    // Show preview immediately
    const reader = new FileReader();
    reader.onloadend = () => {
      setPreview(reader.result as string);
    };
    reader.readAsDataURL(file);

    // Upload
    setUploading(true);
    const storageRef = ref(storage, `${path}/${Date.now()}-${file.name}`);
    const uploadTask = uploadBytesResumable(storageRef, file);

    uploadTask.on(
      'state_changed',
      (snapshot) => {
        setProgress((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
      },
      (error) => {
        console.error('Upload error:', error);
        setUploading(false);
      },
      async () => {
        const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
        onUploadComplete(downloadURL);
        setUploading(false);
      }
    );
  };

  return (
    <div className="relative w-32 h-32">
      {preview ? (
        <Image
          src={preview}
          alt="Preview"
          fill
          className="object-cover rounded-full"
        />
      ) : (
        <div className="w-full h-full bg-gray-200 rounded-full flex items-center justify-center">
          <span className="text-gray-500">No image</span>
        </div>
      )}

      <label className="absolute bottom-0 right-0 bg-blue-500 text-white p-2 rounded-full cursor-pointer">
        <input
          type="file"
          accept="image/*"
          onChange={handleFileChange}
          disabled={uploading}
          className="hidden"
        />
        {uploading ? `${Math.round(progress)}%` : '+'}
      </label>
    </div>
  );
}

エラー処理

よくある Storage エラー
エラーコード説明解決策
storage/unauthorizedユーザーにアクセス権がありませんセキュリティルールを確認し、ユーザーが認証済みであることを確認します
storage/canceledアップロード/ダウンロードがキャンセルされました適切に処理し、再試行できるようにします
storage/unknown不明なエラーですネットワークを確認し、間隔を空けて再試行します
storage/object-not-foundファイルが存在しませんパスを確認し、ファイルが見つからない場合の処理を行います
storage/bucket-not-foundバケットが存在しませんstorageBucket の設定を確認します
storage/quota-exceededストレージの割り当て容量を超過しましたプランをアップグレードするか、ファイルを削除します
storage/unauthenticatedユーザーが認証されていません先にユーザーをサインインさせます
storage/invalid-checksumアップロード中にファイルが破損しましたアップロードを再試行します
storage/retry-limit-exceeded再試行回数が多すぎますネットワークを確認し、後でもう一度試します
エラーハンドラーのユーティリティ
export function getStorageErrorMessage(error: any): string {
  const messages: Record<string, string> = {
    'storage/unauthorized': 'You do not have permission to access this file',
    'storage/canceled': 'Upload was canceled',
    'storage/object-not-found': 'File not found',
    'storage/quota-exceeded': 'Storage quota exceeded',
    'storage/unauthenticated': 'Please sign in to upload files',
    'storage/invalid-checksum': 'Upload failed. Please try again',
    'storage/retry-limit-exceeded': 'Upload failed after multiple retries',
  };

  return messages[error.code] || 'An unexpected error occurred';
}

既知の問題の予防

このスキルは、Firebase Storage で記録されている9 件のエラーを予防します:

問題 #エラー/問題説明回避方法出典
#1storage/unauthorizedセキュリティルールによってアクセスがブロックされますルールをテストし、ユーザーが認証済みであることを確認します一般的
#2CORS エラーブラウザーによってクロスオリジンリクエストがブロックされますgsutil cors set で CORS を設定しますドキュメント
#3大容量ファイルのタイムアウトアップロードがタイムアウトします大容量ファイルには uploadBytesResumable を使用します一般的
#4メモリの問題大容量ファイルをメモリに読み込んでいます大容量ファイルはストリーミングし、署名付き URL を使用します一般的
#5Content-Type の欠落ファイルが誤った MIME タイプで配信されますメタデータで必ず contentType を設定します一般的
#6URL の有効期限切れダウンロード URL が機能しなくなりますURL を再生成し、一時的なアクセスには署名付き URL を使用します一般的
#7storage/quota-exceeded無料プランの上限に達しました使用量を監視し、プランをアップグレードします一般的
#8秘密鍵の改行文字の問題Admin SDK の初期化に失敗します.replace(/\\n/g, '\n') を使用します一般的
#9重複アップロード同じファイルが複数回アップロードされますファイル名にタイムスタンプ/UUID を追加しますベストプラクティス

ベストプラクティス

常に行うこと
  1. アップロード前にファイルの種類とサイズを確認します
  2. 大きなファイルには**uploadBytesResumable を使用**します(一時停止/再開に対応)
  3. メタデータにコンテンツタイプを設定します
  4. 一意のファイル名を使用します(タイムスタンプまたは UUID を追加)
  5. アップロードエラーに対し、再試行を含めて適切に対処します
  6. コンポーネントのアンマウント時に、アップロードタスクの購読を解除します
  7. ウェブアプリケーション向けにCORS を設定します
  8. ファイルを保護するためにセキュリティルールを使用します
  9. 一時的なアクセス用に署名付き URL を生成します
絶対に行わないこと
  1. (本当に公開する場合を除き)認証なしでアップロードしないでください
  2. クライアントから提供されたファイル拡張子を決して信用しないでください(コンテンツタイプを確認します)
  3. 暗号化せずに機密データを保存しないでください
  4. クライアントコードで Admin SDK を公開しないでください
  5. ファイルサイズを無制限にしないでください(DoS のリスクがあります)

Firebase CLI コマンド

# Initialize Storage
firebase init storage

# Deploy security rules
firebase deploy --only storage

# Start emulator
firebase emulators:start --only storage

パッケージのバージョン(2026-01-25 確認済み)

{
  "dependencies": {
    "firebase": "^12.8.0"
  },
  "devDependencies": {
    "firebase-admin": "^13.6.0"
  }
}

公式ドキュメント


最終確認日: 2026-01-25 | スキルのバージョン: 1.0.0

コメント