vercel-kv

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vercel 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
undefined
bash
undefined

Create 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 (
user:123
not
123
) and set TTL for temporary data.

vercel 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');
重要提示: 始终使用命名空间键(如
user:123
而非
123
),并为临时数据设置TTL。

Common 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-trip
Key Naming: Use namespaces like
user:123
,
post:abc:views
,
ratelimit: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:123
post:abc:views
ratelimit:ip:endpoint
的命名空间。

Critical Rules

重要规则

Always:
  • ✅ Set TTL for temporary data (
    setex
    not
    set
    )
  • ✅ Use namespaced keys (
    user:123
    not
    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:
Error: KV_REST_API_URL is not defined
or
KV_REST_API_TOKEN is not defined
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
@vercel/kv
into a shared package can cause Vercel builds to fail even though local builds work. Prevention: Run
vercel env pull .env.local
and ensure
.env.local
is in
.gitignore
. 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
turbo.json
pipeline config:
{ "pipeline": { "build": { "env": ["KV_REST_API_URL", "KV_REST_API_TOKEN"] } } }
.
错误:
Error: KV_REST_API_URL is not defined
KV_REST_API_TOKEN is not defined
来源: https://vercel.com/docs/storage/vercel-kv/quickstart | GitHub Issue #759 原因: 本地或部署环境中未设置环境变量。在单体仓库(Turborepo/pnpm工作区)中,将
@vercel/kv
抽象到共享包可能导致Vercel构建失败,即使本地构建正常。 预防措施: 运行
vercel env pull .env.local
并确保
.env.local
已加入
.gitignore
。对于单体仓库,可选择以下方案:(1) 在消费应用而非共享包中创建客户端;(2) 使用Vercel环境变量UI在项目级别设置;(3) 将环境变量添加到
turbo.json
的流水线配置:
{ "pipeline": { "build": { "env": ["KV_REST_API_URL", "KV_REST_API_TOKEN"] } } }

Issue #2: JSON Serialization Error

问题2:JSON序列化错误

Error:
TypeError: Do not know how to serialize a BigInt
or circular reference errors. Also,
hset()
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
hset()
to store string values that look numeric (e.g.,
'123456'
),
hgetall()
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
'code_123456'
, (2) store as JSON string and parse after retrieval, or (3) validate and recast types:
String(value.field)
after
hgetall()
.
错误:
TypeError: Do not know how to serialize a BigInt
或循环引用错误。此外,
hset()
会将数字字符串强制转换为数字。 来源: https://github.com/vercel/storage/issues/89 | GitHub Issue #727 原因: 尝试存储无法JSON序列化的数据(函数、BigInt、循环引用)。此外,使用
hset()
存储看似数字的字符串(如
'123456'
)时,
hgetall()
会将其返回为数字,破坏类型一致性。 预防措施: 仅存储普通对象、数组、字符串、数字、布尔值、null。将BigInt转换为字符串。对于包含数字字符串的哈希字段,可选择:(1) 使用非数字前缀如
'code_123456'
;(2) 存储为JSON字符串并在检索后解析;(3) 在
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
cache
,
data
,
temp
across different features Prevention: Always use namespaced keys:
feature:id:type
pattern.
错误: 返回意外数据,数据被其他功能覆盖 来源: 生产环境调试、最佳实践 原因: 在不同功能中使用通用键名如
cache
data
temp
预防措施: 始终使用命名空间键:采用
feature:id:type
模式。

Issue #4: TTL Not Set

问题4:未设置TTL

Error: Memory usage grows indefinitely, old data never expires Source: Vercel KV best practices Why It Happens: Using
set()
without
setex()
for temporary data Prevention: Use
setex(key, ttl, value)
for all temporary data. Set appropriate TTL (seconds).
错误: 内存使用量无限增长,旧数据永不过期 来源: Vercel KV最佳实践 原因: 对临时数据使用
set()
而非
setex()
预防措施: 对所有临时数据使用
setex(key, ttl, value)
。设置合适的TTL(秒为单位)。

Issue #5: Rate Limit Exceeded (Free Tier)

问题5:免费层超出速率限制

Error:
Error: Rate limit exceeded
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调用次数。

Issue #6: Storing Large Values

问题6:存储大值

Error:
Error: Value too large
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以下。

Issue #7: Type Mismatch on Get

问题7:Get操作类型不匹配

Error: TypeScript errors, runtime type errors. Generic
kv.get<T>()
sometimes returns
null
even when data exists. Source: Common TypeScript issue | GitHub Issue #510 Why It Happens:
kv.get()
returns
unknown
type, need to cast or validate. Additionally, there's a type inference bug where using generics like
kv.get<T>()
can cause the function to return
null
even when CLI shows data exists, due to serialization/deserialization issues. Prevention: Don't use generics with
get()
. Instead, retrieve without type parameter and cast after retrieval:
const rawData = await kv.get('key'); const data = rawData as MyType | null;
. Validate with Zod or type guards before using.
错误: TypeScript错误、运行时类型错误。泛型
kv.get<T>()
有时即使数据存在也返回
null
来源: 常见TypeScript问题 | GitHub Issue #510 原因:
kv.get()
返回
unknown
类型,需要强制转换或验证。此外,存在类型推断bug,使用泛型如
kv.get<T>()
时,即使CLI显示数据存在,函数也可能返回
null
,这是序列化/反序列化问题导致的。 预防措施: 不要在
get()
中使用泛型。应不带类型参数检索,然后强制转换:
const rawData = await kv.get('key'); const data = rawData as MyType | null;
。在使用前用Zod或类型守卫验证。

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
pipeline.exec()
and handle errors.
错误: 静默失败、部分执行 来源: 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
number
to
string
. Source: Redis best practices | Release Notes v3.0.0 Why It Happens: Using
scan()
with large datasets or wrong cursor handling. Version 3.0.0 introduced a breaking change where scan cursor is now
string
instead of
number
. Prevention: Limit
count
parameter, iterate properly with cursor, avoid full scans in production. In v3.0.0+, use
let cursor: string = "0"
and compare with
cursor !== "0"
(not
!== 0
).
错误: 查询缓慢、超时错误。在v3.0.0+中,游标类型从
number
改为
string
来源: Redis最佳实践 | Release Notes v3.0.0 原因: 对大型数据集使用
scan()
或游标处理错误。3.0.0版本引入破坏性变更,扫描游标现在是
string
而非
number
预防措施: 限制
count
参数,正确使用游标迭代,避免在生产环境中全量扫描。在v3.0.0+中,使用
let cursor: string = "0"
并与
cursor !== "0"
比较(而非
!== 0
)。

Issue #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
expire(key, newTTL)
on access to implement sliding windows.
错误: 会话过早过期、缓存提前失效 来源: 生产环境调试 原因: 访问时未刷新TTL(滑动过期) 预防措施: 在访问时使用
expire(key, newTTL)
实现滑动窗口。

Issue #11: scanIterator() Infinite Loop (v2.0.0+)

问题11:scanIterator()无限循环(v2.0.0+)

Error:
for await
loop never terminates when using
kv.scanIterator()
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
sscanIterator()
. Prevention: Use manual
scan()
with cursor instead of
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");
错误: 使用
kv.scanIterator()
for await
循环永不终止 来源: GitHub Issue #706 原因: v2.0.0+中的bug导致迭代器无法正确标记完成。迭代器能正确处理键,但永不退出,导致函数无法返回。该问题也影响
sscanIterator()
预防措施: 使用手动
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:
kv.zrange(key, 0, -1, { rev: true })
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
rev
flag returns data correctly. Prevention: Omit
rev
flag and reverse in-memory, or use
zrevrange()
instead.
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);
错误:
kv.zrange(key, 0, -1, { rev: true })
即使数据存在也返回空数组 来源: GitHub Issue #742 原因: SDK在处理某些键模式时的反向标志存在bug。CLI始终返回正确值。移除
rev
标志可正确返回数据。 预防措施: 省略
rev
标志并在内存中反转,或改用
zrevrange()
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:
kv.get()
returns
null
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
unstable_noStore()
, use
cache: 'no-store'
in fetch calls, or add retry logic.
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;
}
错误:
kv.get()
在启动开发服务器后的首次调用返回
null
,后续调用返回正确数据 来源: GitHub Issue #781 原因: Next.js静态渲染会缓存首次响应。在Next.js开发服务器的每次编译重启时都会发生。 预防措施: 使用
unstable_noStore()
强制动态渲染,在fetch调用中使用
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:
Uncaught ReferenceError: process is not defined
when importing
@vercel/kv
in Vite Source: GitHub Issue #743 Why It Happens: Vite doesn't polyfill Node.js
process.env
by default.
@vercel/kv
expects
process.env
to exist. Prevention: Use Vite's
define
to polyfill, use
createClient
with
import.meta.env
, or install
vite-plugin-node-polyfills
.
typescript
// 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,
});
错误: 导入
@vercel/kv
时出现
Uncaught ReferenceError: process is not defined
来源: GitHub Issue #743 原因: Vite默认不会填充Node.js的
process.env
@vercel/kv
期望
process.env
存在。 预防措施: 使用Vite的
define
进行填充,使用
createClient
结合
import.meta.env
,或安装
vite-plugin-node-polyfills
typescript
// 选项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:
kv.get()
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
unstable_noStore()
to force dynamic rendering, or use route handlers instead of Server Actions.
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.get()
返回过期/缓存数据,即使KV值已更新 来源: GitHub Issue #510 原因: Next.js静态渲染会缓存Server Actions的响应。控制台输出会显示黄色(缓存命中标识)。 预防措施: 使用
unstable_noStore()
强制动态渲染,或改用路由处理程序而非Server Actions。
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
    string
    instead of
    number
  • Update comparisons from
    cursor !== 0
    to
    cursor !== "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");
