mcpField
V
vercel-kv

vercel-kv

github.com/jackspace数据与数据库1637 次安装12 周前更新
代码仓库

关于 vercel-kv

vercel-kv 技能介绍如何在应用中使用 Redis 兼容的 Vercel KV 存储,面向需要键值缓存和临时数据存取的开发者。内容涵盖基础操作、批处理及缓存、限流和会话等模式,并强调 TTL 与键命名规范。

主要功能

  1. 01键值数据读写
  2. 02原子计数与批量操作
  3. 03流水线执行多条命令
  4. 04缓存、限流和会话模式
  5. 05TTL 与键命名实践

适用场景

  1. 01缓存应用数据
  2. 02跟踪请求速率限制
  3. 03存储带过期时间的会话
  4. 04以原子操作更新计数器

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jackspace/claudeskillz --skill vercel-kv

安装后在 Claude Code 中输入/vercel-kv确认已加载。

附带文件README.mdSKILL.json

详细介绍

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