Cloudflare R2 bende kullanmaya yeni başladım açıkcası, Aşağıdaki işlemleri uyguladım takip ediyorum.


Cloudflare için, Depolama 10Gb bunu birşekilde tutuyorsunuz
Class B okuma bunda biraz problem var 10M versede okuma geçebiliyor bunun için aşağıdaki önemlemleri aldım size de tavsiye ederim.

1) Cloudflare R2 kurarken panelden bir domaine CDN vermeniz ve verdiğiniz CDN cache yeni cache rules oluşturmanız 1 aylık tutmanız lazım. Aynı içerikleri yüklüyor iseniz yüklerken ?v=zamanlama kurmanızı tavsiye ederim.
2) Aşağıdaki Nojde apisini kullanarak
{"success":true,"data":{"objectCount":503,"totalSi ze":"42.98 MB","classAOps":1550,"classBOps":980}} cache kontrol altında tutabilirsiniz.
3) Cloudflare R2 için özellikle dosya temizleme scriptiniz olacak kullanılmayan resimlerin temizlenmesi gerekli.
4) Bölge seçmeniz önemli seçtiğiniz bölge hizmet verdiğiniz yerdir Eğer bölge dışı bir yerde hizmet veriyorsanız bu nedenle Cache kullanmanız önemli ilk yavaş açar sonra cache den devam eder.
const uploadToR2 = async (objectName, buffer, contentType) => {
    const s3Client = getS3Client();
    const bucketName = process.env.R2_BUCKET_NAME;
    const publicUrlBase = process.env.R2_PUBLIC_URL;
    const command = new PutObjectCommand({
        Bucket: bucketName,
        Key: objectName,
        Body: buffer,
        ContentType: contentType,
        CacheControl: 'public, max-age=31536000, immutable', // 1 yıl dosyaların cacheControllenmesi eklerken bildiriyoruz yada CDNle create rules oluşturun.


    });
    await s3Client.send(command);
    const url = `${publicUrlBase}/${objectName}`;
    Logger.info(`Cloudflare R2'ye yüklendi: ${objectName}`);
    return { url, key: objectName };
};
const axios = require('axios');
async function getR2Operations() {
    const apiToken = process.env.CLOUDFLARE_API_TOKEN;
    const accountId = process.env.R2_ACCOUNT_ID;
    const bucketName = process.env.R2_BUCKET_NAME;
    if (!apiToken || !accountId || !bucketName) {
        throw new Error('Cloudflare API Token, Account ID veya Bucket Name eksik!');
    }
    const startOfMonth = new Date(new Date().getFullYear(), new Date().getMonth(), 1);
    startOfMonth.setUTCHours(0, 0, 0, 0);
    const now = new Date();
    const startDate = startOfMonth.toISOString().split('.')[0] + "Z";
    const endDate = now.toISOString().split('.')[0] + "Z";
    const response = await axios.post(
        'https://api.cloudflare.com/client/v4/graphql',
        {
            query: `
              query GetBucketOperations($accountId: String!, $bucketName: String, $startDate: Time, $endDate: Time) {
                viewer {
                  accounts(filter: {accountTag: $accountId}) {
                    r2OperationsAdaptiveGroups(
                      limit: 1000,
                      filter: {
                        datetime_geq: $startDate,
                        datetime_leq: $endDate,
                        bucketName: $bucketName
                      }
                    ) {
                      sum {
                        requests
                      }
                        
                      dimensions {
                        actionType
                      }
                    }
                  }
                }
              }
            `,
            variables: { accountId, bucketName, startDate, endDate }
        },
        {
            headers: {
                'Authorization': `Bearer ${apiToken}`,
                'Content-Type': 'application/json',
            },
        }
    );
    if (response.data.errors) {
        const errorMessage = response.data.errors[0].message;
        console.error("Cloudflare GraphQL API Hatası:", errorMessage);
        throw new Error(`Cloudflare API Hatası: ${errorMessage}`);
    }
    const operationsList = response.data.data.viewer.accounts[0].r2OperationsAdaptiveGroups;
    let classA = 0;
    let classB = 0;
    let free = 0; // Ücretsiz işlemleri de takip etmek için eklendi (isteğe bağlı)
    if (operationsList && operationsList.length > 0) {
        for (const item of operationsList) {
            const action = item.dimensions.actionType;
            const count = item.sum.requests;
            switch (action) {
                // ✅ Class B (Okuma ve Durum Sorgulama)
                case 'GetObject':
                case 'HeadObject':
                case 'HeadBucket': // HATA DÜZELTİLDİ: Class A'dan B'ye taşındı.
                case 'GetBucketCors': // HATA DÜZELTİLDİ: Class A'dan B'ye taşındı.
                case 'GetBucketLifecycleConfiguration': // HATA DÜZELTİLDİ: Class A'dan B'ye taşındı.
                case 'GetBucketEncryption': // Listeye göre eklendi.
                case 'GetBucketLocation': // Listeye göre eklendi.
                case 'UsageSummary': // Listeye göre eklendi.
                case 'SelectObjectContent': // Genellikle Class B olarak kabul edilir.
                    classB += count;
                    break;
                // ✅ Class A (Yazma, Listeleme ve Değiştirme)
                case 'PutObject': //
                case 'ListObjects': //
                case 'ListObjectsV2': //
                case 'CopyObject': //
                case 'ListMultipartUploads': //
                case 'UploadPart': //
                case 'UploadPartCopy': //
                case 'CreateMultipartUpload': //
                case 'CompleteMultipartUpload': //
                case 'PutBucket': //
                case 'ListBuckets': //
                case 'PutBucketEncryption': // .
                case 'PutBucketCors': //
                case 'PutBucketLifecycleConfiguration': //
                case 'ListParts': // Listeye göre eklendi.
                case 'LifecycleStorageTierTransition': //
                    classA += count;
                    break;
                
                // ✅ Ücretsiz Operasyonlar
                case 'DeleteObject':
                case 'DeleteObjects':.
                case 'AbortMultipartUpload':
                case 'DeleteBucket':
                    free += count;
                    // Bu operasyonlar ücretsiz olduğu için hiçbir maliyet sayacı artırılmaz.
                    break;
            }
        }
    }
    console.log(`📊 R2 Kullanım Özeti (Doğru Hesaplama):
    Class A (Yazma): ${classA}
    Class B (Okuma): ${classB}
    Ücretsiz: ${free}`);
    return { classA, classB, free };
}
module.exports = { getR2Operations };
{"success":true,"data":{"objectCount":23563,"total Size":"2.35 GB","classAOps":37160,"classBOps":41080}} tam bir aylık yoğun şekilde kullanım sağladım.

Tek problem depolama alanı, ClassBops için gerçekten cache çok işe yarıyor depolama alanınıda 10 gb altında tutarsanız ömür boyu ücretsiz