mcpField
N
netlify-blobs

netlify-blobs

github.com/netlifyクラウド・DevOps473.1k 回インストール6 日前に更新
リポジトリ

netlify-blobs について

netlify-blobs は、Netlify 開発者向けのオブジェクトおよびファイルストレージを説明するスキルです。アップロードやキャッシュに近い状態の保存に適し、ストアの範囲、操作、一貫性の制約とトランザクションデータとの違いを扱います。

主な機能

  1. 01Blob と JSON の読み書き
  2. 02サイト共有・デプロイ別ストアの選択
  3. 03メタデータ取得と条件付き読み取り
  4. 04オブジェクトの一覧・ページ分割・削除
  5. 05一貫性とリージョンの設定

活用シーン

  1. 01ユーザーがアップロードしたファイルの保存
  2. 02デプロイ間でのオブジェクトや状態の保持
  3. 03デプロイプレビューのテストデータ分離

インストール

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

$npx skills add https://github.com/netlify/context-and-tools --skill netlify-blobs

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

詳細

Netlify Blobs

最新の構文:@netlify/blobs からインポートし、ストアを開いてからハンドルのメソッドを呼び出します。

import { getStore, getDeployStore, listStores } from "@netlify/blobs";
import type { Context } from "@netlify/functions"; // or "@netlify/edge-functions"

Functions、Edge Functions、Build Plugins では、siteID、deployID、token(および getDeployStore の region)が自動的に注入されます。npm install @netlify/blobs でインストールします。

データベースではありません。 動的なユーザーごとのデータ、トランザクションデータ、またはリレーショナルデータには Netlify DB を使用してください。Blobs はオブジェクト、ファイル、キャッシュのような状態を扱うためのものです。頻繁な読み取りと低頻度の書き込みに最適化されています。

ストアのスコープには注意が必要です。まずこちらをお読みください。 getStore は、すべてのデプロイコンテキストで共有されるサイト全体のストアを開きます。デプロイプレビュー上のコードは、本番データを読み取り、上書きし、削除します。サイト全体のストアに対して、プレビューから破壊的なテストを実行したり、使い捨てデータを登録したりしないでください。分離するには getDeployStore() またはコンテキスト固有のストア名を使用してください。

ストアの種類の選び方

  • getStore(name):サイト全体で、すべてのデプロイ間で共有されます。データはデプロイ後も保持され、プレビューから本番データが見えます。
  • getDeployStore(name):デプロイごとに分離され、単一のデプロイに限定されます。ロールバック時に同期され、デプロイの削除時にクリーンアップされます。分離が必要な場合や、失敗したデプロイによって破損してはならないデータに使用してください。
  • Build Plugins はサイトの任意のストアから読み取れますが、書き込み先はデプロイ固有のストア(getDeployStore)に限られます。 ファイルベースのアップロードも、デプロイ固有のストアにのみ書き込みます。

基本的な書き込みと読み取り

const uploads = getStore("file-uploads");

// set: value is ArrayBuffer | Blob | string
await uploads.set(key, file, { metadata: { country: "Spain" } });

// setJSON: any JSON-serializable value
await uploads.setJSON(key, { hello: "world" });

// get: returns value or null. type: text (default) | json | arrayBuffer | blob | stream
const entry = await uploads.get(key);            // string
const obj = await uploads.get(key, { type: "json" });
if (entry === null) { /* 404 */ }

set/setJSON は既存のキーを上書きします。どちらも { modified, etag } を返します。新しいエントリーが生成されなかった場合、etag は省略されます。

ユーザーのアップロードを保存する(Function)
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";
import { v4 as uuid } from "uuid";

export default async (req: Request, context: Context) => {
  const form = await req.formData();
  const file = form.get("file") as File;
  const key = uuid();
  const uploads = getStore("file-uploads");
  await uploads.set(key, file, { metadata: { country: context.geo.country.name } });
  return new Response("Submission saved");
};

Edge Functions も同じですが、import type { Context } from "@netlify/edge-functions"; を使用します。

読み取り(Function)
export default async (req: Request, context: Context) => {
  const { key } = context.params;
  const uploads = getStore("file-uploads");
  const entry = await uploads.get(key);
  if (entry === null) return new Response(`Not found: ${key}`, { status: 404 });
  return new Response(entry);
};

メタデータと条件付き読み取り

分割代入の前に null チェックを行ってください。 getWithMetadata と getMetadata は、キーが存在しない場合に null を返します。戻り値を直接分割代入すると(const { data } = await store.getWithMetadata(key))、キーが見つからない場合にエラーが発生します。結果を変数に代入し、null かどうかを確認してからフィールドを読み取ってください。

// getWithMetadata: data + metadata + etag
const entry = await uploads.getWithMetadata(key);
if (entry === null) return new Response(`Not found: ${key}`, { status: 404 });
const { data, metadata } = entry;
return new Response(data, { headers: { "X-Country": metadata.country } });

