vercel-kv
This skill provides comprehensive knowledge for integrating Vercel KV (Redis-compatible key-value storage powered by Upstash) into Vercel applications. It should be used when setting up Vercel KV for Next.js applications, implementing caching patterns, managing sessions, or handling rate limiting in edge and serverless functions. Use this skill when: - Setting up Vercel KV for Next.js applications - Implementing caching strategies (page cache, API cache, data cache) - Managing user sessions or authentication tokens in serverless environments - Building rate limiting for APIs or features - Storing temporary data with TTL (time-to-live) - Migrating from Cloudflare KV to Vercel KV - Encountering errors like "KV_REST_API_URL not set", "rate limit exceeded", or "JSON serialization errors" - Need Redis-compatible API with strong consistency (vs eventual consistency) Keywords: vercel kv, @vercel/kv, vercel redis, upstash vercel, kv vercel, redis vercel edge, key-value vercel, vercel cache, vercel sessions, vercel rate limit, redis upstash, kv storage, edge kv, serverless redis, vercel ttl, vercel expire, kv typescript, next.js kv, server actions kv, edge runtime kv
NPX Install
npx skill4agent add jackspace/claudeskillz vercel-kvTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Vercel KV (Redis-Compatible Storage)
@vercel/kv@3.0.0Quick Start (3 Minutes)
1. Create Vercel KV Database
# In your Vercel project dashboard
# Storage → Create Database → KV
# Pull environment variables locally
vercel env pull .env.local- - Your KV database URL
KV_REST_API_URL - - Auth token
KV_REST_API_TOKEN - - Read-only token (optional)
KV_REST_API_READ_ONLY_TOKEN
2. Install Package
npm install @vercel/kv3. Use in Your App
'use server';
import { kv } from '@vercel/kv';
export async function incrementViews(slug: string) {
const views = await kv.incr(`views:${slug}`);
return views;
}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 });
}- Always set TTL for temporary data:
await kv.setex('key', 3600, value) - Use namespacing for keys: instead of just
user:${id}:profile${id} - JSON values must be serializable (no functions, circular refs)
The 5-Step Setup Process
Step 1: Create KV Database
- Go to your Vercel project
- Storage → Create Database → KV
- Name your database
- Copy the environment variables
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"- One KV database per project recommended
- Free tier: 30,000 commands/month, 256MB storage
- Environment variables are automatically set for Vercel deployments
Step 2: Install and Configure
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"# wrangler.toml
[vars]
KV_REST_API_URL = "https://your-db.kv.vercel-storage.com"
[[secrets]]
KV_REST_API_TOKEN = "your-token"Step 3: Basic Operations
import { kv } from '@vercel/kv';
// Set a value
await kv.set('user:123', { name: 'Alice', email: 'alice@example.com' });
// Get a value
const user = await kv.get('user:123');
// Returns: { name: 'Alice', email: 'alice@example.com' }
// 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');- Values are automatically JSON-serialized
- is returned for non-existent keys
null - All operations are atomic
- TTL is in seconds
Step 4: Advanced Patterns
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}`);
}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, { ... }]Step 5: Key Naming Conventions
// ❌ 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 profile data
user:{id}:profile - - View counter for post
post:{slug}:views - - Cached page content
cache:{page}:{locale} - - Session data
session:{token} - - Rate limit tracking
ratelimit:{ip}:{endpoint} - - Distributed locks
lock:{resource}
Critical Rules
Always Do
user:123123nullNever Do
datacachetemp.env.local.gitignoreKnown Issues Prevention
Issue #1: Missing Environment Variables
Error: KV_REST_API_URL is not definedKV_REST_API_TOKEN is not definedvercel env pull .env.local.env.local.gitignoreIssue #2: JSON Serialization Error
TypeError: Do not know how to serialize a BigIntIssue #3: Key Naming Collisions
cachedatatempfeature:id:typeIssue #4: TTL Not Set
set()setex()setex(key, ttl, value)Issue #5: Rate Limit Exceeded (Free Tier)
Error: Rate limit exceededIssue #6: Storing Large Values
Error: Value too largeIssue #7: Type Mismatch on Get
kv.get()unknownconst user = await kv.get<User>('user:123')Issue #8: Pipeline Errors Not Handled
pipeline.exec()Issue #9: Scan Operation Inefficiency
scan()countIssue #10: Missing TTL Refresh
expire(key, newTTL)Configuration Files Reference
package.json
{
"dependencies": {
"@vercel/kv": "^3.0.0"
}
}.env.local (Local Development)
# 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*.localCommon Patterns
Pattern 1: Cache-Aside (Lazy Loading)
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;
}Pattern 2: Write-Through Cache
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];
}Pattern 3: Distributed Lock
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);
}
}Pattern 4: Leaderboard
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;
}Dependencies
- - Vercel KV client library
@vercel/kv@^3.0.0
- - Runtime type validation for KV data
zod@^3.24.0 - - Mock KV for testing
ioredis-mock@^8.9.0
Official Documentation
- Vercel KV: https://vercel.com/docs/storage/vercel-kv
- Vercel KV Quickstart: https://vercel.com/docs/storage/vercel-kv/quickstart
- Vercel KV SDK Reference: https://vercel.com/docs/storage/vercel-kv/kv-reference
- GitHub: https://github.com/vercel/storage
- Redis Commands: https://redis.io/commands (Vercel KV is Redis-compatible)
Package Versions (Verified 2025-10-29)
{
"dependencies": {
"@vercel/kv": "^3.0.0"
}
}Production Example
- Next.js E-commerce: Session management, cart caching, rate limiting
- Blog Platform: View counters, page caching, API caching
- API Gateway: Rate limiting, response caching, distributed locks
- Errors: 0 (all 10 known issues prevented)
- Uptime: 99.9%+ (Upstash SLA)
Troubleshooting
Problem: KV_REST_API_URL is not defined
KV_REST_API_URL is not definedvercel env pull .env.localProblem: Rate limit exceeded (free tier)
mgetgetProblem: Values not expiring
setex()set()expire(key, ttl)set()Problem: JSON serialization error
Complete Setup Checklist
- Vercel KV database created in dashboard
- Environment variables pulled locally ()
vercel env pull - package installed
@vercel/kv - added to
.env.local.gitignore - Key naming convention established (namespaced keys)
- TTL set for all temporary data
- Rate limit monitoring set up
- Type validation implemented (Zod schemas)
- Error handling for null returns
- Tested locally and in production
- Check official docs: https://vercel.com/docs/storage/vercel-kv
- Review Redis commands: https://redis.io/commands
- Monitor usage in Vercel dashboard
- Ensure environment variables are set correctly