关于 netlify-blobs
netlify-blobs 是面向 Netlify 开发者的对象与文件存储技能,适用于上传文件及保存缓存类状态。它说明站点级和部署级存储的范围、读写操作与一致性限制,并强调它不适合事务数据。
主要功能
- 01读写 Blob 与 JSON 数据
- 02选择站点级或部署级存储
- 03读取元数据并进行条件读取
- 04列出、分页和删除对象
- 05配置一致性与存储区域
适用场景
- 01保存用户上传的文件
- 02在部署间保存对象或缓存状态
- 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);
};
元数据与条件读取
解构前先检查空值。 如果键不存在,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 不会自动过期。请在元数据中存储过期时间戳,读取时检查,并在过期后调用 delete 删除 blob:
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 会将它们上传到部署专属存储,并保留目录结构。要附加元数据,请添加同级 JSON 文件,文件名为 $<filename>.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,请前往 数据与存储 > Blobs。
模块版本迁移
使用 @netlify/blobs 6.5.0 或更早版本写入的站点级存储,在升级后将无法访问,因为命名空间发生了变化。请使用最新的 CLI 迁移,然后使用 7.0.0+ 版本的模块:
netlify recipes blobs-migrate YOUR_STORE_NAME
参考
完整 API 和背景说明:Netlify Blobs 文档和数据与存储概览。
Netlify Blobs 约定
这些是组织约定,而非文档事实——由 ctx-gen 合并到渲染后的技能中,不会自动生成。由技能维护者负责。
- Blobs 不是数据库。对于动态、按用户区分或事务性数据,请使用 Netlify DB——Blobs 用于存储对象、文件和类似缓存的状态。
- 存储操作失败时,应报告错误并查看函数日志——不要虚构 REST 端点或旁路 API 来重试。
netlify blobs:list/get/set/delete可用于检查;CLI 参考是这些命令的权威来源——请链接到参考文档,不要重复描述。- Blobs 没有内置访问控制——提供服务的函数负责把关。如有疑问,默认设为私有:通过经过身份验证的函数限制读取,而不是公开暴露 Blobs。
- 站点范围的存储会在所有部署上下文之间共享——部署预览中的代码可以读取、覆盖和删除生产数据。切勿从预览中运行破坏性测试或写入临时数据;请使用
getDeployStore()或特定上下文的存储名称来实现隔离。 - 不要基于一个 Blob 键实现计数器、余额或读改写逻辑——即使使用
onlyIfMatch重试也不行。这些属于事务性数据;请使用 Netlify DB。 - 构建插件:两方面都要说明——它们可以从站点的任何存储中读取,但只能写入特定部署的存储(
getDeployStore)。