vercel-kv
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVercel KV
Vercel KV
Last Updated: 2026-01-21
Version: @vercel/kv@3.0.0 (Redis-compatible, powered by Upstash)
最后更新时间: 2026-01-21
版本: @vercel/kv@3.0.0(兼容Redis,由Upstash提供支持)
Quick Start
快速开始
bash
undefinedbash
undefinedCreate KV: Vercel Dashboard → Storage → KV
创建KV:Vercel控制台 → 存储 → KV
vercel env pull .env.local # Creates KV_REST_API_URL and KV_REST_API_TOKEN
npm install @vercel/kv
**Basic Usage**:
```typescript
import { kv } from '@vercel/kv';
// Set with TTL (expires in 1 hour)
await kv.setex('session:abc', 3600, { userId: 123 });
// Get
const session = await kv.get('session:abc');
// Increment counter (atomic)
const views = await kv.incr('views:post:123');CRITICAL: Always use namespaced keys ( not ) and set TTL for temporary data.
user:123123vercel env pull .env.local # 生成KV_REST_API_URL和KV_REST_API_TOKEN
npm install @vercel/kv
**基本用法**:
```typescript
import { kv } from '@vercel/kv';
// 设置带TTL的键(1小时后过期)
await kv.setex('session:abc', 3600, { userId: 123 });
// 获取值
const session = await kv.get('session:abc');
// 原子性递增计数器
const views = await kv.incr('views:post:123');重要提示: 始终使用命名空间键(如而非),并为临时数据设置TTL。
user:123123Common Patterns
常见模式
Caching (cache-aside):
typescript
const cached = await kv.get(`post:${slug}`);
if (cached) return cached;
const post = await db.query.posts.findFirst({ where: eq(posts.slug, slug) });
await kv.setex(`post:${slug}`, 3600, post); // Cache 1 hour
return post;Rate Limiting:
typescript
async function checkRateLimit(ip: string): Promise<boolean> {
const key = `ratelimit:${ip}`;
const current = await kv.incr(key);
if (current === 1) await kv.expire(key, 60); // 60s window
return current <= 10; // 10 requests per window
}Session Management:
typescript
const sessionId = crypto.randomUUID();
await kv.setex(`session:${sessionId}`, 7 * 24 * 3600, { userId });Pipeline (batch operations):
typescript
const pipeline = kv.pipeline();
pipeline.set('user:1', data);
pipeline.incr('counter');
const results = await pipeline.exec(); // Single round-tripKey Naming: Use namespaces like , ,
user:123post:abc:viewsratelimit:ip:endpoint缓存(旁路缓存模式):
typescript
const cached = await kv.get(`post:${slug}`);
if (cached) return cached;
const post = await db.query.posts.findFirst({ where: eq(posts.slug, slug) });
await kv.setex(`post:${slug}`, 3600, post); // 缓存1小时
return post;速率限制:
typescript
async function checkRateLimit(ip: string): Promise<boolean> {
const key = `ratelimit:${ip}`;
const current = await kv.incr(key);
if (current === 1) await kv.expire(key, 60); // 60秒窗口
return current <= 10; // 每个窗口最多10次请求
}会话管理:
typescript
const sessionId = crypto.randomUUID();
await kv.setex(`session:${sessionId}`, 7 * 24 * 3600, { userId });管道(批量操作):
typescript
const pipeline = kv.pipeline();
pipeline.set('user:1', data);
pipeline.incr('counter');
const results = await pipeline.exec(); // 单次往返请求键命名规范: 使用类似、、的命名空间。
user:123post:abc:viewsratelimit:ip:endpointCritical Rules
重要规则
Always:
- ✅ Set TTL for temporary data (not
setex)set - ✅ Use namespaced keys (not
user:123)123 - ✅ Handle null returns (non-existent keys)
- ✅ Use pipeline for batch operations
Never:
- ❌ Forget to set TTL (memory leak)
- ❌ Store large values >1MB (use Vercel Blob)
- ❌ Use KV as primary database (it's a cache)
- ❌ Store non-JSON-serializable data (functions, BigInt, circular refs)
必须遵守:
- ✅ 为临时数据设置TTL(使用而非
setex)set - ✅ 使用命名空间键(如而非
user:123)123 - ✅ 处理null返回值(键不存在的情况)
- ✅ 对批量操作使用管道
禁止操作:
- ❌ 不为临时数据设置TTL(会导致内存泄漏)
- ❌ 存储超过1MB的大值(请使用Vercel Blob)
- ❌ 将KV作为主数据库使用(它是缓存服务)
- ❌ 存储无法JSON序列化的数据(函数、BigInt、循环引用)
Known Issues Prevention
已知问题预防
This skill prevents 15 documented issues:
本指南可预防15个已记录的问题:
Issue #1: Missing Environment Variables
问题1:缺失环境变量
Error: or
Source: https://vercel.com/docs/storage/vercel-kv/quickstart | GitHub Issue #759
Why It Happens: Environment variables not set locally or in deployment. In monorepos (Turborepo/pnpm workspaces), abstracting into a shared package can cause Vercel builds to fail even though local builds work.
Prevention: Run and ensure is in . For monorepos, either (1) create client in consuming app not shared package, (2) use Vercel Environment Variables UI to set at project level, or (3) add env vars to pipeline config: .
Error: KV_REST_API_URL is not definedKV_REST_API_TOKEN is not defined@vercel/kvvercel env pull .env.local.env.local.gitignoreturbo.json{ "pipeline": { "build": { "env": ["KV_REST_API_URL", "KV_REST_API_TOKEN"] } } }错误: 或
来源: https://vercel.com/docs/storage/vercel-kv/quickstart | GitHub Issue #759
原因: 本地或部署环境中未设置环境变量。在单体仓库(Turborepo/pnpm工作区)中,将抽象到共享包可能导致Vercel构建失败,即使本地构建正常。
预防措施: 运行并确保已加入。对于单体仓库,可选择以下方案:(1) 在消费应用而非共享包中创建客户端;(2) 使用Vercel环境变量UI在项目级别设置;(3) 将环境变量添加到的流水线配置:。
Error: KV_REST_API_URL is not definedKV_REST_API_TOKEN is not defined@vercel/kvvercel env pull .env.local.env.local.gitignoreturbo.json{ "pipeline": { "build": { "env": ["KV_REST_API_URL", "KV_REST_API_TOKEN"] } } }Issue #2: JSON Serialization Error
问题2:JSON序列化错误
Error: or circular reference errors. Also, coerces numeric strings to numbers.
Source: https://github.com/vercel/storage/issues/89 | GitHub Issue #727
Why It Happens: Trying to store non-JSON-serializable data (functions, BigInt, circular refs). Additionally, when using to store string values that look numeric (e.g., ), returns them as numbers, breaking type consistency.
Prevention: Only store plain objects, arrays, strings, numbers, booleans, null. Convert BigInt to string. For hash fields with numeric strings, either (1) use non-numeric prefix like , (2) store as JSON string and parse after retrieval, or (3) validate and recast types: after .
TypeError: Do not know how to serialize a BigInthset()hset()'123456'hgetall()'code_123456'String(value.field)hgetall()错误: 或循环引用错误。此外,会将数字字符串强制转换为数字。
来源: https://github.com/vercel/storage/issues/89 | GitHub Issue #727
原因: 尝试存储无法JSON序列化的数据(函数、BigInt、循环引用)。此外,使用存储看似数字的字符串(如)时,会将其返回为数字,破坏类型一致性。
预防措施: 仅存储普通对象、数组、字符串、数字、布尔值、null。将BigInt转换为字符串。对于包含数字字符串的哈希字段,可选择:(1) 使用非数字前缀如;(2) 存储为JSON字符串并在检索后解析;(3) 在后验证并重新转换类型:。
TypeError: Do not know how to serialize a BigInthset()hset()'123456'hgetall()'code_123456'hgetall()String(value.field)Issue #3: Key Naming Collisions
问题3:键命名冲突
Error: Unexpected data returned, data overwritten by different feature
Source: Production debugging, best practices
Why It Happens: Using generic key names like , , across different features
Prevention: Always use namespaced keys: pattern.
cachedatatempfeature:id:type错误: 返回意外数据,数据被其他功能覆盖
来源: 生产环境调试、最佳实践
原因: 在不同功能中使用通用键名如、、
预防措施: 始终使用命名空间键:采用模式。
cachedatatempfeature:id:typeIssue #4: TTL Not Set
问题4:未设置TTL
Error: Memory usage grows indefinitely, old data never expires
Source: Vercel KV best practices
Why It Happens: Using without for temporary data
Prevention: Use for all temporary data. Set appropriate TTL (seconds).
set()setex()setex(key, ttl, value)错误: 内存使用量无限增长,旧数据永不过期
来源: Vercel KV最佳实践
原因: 对临时数据使用而非
预防措施: 对所有临时数据使用。设置合适的TTL(秒为单位)。
set()setex()setex(key, ttl, value)Issue #5: Rate Limit Exceeded (Free Tier)
问题5:免费层超出速率限制
Error: or commands failing
Source: https://vercel.com/docs/storage/vercel-kv/limits
Why It Happens: Exceeding 30,000 commands/month on free tier
Prevention: Monitor usage in Vercel dashboard, upgrade plan if needed, use caching to reduce KV calls.
Error: Rate limit exceeded错误: 或命令执行失败
来源: https://vercel.com/docs/storage/vercel-kv/limits
原因: 免费层每月请求数超过30,000次
预防措施: 在Vercel控制台监控使用情况,必要时升级套餐,使用缓存减少KV调用次数。
Error: Rate limit exceededIssue #6: Storing Large Values
问题6:存储大值
Error: or performance degradation
Source: https://vercel.com/docs/storage/vercel-kv/limits
Why It Happens: Trying to store values >1MB in KV
Prevention: Use Vercel Blob for files/images. Keep KV values small (<100KB recommended).
Error: Value too large错误: 或性能下降
来源: https://vercel.com/docs/storage/vercel-kv/limits
原因: 尝试在KV中存储超过1MB的值
预防措施: 使用Vercel Blob存储文件/图片。KV值建议保持在100KB以下。
Error: Value too largeIssue #7: Type Mismatch on Get
问题7:Get操作类型不匹配
Error: TypeScript errors, runtime type errors. Generic sometimes returns even when data exists.
Source: Common TypeScript issue | GitHub Issue #510
Why It Happens: returns type, need to cast or validate. Additionally, there's a type inference bug where using generics like can cause the function to return even when CLI shows data exists, due to serialization/deserialization issues.
Prevention: Don't use generics with . Instead, retrieve without type parameter and cast after retrieval: . Validate with Zod or type guards before using.
kv.get<T>()nullkv.get()unknownkv.get<T>()nullget()const rawData = await kv.get('key'); const data = rawData as MyType | null;错误: TypeScript错误、运行时类型错误。泛型有时即使数据存在也返回。
来源: 常见TypeScript问题 | GitHub Issue #510
原因: 返回类型,需要强制转换或验证。此外,存在类型推断bug,使用泛型如时,即使CLI显示数据存在,函数也可能返回,这是序列化/反序列化问题导致的。
预防措施: 不要在中使用泛型。应不带类型参数检索,然后强制转换:。在使用前用Zod或类型守卫验证。
kv.get<T>()nullkv.get()unknownkv.get<T>()nullget()const rawData = await kv.get('key'); const data = rawData as MyType | null;Issue #8: Pipeline Errors Not Handled
问题8:管道错误未处理
Error: Silent failures, partial execution
Source: https://github.com/vercel/storage/issues/120
Why It Happens: Pipeline execution can have individual command failures
Prevention: Check results array from and handle errors.
pipeline.exec()错误: 静默失败、部分执行
来源: https://github.com/vercel/storage/issues/120
原因: 管道执行时个别命令可能失败
预防措施: 检查返回的结果数组并处理错误。
pipeline.exec()Issue #9: Scan Operation Inefficiency
问题9:扫描操作效率低下
Error: Slow queries, timeout errors. In v3.0.0+, cursor type changed from to .
Source: Redis best practices | Release Notes v3.0.0
Why It Happens: Using with large datasets or wrong cursor handling. Version 3.0.0 introduced a breaking change where scan cursor is now instead of .
Prevention: Limit parameter, iterate properly with cursor, avoid full scans in production. In v3.0.0+, use and compare with (not ).
numberstringscan()stringnumbercountlet cursor: string = "0"cursor !== "0"!== 0错误: 查询缓慢、超时错误。在v3.0.0+中,游标类型从改为。
来源: Redis最佳实践 | Release Notes v3.0.0
原因: 对大型数据集使用或游标处理错误。3.0.0版本引入破坏性变更,扫描游标现在是而非。
预防措施: 限制参数,正确使用游标迭代,避免在生产环境中全量扫描。在v3.0.0+中,使用并与比较(而非)。
numberstringscan()stringnumbercountlet cursor: string = "0"cursor !== "0"!== 0Issue #10: Missing TTL Refresh
问题10:未刷新TTL
Error: Session expires too early, cache invalidates prematurely
Source: Production debugging
Why It Happens: Not refreshing TTL on access (sliding expiration)
Prevention: Use on access to implement sliding windows.
expire(key, newTTL)错误: 会话过早过期、缓存提前失效
来源: 生产环境调试
原因: 访问时未刷新TTL(滑动过期)
预防措施: 在访问时使用实现滑动窗口。
expire(key, newTTL)Issue #11: scanIterator() Infinite Loop (v2.0.0+)
问题11:scanIterator()无限循环(v2.0.0+)
Error: loop never terminates when using
Source: GitHub Issue #706
Why It Happens: Bug in v2.0.0+ where iterator doesn't properly signal completion. The iterator processes keys correctly but never exits, preventing the function from returning. Also affects .
Prevention: Use manual with cursor instead of .
for awaitkv.scanIterator()sscanIterator()scan()scanIterator()typescript
// Don't use scanIterator() - it hangs in v2.0.0+
for await (const key of kv.scanIterator()) {
// This loop never terminates
}
// Use manual scan with cursor instead
let cursor: string = "0"; // v3.x uses string
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
for (const key of keys) {
const value = await kv.get(key);
// process key/value
}
} while (cursor !== "0");错误: 使用时循环永不终止
来源: GitHub Issue #706
原因: v2.0.0+中的bug导致迭代器无法正确标记完成。迭代器能正确处理键,但永不退出,导致函数无法返回。该问题也影响。
预防措施: 使用手动加游标替代。
kv.scanIterator()for awaitsscanIterator()scan()scanIterator()typescript
// 不要使用scanIterator() - v2.0.0+中会挂起
for await (const key of kv.scanIterator()) {
// 此循环永不终止
}
// 改用手动扫描加游标
let cursor: string = "0"; // v3.x使用string类型
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
for (const key of keys) {
const value = await kv.get(key);
// 处理键/值
}
} while (cursor !== "0");Issue #12: zrange() with rev: true Returns Empty Array
问题12:zrange()带rev: true返回空数组
Error: returns empty array even though data exists
Source: GitHub Issue #742
Why It Happens: SDK bug in reverse flag handling for certain key patterns. CLI always returns correct values. Removing the flag returns data correctly.
Prevention: Omit flag and reverse in-memory, or use instead.
kv.zrange(key, 0, -1, { rev: true })revrevzrevrange()typescript
// This sometimes returns empty array (BUG)
const chats = await kv.zrange(`user:chat:${userId}`, 0, -1, { rev: true });
// [] - but CLI shows 12 items
// Workaround 1: Omit rev flag and reverse in-memory
const chats = await kv.zrange(`user:chat:${userId}`, 0, -1);
const reversedChats = chats.reverse();
// Workaround 2: Use zrevrange instead
const chats = await kv.zrevrange(`user:chat:${userId}`, 0, -1);错误: 即使数据存在也返回空数组
来源: GitHub Issue #742
原因: SDK在处理某些键模式时的反向标志存在bug。CLI始终返回正确值。移除标志可正确返回数据。
预防措施: 省略标志并在内存中反转,或改用。
kv.zrange(key, 0, -1, { rev: true })revrevzrevrange()typescript
// 此代码有时返回空数组(BUG)
const chats = await kv.zrange(`user:chat:${userId}`, 0, -1, { rev: true });
// [] - 但CLI显示有12条数据
// 解决方案1:省略rev标志并在内存中反转
const chats = await kv.zrange(`user:chat:${userId}`, 0, -1);
const reversedChats = chats.reverse();
// 解决方案2:改用zrevrange
const chats = await kv.zrevrange(`user:chat:${userId}`, 0, -1);Issue #13: Next.js Dev Server Returns Null on First Call
问题13:Next.js开发服务器首次调用返回Null
Error: returns on first call after starting dev server, correct data on subsequent calls
Source: GitHub Issue #781
Why It Happens: Next.js static rendering caches the first response. Happens on each compilation restart in Next.js dev server.
Prevention: Force dynamic rendering with , use in fetch calls, or add retry logic.
kv.get()nullunstable_noStore()cache: 'no-store'typescript
import { unstable_noStore as noStore } from 'next/cache';
export async function getData() {
noStore(); // Force dynamic rendering
const data = await kv.get('mykey');
return data; // Now returns correct value on first call
}
// Or add retry logic
async function getWithRetry(key: string, retries = 2) {
let data = await kv.get(key);
let attempt = 0;
while (!data && attempt < retries) {
await new Promise(r => setTimeout(r, 100));
data = await kv.get(key);
attempt++;
}
return data;
}错误: 在启动开发服务器后的首次调用返回,后续调用返回正确数据
来源: GitHub Issue #781
原因: Next.js静态渲染会缓存首次响应。在Next.js开发服务器的每次编译重启时都会发生。
预防措施: 使用强制动态渲染,在fetch调用中使用,或添加重试逻辑。
kv.get()nullunstable_noStore()cache: 'no-store'typescript
import { unstable_noStore as noStore } from 'next/cache';
export async function getData() {
noStore(); // 强制动态渲染
const data = await kv.get('mykey');
return data; // 现在首次调用会返回正确值
}
// 或添加重试逻辑
async function getWithRetry(key: string, retries = 2) {
let data = await kv.get(key);
let attempt = 0;
while (!data && attempt < retries) {
await new Promise(r => setTimeout(r, 100));
data = await kv.get(key);
attempt++;
}
return data;
}Issue #14: Vite "process is not defined" Error
问题14:Vite "process is not defined"错误
Error: when importing in Vite
Source: GitHub Issue #743
Why It Happens: Vite doesn't polyfill Node.js by default. expects to exist.
Prevention: Use Vite's to polyfill, use with , or install .
Uncaught ReferenceError: process is not defined@vercel/kvprocess.env@vercel/kvprocess.envdefinecreateClientimport.meta.envvite-plugin-node-polyfillstypescript
// Option 1: Vite config with define
// vite.config.ts
import { defineConfig, loadEnv } from 'vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
define: {
'process.env.KV_REST_API_URL': JSON.stringify(env.KV_REST_API_URL),
'process.env.KV_REST_API_TOKEN': JSON.stringify(env.KV_REST_API_TOKEN),
},
};
});
// Option 2: Use createClient with import.meta.env
import { createClient } from '@vercel/kv';
const kv = createClient({
url: import.meta.env.VITE_KV_REST_API_URL,
token: import.meta.env.VITE_KV_REST_API_TOKEN,
});错误: 导入时出现
来源: GitHub Issue #743
原因: Vite默认不会填充Node.js的。期望存在。
预防措施: 使用Vite的进行填充,使用结合,或安装。
@vercel/kvUncaught ReferenceError: process is not definedprocess.env@vercel/kvprocess.envdefinecreateClientimport.meta.envvite-plugin-node-polyfillstypescript
// 选项1:Vite配置中使用define
// vite.config.ts
import { defineConfig, loadEnv } from 'vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
define: {
'process.env.KV_REST_API_URL': JSON.stringify(env.KV_REST_API_URL),
'process.env.KV_REST_API_TOKEN': JSON.stringify(env.KV_REST_API_TOKEN),
},
};
});
// 选项2:使用createClient结合import.meta.env
import { createClient } from '@vercel/kv';
const kv = createClient({
url: import.meta.env.VITE_KV_REST_API_URL,
token: import.meta.env.VITE_KV_REST_API_TOKEN,
});Issue #15: Next.js Server Actions Return Stale Data
问题15:Next.js Server Actions返回过期数据
Error: in Server Actions returns stale/cached data even after KV values are updated
Source: GitHub Issue #510
Why It Happens: Next.js static rendering caches Server Action responses. Console output appears yellow (cache hit indicator).
Prevention: Use to force dynamic rendering, or use route handlers instead of Server Actions.
kv.get()unstable_noStore()typescript
// In Server Actions
'use server'
import { unstable_noStore as noStore } from 'next/cache';
export async function logChat(text: string) {
noStore(); // Force dynamic rendering
let n_usage = await kv.get('n_usage');
// Now returns fresh value, not cached
}
// Or use route handlers (automatically dynamic)
// app/api/chat/route.ts
export async function GET() {
let n_usage = await kv.get('n_usage'); // Fresh data
return Response.json({ n_usage });
}错误: Server Actions中的返回过期/缓存数据,即使KV值已更新
来源: GitHub Issue #510
原因: Next.js静态渲染会缓存Server Actions的响应。控制台输出会显示黄色(缓存命中标识)。
预防措施: 使用强制动态渲染,或改用路由处理程序而非Server Actions。
kv.get()unstable_noStore()typescript
// 在Server Actions中
'use server'
import { unstable_noStore as noStore } from 'next/cache';
export async function logChat(text: string) {
noStore(); // 强制动态渲染
let n_usage = await kv.get('n_usage');
// 现在返回最新值,而非缓存值
}
// 或使用路由处理程序(自动动态)
// app/api/chat/route.ts
export async function GET() {
let n_usage = await kv.get('n_usage'); // 最新数据
return Response.json({ n_usage });
}Version Migration Guide
版本迁移指南
v3.0.0 Breaking Changes
v3.0.0破坏性变更
Cursor Type Changed (Release Notes):
- Scan cursor now returns instead of
stringnumber - Update comparisons from to
cursor !== 0cursor !== "0"
typescript
// v2.x
let cursor: number = 0;
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
} while (cursor !== 0);
// v3.x
let cursor: string = "0";
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
} while (cursor !== "0");游标类型变更 (发布说明):
- 扫描游标现在返回而非
stringnumber - 将比较逻辑从更新为
cursor !== 0cursor !== "0"
typescript
// v2.x
let cursor: number = 0;
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
} while (cursor !== 0);
// v3.x
let cursor: string = "0";
do {
const [newCursor, keys] = await kv.scan(cursor);
cursor = newCursor;
} while (cursor !== "0");v2.0.0 Breaking Changes
v2.0.0破坏性变更
Auto-Pipelining Enabled by Default (Release Notes):
- Commands now automatically batched for performance
- May cause timing issues in edge cases
- Disable with if needed
enableAutoPipelining: false
typescript
// If auto-pipelining causes issues, disable it:
import { createClient } from '@vercel/kv';
const kv = createClient({
url: process.env.KV_REST_API_URL,
token: process.env.KV_REST_API_TOKEN,
enableAutoPipelining: false // Disable auto-pipelining
});scanIterator() Bug Introduced: v2.0.0+ has infinite loop bug with . See Issue #11 for workaround.
scanIterator()自动管道默认启用 (发布说明):
- 命令现在会自动批量处理以提升性能
- 在某些边缘情况下可能导致时序问题
- 如有需要,可通过禁用
enableAutoPipelining: false
typescript
// 如果自动管道导致问题,可禁用:
import { createClient } from '@vercel/kv';
const kv = createClient({
url: process.env.KV_REST_API_URL,
token: process.env.KV_REST_API_TOKEN,
enableAutoPipelining: false // 禁用自动管道
});scanIterator()引入Bug: v2.0.0+中存在无限循环bug。请查看问题11的解决方案。
scanIterator()Known Limitations
已知限制
Redis Streams Not Supported
Redis流不支持
@vercel/kvWorkaround: Use directly for streams:
@upstash/redistypescript
// @vercel/kv doesn't have stream methods
// await kv.xAdd(...) // TypeError: kv.xAdd is not a function
// Use Upstash Redis client directly
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.KV_REST_API_URL!,
token: process.env.KV_REST_API_TOKEN!,
});
await redis.xadd('stream:events', '*', { event: 'user.login' });
const messages = await redis.xread('stream:events', '0');@vercel/kv解决方案: 直接使用处理流:
@upstash/redistypescript
// @vercel/kv没有流方法
// await kv.xAdd(...) // TypeError: kv.xAdd is not a function
// 直接使用Upstash Redis客户端
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.KV_REST_API_URL!,
token: process.env.KV_REST_API_TOKEN!,
});
await redis.xadd('stream:events', '*', { event: 'user.login' });
const messages = await redis.xread('stream:events', '0');Hash Operations Return Objects Not Strings
哈希操作返回对象而非字符串
Source: GitHub Issue #674
kv.hgetall()typescript
await kv.hset('foobar', { '1834': 'https://example.com' });
const data = await kv.hgetall('foobar');
console.log(typeof data); // "object" not "string"
// It's already an object - use directly
console.log(data['1834']); // 'https://example.com'
// If you need JSON string
const jsonString = JSON.stringify(data);kv.hgetall()typescript
await kv.hset('foobar', { '1834': 'https://example.com' });
const data = await kv.hgetall('foobar');
console.log(typeof data); // "object"而非"string"
// 它已经是对象 - 直接使用
console.log(data['1834']); // 'https://example.com'
// 如果需要JSON字符串
const jsonString = JSON.stringify(data);Advanced Patterns
高级模式
Distributed Lock (prevents race conditions):
typescript
const lockKey = `lock:${resource}`;
const lockValue = crypto.randomUUID();
const acquired = await kv.setnx(lockKey, lockValue);
if (acquired) {
await kv.expire(lockKey, 10); // TTL prevents deadlock
try {
await processOrders();
} finally {
const current = await kv.get(lockKey);
if (current === lockValue) await kv.del(lockKey);
}
}Leaderboard (sorted sets):
typescript
await kv.zadd('leaderboard', { score, member: userId.toString() });
// Note: zrange with { rev: true } has a known bug (Issue #12)
// Use zrevrange instead for reliability
const top = await kv.zrevrange('leaderboard', 0, 9, { withScores: true });
const rank = await kv.zrevrank('leaderboard', userId.toString());Last verified: 2026-01-21 | Skill version: 2.0.0 | Changes: Added 5 new issues (scanIterator hang, zrange rev bug, Next.js dev server null, Vite process error, Server Actions stale cache), expanded Issues #1, #2, #7, #9 with TIER 1-2 research findings, added Version Migration Guide and Known Limitations sections
分布式锁(防止竞态条件):
typescript
const lockKey = `lock:${resource}`;
const lockValue = crypto.randomUUID();
const acquired = await kv.setnx(lockKey, lockValue);
if (acquired) {
await kv.expire(lockKey, 10); // TTL防止死锁
try {
await processOrders();
} finally {
const current = await kv.get(lockKey);
if (current === lockValue) await kv.del(lockKey);
}
}排行榜(有序集合):
typescript
await kv.zadd('leaderboard', { score, member: userId.toString() });
// 注意:带{ rev: true }的zrange存在已知bug(问题12)
// 为了可靠性,改用zrevrange
const top = await kv.zrevrange('leaderboard', 0, 9, { withScores: true });
const rank = await kv.zrevrank('leaderboard', userId.toString());最后验证时间: 2026-01-21 | 技能版本: 2.0.0 | 变更: 新增5个问题(scanIterator挂起、zrange rev bug、Next.js开发服务器返回null、Vite process错误、Server Actions缓存过期),补充问题#1、#2、#7、#9的TIER 1-2研究结果,新增版本迁移指南和已知限制章节