游标类型变更 (发布说明):
  • 扫描游标现在返回
    string
    而非
    number
  • 将比较逻辑从
    cursor !== 0
    更新为
    cursor !== "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
    enableAutoPipelining: false
    if needed
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
scanIterator()
. See Issue #11 for workaround.

自动管道默认启用 (发布说明):
  • 命令现在会自动批量处理以提升性能
  • 在某些边缘情况下可能导致时序问题
  • 如有需要,可通过
    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+中
scanIterator()
存在无限循环bug。请查看问题11的解决方案。

Known Limitations

已知限制

Redis Streams Not Supported

Redis流不支持

@vercel/kv
does not support Redis Streams (XREAD, XADD, XGROUP, etc.) even though underlying Upstash Redis supports them. The package lacks stream methods.
Workaround: Use
@upstash/redis
directly for streams:
typescript
// @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
不支持Redis流(XREAD、XADD、XGROUP等),即使底层Upstash Redis支持这些功能。该包缺少流相关方法。
解决方案: 直接使用
@upstash/redis
处理流:
typescript
// @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

哈希操作返回对象而非字符串

kv.hgetall()
returns a JavaScript object, not a JSON string. This confuses developers expecting string serialization.
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()
返回JavaScript对象,而非JSON字符串。这会让期望字符串序列化的开发者感到困惑。
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研究结果,新增版本迁移指南和已知限制章节