mcpField
V
vercel-kv

vercel-kv

github.com/ovachiever数据与数据库5647 次安装48 周前更新
代码仓库

关于 vercel-kv

vercel-kv 是面向使用 Vercel KV 的开发者的技能,该存储兼容 Redis,可通过 @vercel/kv 访问。内容涵盖配置,以及缓存、计数器、会话、限流和批量操作模式。

主要功能

  1. 01配置 Vercel KV 凭据
  2. 02读写兼容 JSON 的值
  3. 03使用兼容 Redis 的原子操作
  4. 04设置 TTL 和命名空间键
  5. 05通过管道批量执行命令
  6. 06采用缓存与会话模式

适用场景

  1. 01缓存应用数据
  2. 02跟踪计数器和请求限流
  3. 03存储临时会话数据
  4. 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.json
assets/
example-template.txt
references/
example-reference.md
scripts/
example-script.sh
templates/
package.json
session-management.ts
simple-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 数据库 URL
  • KV_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 控制台

  1. 前往你的 Vercel 项目
  2. Storage → Create Database → KV
  3. 为数据库命名
  4. 复制环境变量

选项 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 模拟库

官方文档


软件包版本(已于 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 的情况
  • 在本地和生产环境中进行测试

有疑问?遇到问题?

  1. 查看官方文档:https://vercel.com/docs/storage/vercel-kv
  2. 查看 Redis 命令:https://redis.io/commands
  3. 在 Vercel 控制台中监控使用情况
  4. 确保正确设置了环境变量

评论

支持 Markdown