// getMetadata: metadata + etag only, without downloading the blob
const meta = await uploads.getMetadata(key); // { etag, metadata } or null
if (meta === null) { /* missing */ }

どちらも { consistency, etag, type } を受け取ります。

条件付き読み取り: キャッシュ済みの etag を渡します。サーバー側で一致する場合、data は null となり、手元のデータが最新であることを意味します。前後の引用符や弱い検証子のプレフィックスも含め、ETag 値全体を比較してください。

const entry = await uploads.getWithMetadata("my-key", { etag: cachedETag });
if (entry !== null && entry.etag === cachedETag) {
  // entry.data is null: cached copy still fresh
}

並行処理:アトミックな条件付き書き込み

後からの書き込みが優先されます。並行制御はありません。 onlyIfMatch による再試行を行う場合でも、blob キーを使ってカウンター、残高、読み取り後の書き戻し処理を実装しないでください。これらはトランザクションデータです。Netlify DB を使用してください。

set/setJSON は { onlyIfNew, onlyIfMatch } を受け取ります。

// Create only if key does not exist
const { modified } = await emails.set("[email protected]", "Jane Doe", { onlyIfNew: true });
if (!modified) return new Response("Email already exists", { status: 400 });

// Update only if the ETag still matches
const { modified } = await emails.set("[email protected]", "New Jane", { onlyIfMatch: etag });
if (!modified) return new Response("Cached data is stale", { status: 400 });

一覧表示

const { blobs } = await uploads.list(); // blobs: [{ etag, key }]

list({ directories, paginate, prefix })。/ を使ってキーを階層的にグループ化します。

const { blobs, directories } = await animals.list({ directories: true });
// directories: ["cats", "dogs"]; blobs: top-level keys only

// Drill in. Trailing slash REQUIRED (without it "catsuit" also matches)
const res = await animals.list({ directories: true, prefix: "cats/" });

ページネーション:list はデフォルトで、最大 1,000 件ずつのすべてのページを返します。paginate: true を設定すると、AsyncIterator が返されます。

for await (const page of store.list({ paginate: true })) {
  console.log(page.blobs);
}

listStores({ paginate }) は、最大 1,000 件ずつのページで { stores: string[] } を返します。デプロイ固有のストアは含まれません。

削除

await uploads.delete(key);                        // resolves undefined
const { deletedBlobs } = await uploads.deleteAll(); // deletes every object = deletes the store

有効期限(サーバー側 TTL なし)

Blobs は自動では期限切れになりません。メタデータに有効期限のタイムスタンプを保存し、読み取り時に確認して、期限を過ぎた blob を delete で削除してください。

await uploads.set(key, body, { metadata: { expiration: new Date("2025-01-01").getTime() } });
const entry = await uploads.getWithMetadata(key);
if (entry === null) return new Response("Blob does not exist");
const { expiration } = entry.metadata;
if (expiration && expiration < Date.now()) await uploads.delete(key);

一貫性

デフォルトは結果整合性です。新しい書き込みはすぐにグローバルで利用できますが、更新や削除がすべてのエッジロケーションに反映されるまで最大 60 秒かかります。ストアごと、または読み取りごとに強い一貫性を有効にできます。

const store = getStore({ name: "animals", consistency: "strong" }); // whole store
await store.get("dog", { consistency: "strong" });                  // single read

Netlify CLI は常に強い一貫性を使用します。

リージョン

region には、関数のリージョンに使われる空港コードではなく、AWS のリージョンコードを指定します。サポートされる値は ap-southeast-1、ap-southeast-2、eu-central-1、us-east-1、us-east-2 です。それ以外の値を指定すると、リクエストを行う前に InvalidBlobsRegionError がスローされます。

  • デプロイ固有のストアは、デフォルトで関数のリージョンになります(自動的に挿入されます)。
  • サイト全体のストアは、デフォルトで us-east-2 になり、関数のリージョンには追従しません。

注意点:サイト全体のリージョンは呼び出しごとに指定します。 サイト全体のストアを特定のリージョンに維持するには、そのストアに対する getStore の呼び出し(読み取り、書き込み、削除)すべてで region を指定してください。省略した呼び出しでは us-east-2 が使われ、データが存在しないように見えます。エラーも警告も発生しません。

注意点:リージョンを変更してもデータは移動しません。 新しいリージョンではストアが空に見えますが、データは古いリージョンに残ります。移行するには、新しいリージョンで開いたストアに各エントリをコピーしてから、古いストアから削除してください。

const uploads = getDeployStore({ name: "file-uploads", region: "ap-southeast-2" });
const profiles = getStore({ name: "user-profiles", region: "eu-central-1" });

ファイルベースのアップロード(ビルドプラグイン不要)

