关于 vercel-kv
vercel-kv 是面向使用 Vercel KV 的开发者的技能,该存储兼容 Redis,可通过 @vercel/kv 访问。内容涵盖配置,以及缓存、计数器、会话、限流和批量操作模式。
主要功能
- 01配置 Vercel KV 凭据
- 02读写兼容 JSON 的值
- 03使用兼容 Redis 的原子操作
- 04设置 TTL 和命名空间键
- 05通过管道批量执行命令
- 06采用缓存与会话模式
适用场景
- 01缓存应用数据
- 02跟踪计数器和请求限流
- 03存储临时会话数据
- 04通过单个管道执行多条 KV 命令
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ovachiever/ droid-tings --skill vercel-kv安装后在 Claude Code 中输入/vercel-kv确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.md.claude-plugin/plugin.jsonassets/example-template.txtreferences/example-reference.mdscripts/example-script.shtemplates/package.jsonsession-management.tssimple-rate-limiting.ts详细介绍
Vercel KV(兼容 Redis 的存储)
状态:已可用于生产环境
最近更新:2025-10-29
依赖项:无
最新版本:@vercel/[email protected]
快速开始(3 分钟)
1. 创建 Vercel KV 数据库
# In your Vercel project dashboard
# Storage → Create Database → KV
# Pull environment variables locally
vercel env pull .env.local
这会自动创建:
KV_REST_API_URL- KV 数据库 URLKV_REST_API_TOKEN- 身份验证令牌KV_REST_API_READ_ONLY_TOKEN- 只读令牌(可选)
2. 安装软件包
npm install @vercel/kv
3. 在应用中使用
Next.js 服务端操作:
'use server';
import { kv } from '@vercel/kv';
export async function incrementViews(slug: string) {
const views = await kv.incr(`views:${slug}`);
return views;
}
边缘 API 路由:
import { kv } from '@vercel/kv';
export const runtime = 'edge';
export async function GET(request: Request) {
const value = await kv.get('mykey');
return Response.json({ value });
}
重要提示:
- 临时数据务必设置 TTL:
await kv.setex('key', 3600, value) - 为键使用命名空间:使用
user:${id}:profile,而不是单独使用${id} - JSON 值必须可序列化(不能包含函数或循环引用)
5 步设置流程
第 1 步:创建 KV 数据库
选项 A:Vercel 控制台
- 前往你的 Vercel 项目
- Storage → Create Database → KV
- 为数据库命名
- 复制环境变量
选项 B:Vercel CLI
vercel env pull .env.local
这会创建:
# .env.local (automatically created)
KV_REST_API_URL="https://xyz.kv.vercel-storage.com"
KV_REST_API_TOKEN="your-token-here"
KV_REST_API_READ_ONLY_TOKEN="your-readonly-token"
要点:
- 建议每个项目使用一个 KV 数据库
- 免费层级:每月 30,000 条命令,256MB 存储空间
- Vercel 部署会自动设置环境变量
第 2 步:安装并配置
npm install @vercel/kv
本地开发时,创建 .env.local:
# .env.local
KV_REST_API_URL="https://your-db.kv.vercel-storage.com"
KV_REST_API_TOKEN="your-token"
生产环境中,环境变量会自动提供。
Cloudflare Workers(使用 Vercel KV):
# wrangler.toml
[vars]
KV_REST_API_URL = "https://your-db.kv.vercel-storage.com"
[[secrets]]
KV_REST_API_TOKEN = "your-token"
第 3 步:基本操作
设置/获取:
import { kv } from '@vercel/kv';
// Set a value
await kv.set('user:123', { name: 'Alice', email: '[email protected]' });
// Get a value
const user = await kv.get('user:123');
// Returns: { name: 'Alice', email: '[email protected]' }
// Set with TTL (expires in 1 hour)
await kv.setex('session:abc', 3600, { userId: 123 });
// Check if key exists
const exists = await kv.exists('user:123'); // Returns 1 if exists, 0 if not
// Delete a key
await kv.del('user:123');
原子操作:
// Increment counter
const views = await kv.incr('views:post:123');
// Decrement counter
const stock = await kv.decr('inventory:item:456');
// Increment by amount
await kv.incrby('score:user:789', 10);
// Set if not exists (returns 1 if set, 0 if key already exists)
const wasSet = await kv.setnx('lock:process', 'running');
多项操作:
// Get multiple keys
const values = await kv.mget('user:1', 'user:2', 'user:3');
// Returns: [{ name: '...' }, { name: '...' }, null]
// Set multiple keys
await kv.mset({
'user:1': { name: 'Alice' },
'user:2': { name: 'Bob' }
});
// Delete multiple keys
await kv.del('key1', 'key2', 'key3');
要点:
- 值会自动进行 JSON 序列化
- 对于不存在的键,会返回
null - 所有操作都是原子的
- TTL 的单位为秒
第 4 步:高级模式
缓存模式:
import { kv } from '@vercel/kv';
async function getPost(slug: string) {
// Try cache first
const cached = await kv.get(`post:${slug}`);
if (cached) return cached;
// Fetch from database
const post = await db.select().from(posts).where(eq(posts.slug, slug));
// Cache for 1 hour
await kv.setex(`post:${slug}`, 3600, post);
return post;
}
速率限制:
import { kv } from '@vercel/kv';
async function checkRateLimit(ip: string): Promise<boolean> {
const key = `ratelimit:${ip}`;
const limit = 10; // 10 requests
const window = 60; // per 60 seconds
const current = await kv.incr(key);
if (current === 1) {
// First request, set TTL
await kv.expire(key, window);
}
return current <= limit;
}
// Usage in API route
export async function POST(request: Request) {
const ip = request.headers.get('x-forwarded-for') || 'unknown';
if (!await checkRateLimit(ip)) {
return new Response('Rate limit exceeded', { status: 429 });
}
// Process request...
}
会话管理:
import { kv } from '@vercel/kv';
import { cookies } from 'next/headers';
export async function createSession(userId: number) {
const sessionId = crypto.randomUUID();
const sessionData = { userId, createdAt: Date.now() };
// Store session for 7 days
await kv.setex(`session:${sessionId}`, 7 * 24 * 3600, sessionData);
// Set cookie
cookies().set('session', sessionId, {
httpOnly: true,
secure: true,
maxAge: 7 * 24 * 3600
});
return sessionId;
}
export async function getSession() {
const sessionId = cookies().get('session')?.value;
if (!sessionId) return null;
return await kv.get(`session:${sessionId}`);
}
Pipeline(批量操作):
import { kv } from '@vercel/kv';
// Execute multiple commands in a single round-trip
const pipeline = kv.pipeline();
pipeline.set('user:1', { name: 'Alice' });
pipeline.incr('counter');
pipeline.get('config');
const results = await pipeline.exec();
// Returns: ['OK', 1, { ... }]
第 5 步:键命名约定
使用命名空间:
// ❌ Bad: No structure
await kv.set('123', data);
// ✅ Good: Clear namespace
await kv.set('user:123', data);
await kv.set('post:abc:views', 100);
await kv.set('cache:homepage:en', html);
命名模式:
user:{id}:profile- 用户个人资料数据post:{slug}:views- 帖子的浏览量计数器cache:{page}:{locale}- 缓存的页面内容session:{token}- 会话数据ratelimit:{ip}:{endpoint}- 速率限制跟踪lock:{resource}- 分布式锁
重要规则
务必做到
✅ 为临时数据设置 TTL - 避免内存泄漏和过期数据
✅ 使用带命名空间的键 - 使用 user:123,而不是 123(避免冲突)
✅ 处理 null 返回值 - 不存在的键会返回 null
✅ 多项操作使用 pipeline - 降低延迟(只需一次往返)
✅ 仅序列化兼容 JSON 的数据 - 不要包含函数、循环引用等
✅ 使用 SETNX 实现分布式锁 - 防止竞态条件
✅ 监控命令使用量 - 确保不超过免费层级的限制(每月 30K 条命令)
✅ 公开读取时使用只读令牌 - 安全性更高
务必避免
❌ 切勿在未加密的情况下存储敏感数据 - 默认情况下,KV 不会对静态数据加密
❌ 切勿忘记设置 TTL - 未设置 TTL 的键会永久保留(造成内存泄漏)
❌ 切勿使用通用键名 - data、cache、temp 等名称会发生冲突
❌ 切勿存储较大的值(>1MB) - 大文件请使用 Vercel Blob
❌ 切勿将 KV 用作主数据库 - 它是缓存,不是持久化存储
❌ 切勿超过速率限制 - 免费层级每月限 30K 条命令
❌ 切勿假设数据具有强持久性 - KV 用于临时数据,而非关键数据
❌ 切勿提交 .env.local - 其中包含 KV 令牌(请将其添加到 .gitignore)
预防已知问题
此技能可预防 10 个已记录的问题:
问题 #1:缺少环境变量
错误:Error: KV_REST_API_URL is not defined 或 KV_REST_API_TOKEN is not defined
来源:https://vercel.com/docs/storage/vercel-kv/quickstart
发生原因:本地或部署环境中未设置环境变量
预防方法:运行 vercel env pull .env.local,并确保 .env.local 已添加到 .gitignore。
问题 #2:JSON 序列化错误
错误:TypeError: Do not know how to serialize a BigInt 或循环引用错误
来源:https://github.com/vercel/storage/issues/89
原因:尝试存储无法序列化为 JSON 的数据(函数、BigInt、循环引用)
预防措施:仅存储普通对象、数组、字符串、数字、布尔值和 null。将 BigInt 转换为字符串。
问题 #3:键名冲突
错误:返回了意外数据,数据被其他功能覆盖
来源:生产环境调试、最佳实践
原因:在不同功能中使用 cache、data、temp 等通用键名
预防措施:始终使用命名空间键:遵循 feature:id:type 模式。
问题 #4:未设置 TTL
错误:内存使用量持续增长,旧数据永不过期
来源:Vercel KV 最佳实践
原因:临时数据使用 set() 而不是 setex()
预防措施:所有临时数据都使用 setex(key, ttl, value)。设置合适的 TTL(秒)。
问题 #5:超出速率限制(免费层级)
错误:Error: Rate limit exceeded 或命令失败
来源:https://vercel.com/docs/storage/vercel-kv/limits
原因:超出免费层级每月 30,000 条命令的限制
预防措施:在 Vercel 控制面板中监控用量;如有需要,升级套餐并使用缓存来减少 KV 调用。
问题 #6:存储的值过大
错误:Error: Value too large 或性能下降
来源:https://vercel.com/docs/storage/vercel-kv/limits
原因:尝试在 KV 中存储大于 1 MB 的值
预防措施:文件和图片使用 Vercel Blob。保持 KV 值较小(建议小于 100 KB)。
问题 #7:Get 时类型不匹配
错误:TypeScript 错误、运行时类型错误
来源:常见的 TypeScript 问题
原因:kv.get() 返回 unknown 类型,需要进行类型转换或验证
预防措施:使用类型断言并进行验证:const user = await kv.get<User>('user:123'),并使用 Zod 验证。
问题 #8:未处理管道错误
错误:静默失败、部分执行
来源:https://github.com/vercel/storage/issues/120
原因:管道执行过程中,个别命令可能会失败
预防措施:检查 pipeline.exec() 返回的结果数组并处理错误。
问题 #9:Scan 操作效率低下
错误:查询缓慢、超时错误
来源:Redis 最佳实践
原因:数据集较大或游标处理错误时使用 scan()
预防措施:限制 count 参数,使用游标正确迭代,并避免在生产环境中进行全量扫描。
问题 #10:未刷新 TTL
错误:会话过早过期,缓存过早失效
来源:生产环境调试
原因:访问时未刷新 TTL(滑动过期)
预防措施:访问时使用 expire(key, newTTL) 实现滑动窗口。
配置文件参考
package.json
{
"dependencies": {
"@vercel/kv": "^3.0.0"
}
}
.env.local(本地开发)
# Created by: vercel env pull .env.local
KV_REST_API_URL="https://your-database.kv.vercel-storage.com"
KV_REST_API_TOKEN="your-token-here"
KV_REST_API_READ_ONLY_TOKEN="optional-readonly-token"
.gitignore
.env.local
.env*.local
常见模式
模式 1:旁路缓存(延迟加载)
import { kv } from '@vercel/kv';
async function getUser(id: number) {
const cacheKey = `user:${id}`;
// Check cache
const cached = await kv.get<User>(cacheKey);
if (cached) return cached;
// Fetch from database
const user = await db.query.users.findFirst({
where: eq(users.id, id)
});
if (!user) return null;
// Cache for 5 minutes
await kv.setex(cacheKey, 300, user);
return user;
}
模式 2:直写缓存
import { kv } from '@vercel/kv';
async function updateUser(id: number, data: Partial<User>) {
// Update database
const updated = await db.update(users)
.set(data)
.where(eq(users.id, id))
.returning();
// Update cache
await kv.setex(`user:${id}`, 300, updated[0]);
return updated[0];
}
模式 3:分布式锁
import { kv } from '@vercel/kv';
async function acquireLock(resource: string, timeout: number = 10) {
const lockKey = `lock:${resource}`;
const lockValue = crypto.randomUUID();
// Try to set lock (only if not exists)
const acquired = await kv.setnx(lockKey, lockValue);
if (acquired) {
// Set TTL to prevent deadlock
await kv.expire(lockKey, timeout);
return lockValue;
}
return null;
}
async function releaseLock(resource: string, lockValue: string) {
const lockKey = `lock:${resource}`;
const current = await kv.get(lockKey);
// Only delete if we own the lock
if (current === lockValue) {
await kv.del(lockKey);
}
}
// Usage
const lock = await acquireLock('process-orders');
if (lock) {
try {
await processOrders();
} finally {
await releaseLock('process-orders', lock);
}
}
模式 4:排行榜
import { kv } from '@vercel/kv';
async function updateScore(userId: number, score: number) {
await kv.zadd('leaderboard', { score, member: userId.toString() });
}
async function getTopPlayers(limit: number = 10) {
// Get top scores (descending)
const top = await kv.zrange('leaderboard', 0, limit - 1, { rev: true, withScores: true });
return top;
}
async function getUserRank(userId: number) {
// Get user's rank (0-based)
const rank = await kv.zrevrank('leaderboard', userId.toString());
return rank !== null ? rank + 1 : null;
}
依赖项
必需:
@vercel/kv@^3.0.0- Vercel KV 客户端库
可选:
zod@^3.24.0- KV 数据的运行时类型验证ioredis-mock@^8.9.0- 用于测试的 KV 模拟库
官方文档
- Vercel KV:https://vercel.com/docs/storage/vercel-kv
- Vercel KV 快速入门:https://vercel.com/docs/storage/vercel-kv/quickstart
- Vercel KV SDK 参考:https://vercel.com/docs/storage/vercel-kv/kv-reference
- GitHub:https://github.com/vercel/storage
- Redis 命令:https://redis.io/commands(Vercel KV 兼容 Redis)
软件包版本(已于 2025-10-29 验证)
{
"dependencies": {
"@vercel/kv": "^3.0.0"
}
}
生产环境示例
此技能基于 Vercel KV 的生产部署:
- Next.js 电商:会话管理、购物车缓存、速率限制
- 博客平台:浏览量计数器、页面缓存、API 缓存
- API 网关:速率限制、响应缓存、分布式锁
- 错误:0(已防止全部 10 个已知问题)
- 运行时间:99.9% 以上(Upstash SLA)
故障排查
问题:KV_REST_API_URL is not defined
解决方案:运行 vercel env pull .env.local 获取环境变量。
问题:超出速率限制(免费套餐)
解决方案:升级套餐或优化查询(使用 mget 替代多次 get 调用,并添加缓存层)。
问题:值未过期
解决方案:使用 setex() 替代 set(),或在 set() 后调用 expire(key, ttl)。
问题:JSON 序列化错误
解决方案:确保值可序列化为 JSON(不包含函数、BigInt 或循环引用)。将 BigInt 转换为字符串。
完整设置清单
- 在 Vercel 控制台中创建 Vercel KV 数据库
- 在本地拉取环境变量(
vercel env pull) - 安装
@vercel/kv包 - 将
.env.local添加到.gitignore - 确立键命名约定(使用命名空间键)
- 为所有临时数据设置 TTL
- 设置速率限制监控
- 实现类型验证(Zod 模式)
- 处理返回 null 的情况
- 在本地和生产环境中进行测试
有疑问?遇到问题?
- 查看官方文档:https://vercel.com/docs/storage/vercel-kv
- 查看 Redis 命令:https://redis.io/commands
- 在 Vercel 控制台中监控使用情况
- 确保正确设置了环境变量