EdgeCases Logo
Mar 2026
architecture
Surface
6 min read

Vercel Edge Config vs KV vs Blob

Vercel's three storage options serve different needs—feature flags, sessions, and files. Here's when to use each.

vercel
edge-config
redis
kv
blob
storage
infrastructure
architecture

Vercel offers three storage options, each optimized for different use cases. Pick the wrong one and you're either overpaying for simple feature flags or hitting rate limits on user uploads. Here's the decision matrix for infrastructure and architecture decisions.

Edge Config: Low-Latency Global Reads

Edge Config is a distributed key-value store designed for frequent reads and infrequent writes. Think feature flags, A/B testing configurations, redirects—data that stays relatively static but needs to be accessible globally with minimal latency.

// Feature flag in Edge Config
import { get } from '@vercel/edge-config';

export async function middleware(request: NextRequest) {
  const config = await get();
  const betaEnabled = config?.features?.beta ?? false;

  if (!betaEnabled && request.nextUrl.pathname.startsWith('/beta')) {
    return NextResponse.redirect(new URL('/', request.url));
  }
}
  • Read latency: <15ms at P99, often <1ms
  • Works in: Middleware and Edge Functions only
  • Write latency: Varies (not real-time)
  • No redeploy needed: Update via API or Dashboard

The killer feature: reads don't block. Config is pushed to edge locations proactively, so every edge location has local access. Use Edge Config when you need millisecond-level read performance and can tolerate eventual consistency on writes.

Redis (formerly KV): Fast Key-Value Storage

Vercel KV has been migrated to Upstash Redis via the Marketplace. Use Redis for ephemeral data, caching layers, rate limiting, and anything that needs fast reads with immediate write consistency.

// Rate limiting with Redis
import { Redis } from '@upstash/redis';

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL,
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
});

export async function rateLimit(ip: string) {
  const key = `rate-limit:${ip}`;
  const count = await redis.incr(key);

  if (count === 1) {
    await redis.expire(key, 60); // 1 minute window
  }

  if (count > 10) {
    throw new Error('Rate limit exceeded');
  }
}

Redis shines for per-user or per-request data that doesn't make sense in Edge Config:

  • Session data: Temporary tokens, active sessions
  • Counters: Rate limiting, analytics, usage tracking
  • Caching: Database query results, expensive computations
  • Pub/Sub: Real-time notifications, WebSocket alternatives

Unlike Edge Config, Redis writes are immediate. You get read-after-write consistency, making it suitable for data that updates frequently and must reflect changes instantly.

Blob: Object Storage for Files

Blob is for files—images, videos, PDFs, any binary data. Think S3 replacement with Vercel-native integration. Upload at build time or runtime, serve through CDN, pay based on storage averages and data transfer.

// Server-side upload
import { put } from '@vercel/blob';

const blob = await put('avatars/user-123.jpg', file, {
  access: 'public', // or 'private' for authenticated reads
});

// Direct client-side upload (no Function bandwidth)
import { upload } from '@vercel/blob/client';

const { url } = await upload('profile.png', file, {
  access: 'public',
  handleUploadUrl: '/api/upload/auth',
});

Blob isn't for key-value data—it's for actual files stored as objects. The distinction matters:

  • Public blobs: Anyone with URL can access (CDN-delivered)
  • Private blobs: Authenticated reads via your Function
  • Client uploads: Direct browser-to-Blob, bypassing your Function
  • CDN caching: Up to 1 month by default

Use Blob for user uploads, static assets that aren't part of your git repo, media files, document storage—anything where you'd traditionally use S3.

The Comparison Table

FeatureEdge ConfigRedisBlob
Use caseFeature flags, A/B tests, redirectsCaching, sessions, rate limitingFile storage (images, videos)
Read latency<15ms P99 (edge-local)~10-50ms (depends on region)CDN-cached (similar to static)
Write latencyVaries (eventual consistency)Immediate~100-500ms upload
Works inMiddleware, Edge FunctionsAll runtimesAll runtimes, client-side
Data typeJSON-like (up to 64KB/item)Strings, numbers, JSONBinary files (any size)
BillingPer read/write operationPer operation + storageStorage + data transfer

Common Mistake: Using Blob for Config

// ❌ Wrong: Blob is for files, not config
const config = await fetch('https://blob.vercel-storage.com/config.json')
  .then(r => r.json());

// ✅ Right: Edge Config is for config
const config = await get();

// ✅ Right: Redis for dynamic config that needs fast writes
const config = await redis.hgetall('app-config');

Fetching a JSON file from Blob on every request adds latency and data transfer costs. Edge Config is purpose-built for this—use it.

Common Mistake: Edge Config for Session Data

// ❌ Wrong: Edge Config has item size limits (64KB)
await edgeConfig.set('session-123', { userData: hugeObject });

// ✅ Right: Redis handles large payloads efficiently
await redis.hset('session-123', 'data', JSON.stringify(userData));

Edge Config items are capped at 64KB. Sessions, user profiles, cart data—anything per-user that might exceed this limit belongs in Redis.

Common Mistake: Redis for Feature Flags

// ❌ Wrong: Feature flags need ultra-low latency everywhere
const flag = await redis.get('feature.beta');

// ✅ Right: Edge Config is pre-distributed globally
const config = await get();
const flag = config?.features?.beta ?? false;

In Middleware running at the edge, a round-trip to Redis can take 50-100ms. Edge Config reads are <15ms. For feature flags that must evaluate instantly at the edge, use Edge Config.

Decision Flowchart

// Storing data? Ask yourself:

1. Is it a file (image, video, PDF)?
YESUse Blob

2. Is it user-specific, frequently updated, or needs immediate writes?
YESUse Redis

3. Is it global config, feature flags, or A/B test segments?
YESUse Edge Config

// If still unsure:
// - Need <15ms global reads for Middleware → Edge Config
// - Need session storage or rate limiting → Redis
// - Need to store user uploads or media → Blob

Combining Them: The Real-World Pattern

Production apps often use all three:

// Middleware: Use Edge Config for feature flags (ultra-fast)
export async function middleware(request: NextRequest) {
  const config = await get();
  if (!config?.features?.newLayout) return NextResponse.next();

  // Redis: Check rate limit
  const ip = request.ip ?? '127.0.0.1';
  if (await isRateLimited(ip)) {
    return NextResponse.redirect(new URL('/rate-limited', request.url));
  }

  return NextResponse.next();
}

// Server Component: Cache expensive DB queries in Redis
async function getUserData(userId: string) {
  const cached = await redis.get(`user:${userId}`);
  if (cached) return JSON.parse(cached);

  const data = await db.users.findUnique({ where: { id: userId } });
  await redis.setex(`user:${userId}`, 3600, JSON.stringify(data));
  return data;
}

// API Route: Handle user upload to Blob
export async function POST(request: NextRequest) {
  const formData = await request.formData();
  const file = formData.get('file') as File;

  const blob = await put(`uploads/${crypto.randomUUID()}.png`, file, {
    access: 'public',
  });

  return Response.json({ url: blob.url });
}

Each tool has a purpose. Don't default to one and shoehorn everything into it. Edge Config for global reads, Redis for dynamic data, Blob for files.

Advertisement

Related Insights

Explore related edge cases and patterns

CSS
Surface
Dynamic Fonts in Web Development
5 min
CSS
Deep
Font Loading: The FOUT, FOIT, and CLS Dilemma
7 min
CSS
Deep
OpenType Features: Ligatures, Tabular Numbers, and Small Caps
7 min

Advertisement