vercel-kv
Integrate Redis-compatible Vercel KV for caching, session management, and rate limiting in Next.js applications. Powered by Upstash with strong consistency and TTL support.Use when implementing cache strategies, storing temporary data with expiration, building rate limiters, or troubleshooting missing environment variables, serialization errors, or rate limit issues.
When & Why to Use This Skill
This Claude skill facilitates the seamless integration of Redis-compatible Vercel KV storage into Next.js applications. It provides expert guidance on implementing high-performance caching, secure session management, and robust rate limiting. By leveraging Upstash-powered infrastructure, the skill ensures developers follow best practices for data consistency, TTL (Time-To-Live) management, and efficient batch processing to build scalable and resilient web applications.
Use Cases
- Implementing cache-aside patterns to significantly reduce database load and improve response times for Next.js applications.
- Building scalable API rate limiters using atomic increments to protect infrastructure from abuse and resource exhaustion.
- Managing secure user sessions with sliding expiration windows to balance user experience and security.
- Troubleshooting and preventing common production errors such as JSON serialization failures, missing environment variables, and memory leaks caused by missing TTLs.
- Optimizing complex data workflows using Redis pipelines to execute multiple commands in a single network round-trip, reducing latency.
| name | vercel-kv |
|---|---|
| description | | |
Vercel KV
Last Updated: 2025-11-28 Version: @vercel/kv@3.0.0 (Redis-compatible, powered by Upstash)
Quick Start
# Create KV: Vercel Dashboard → Storage → KV
vercel env pull .env.local # Creates KV_REST_API_URL and KV_REST_API_TOKEN
npm install @vercel/kv
Basic Usage:
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.
Common Patterns
Caching (cache-aside):
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:
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:
const sessionId = crypto.randomUUID();
await kv.setex(`session:${sessionId}`, 7 * 24 * 3600, { userId });
Pipeline (batch operations):
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
Critical Rules
Always:
- ✅ Set TTL for temporary data (
setexnotset) - ✅ Use namespaced keys (
user:123not123) - ✅ 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)
Known Issues Prevention
This skill prevents 10 documented issues:
Issue #1: Missing Environment Variables
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
Why It Happens: Environment variables not set locally or in deployment
Prevention: Run vercel env pull .env.local and ensure .env.local is in .gitignore.
Issue #2: JSON Serialization Error
Error: TypeError: Do not know how to serialize a BigInt or circular reference errors
Source: https://github.com/vercel/storage/issues/89
Why It Happens: Trying to store non-JSON-serializable data (functions, BigInt, circular refs)
Prevention: Only store plain objects, arrays, strings, numbers, booleans, null. Convert BigInt to string.
Issue #3: Key Naming Collisions
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.
Issue #4: TTL Not Set
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).
Issue #5: Rate Limit Exceeded (Free Tier)
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.
Issue #6: Storing Large Values
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).
Issue #7: Type Mismatch on Get
Error: TypeScript errors, runtime type errors
Source: Common TypeScript issue
Why It Happens: kv.get() returns unknown type, need to cast or validate
Prevention: Use type assertion with validation: const user = await kv.get<User>('user:123') and validate with Zod.
Issue #8: Pipeline Errors Not Handled
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.
Issue #9: Scan Operation Inefficiency
Error: Slow queries, timeout errors
Source: Redis best practices
Why It Happens: Using scan() with large datasets or wrong cursor handling
Prevention: Limit count parameter, iterate properly with cursor, avoid full scans in production.
Issue #10: Missing TTL Refresh
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.
Advanced Patterns
Distributed Lock (prevents race conditions):
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):
await kv.zadd('leaderboard', { score, member: userId.toString() });
const top = await kv.zrange('leaderboard', 0, 9, { rev: true, withScores: true });
const rank = await kv.zrevrank('leaderboard', userId.toString());