ベースディレクトリ内の .netlify/blobs/deploy にファイルを配置します。Netlify はディレクトリ構造を維持したまま、デプロイ固有のストアにアップロードします。メタデータを付与するには、同じ階層に $<filename>.json という名前の JSON ファイルを追加します。有効な JSON でない場合、デプロイは失敗します。

.netlify/blobs/deploy/
├─ dogs/good-boy.jpg
├─ dogs/$good-boy.jpg.json   # metadata for good-boy.jpg
├─ cat.jpg
└─ mouse.jpg

注意: Netlify は各ビルドの前に .netlify/blobs/deploy を空にします。リポジトリにコミットされたファイルはアップロードされません。ビルドコマンドまたはビルドプラグインを使って、ビルド中に Blob ファイルを作成してください。

アクセス制御(デフォルトはプライベート)

Blob には組み込みのアクセス制御がありません。アクセス制御の役割は配信関数が担います。Blob にアクセスできるのはサイト独自のコード経由のみであり、保存時および転送時に暗号化されます。判断に迷った場合は、デフォルトでプライベートにし、Blob を公開するのではなく、認証済みの関数の背後で読み取りを制御してください。

機密データの場合、ユーザーが任意に指定したキーを配信しないでください。呼び出し元が改ざんできない情報を使ってキーの範囲を限定してください。Blobs は Netlify の HIPAA 準拠サービスには含まれていません。

制約

  • ストア名:/ または : は使用できません。最大 64 バイトです。
  • キー:空であってはならず、/ で始めることはできません。最大 600 バイトです。あらゆる Unicode 文字を使用できますが、1 バイトを超える文字もあります。
  • オブジェクトの最大サイズは 5 GB、メタデータの最大サイズは 2 KB です。
  • Go で記述された関数は Netlify Blobs にアクセスできません。
  • Fetch API が必要です(Node.js 18 以降)。それ以外の場合は、カスタムの fetch を渡してください:getStore({ fetch, name: "file-uploads" })。
  • ローカル開発(Netlify Dev)では、サンドボックス化されたローカルストアが使われます。ファイルベースのアップロードには対応しておらず、本番データを読み取ることもできません。
  • ファイルベースのアップロードには、継続的デプロイまたは CLI によるデプロイが必要です。

操作に失敗した場合

エラーを表示し、関数のログを確認してください。再試行のために REST エンドポイントやサイドチャネル API を作り出さないでください。

CLI と UI

確認用の netlify blobs:list/get/set/delete が用意されています。CLI コマンドリファレンスを参照してください。UI で Blob を閲覧、ダウンロードするには、Data & Storage > Blobs に移動してください。

モジュールのバージョン移行

@netlify/blobs 6.5.0 以前で書き込まれたサイト全体のストアは、名前空間の変更により、アップグレード後にアクセスできなくなります。最新の CLI を使って移行してから、モジュール 7.0.0 以降を使用してください:

netlify recipes blobs-migrate YOUR_STORE_NAME

リファレンス

API の詳細と背景については、Netlify Blobs のドキュメントおよびデータとストレージの概要を参照してください。

Netlify の Blobs に関する運用ルール

これらは組織内の規約であり、ドキュメントの事実ではありません。ctx-gen によってレンダリングされたスキルに統合され、生成されることはありません。スキルのメンテナーが管理します。

  1. Blobs はデータベースではありません。動的なデータ、ユーザーごとのデータ、またはトランザクションデータには Netlify DB を使用してください。Blobs はオブジェクト、ファイル、キャッシュのような状態を保存するためのものです。
  2. ストア操作に失敗した場合は、エラーを表示して関数のログを確認してください。再試行のために REST エンドポイントやサイドチャネル API を作り出さないでください。
  3. netlify blobs:list/get/set/delete は調査用に使用できます。CLI リファレンスが正確な情報源なので、内容を書き直さずリンクしてください。
  4. Blobs には組み込みのアクセス制御がありません。アクセスを制御するのは提供関数です。判断に迷う場合は、読み取りを認証済み関数の背後で制御し、Blobs を公開しないことを基本としてください。
  5. サイトスコープのストアは、すべてのデプロイコンテキストで共有されます。デプロイプレビュー上のコードは、本番データを読み取り、上書きし、削除します。プレビューから破壊的なテストを実行したり、使い捨てデータを投入したりしないでください。分離には getDeployStore() またはコンテキスト固有のストア名を使用してください。
  6. onlyIfMatch による再試行を行う場合も含め、Blob キーを使ってカウンター、残高、読み取り・変更・書き込みのロジックを構築しないでください。これらはトランザクションデータなので、Netlify DB を使用してください。
  7. ビルドプラグインでは、両方の点を明記してください。サイト内のどのストアからも読み取れますが、書き込みはデプロイ固有のストア(getDeployStore)に限られます。

コメント