{"post":{"slug":"how-to-set-up-redis-caching-2026","title":"How to set up Redis caching in 2026","excerpt":"Redis caching cuts database load and API latency dramatically. This guide covers cache-aside, TTL strategy, invalidation, and Upstash for serverless in 2026.","category":"code","date":"April 18, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">R</span>edis caching is one of the highest-leverage performance improvements available — a hot cache turns a 50 ms database query into a sub-millisecond memory lookup. In 2026, the tooling splits into two clear camps: `ioredis` for traditional server deployments, and Upstash for serverless/edge runtimes where persistent TCP connections are not available.\n\n## What changed in 2026\n\n- **Upstash Redis** became the standard for serverless caching — HTTP-over-TLS, globally distributed, and free tier covers most hobby projects.\n- **`ioredis` v6** is stable; `node-redis` v4 is the alternative; both are actively maintained.\n- **Next.js App Router** has a built-in `unstable_cache` function that integrates well with Redis-backed stores.\n- **Dragonfly** and **KeyDB** are Redis-compatible alternatives that offer better multi-threaded throughput on large machines — worth evaluating at high scale.\n\n## Basic setup with ioredis\n\n```bash\nnpm install ioredis\n```\n\n```ts\nimport Redis from 'ioredis';\n\nexport const redis = new Redis(process.env.REDIS_URL, {\n  maxRetriesPerRequest: 3,\n  enableReadyCheck: true,\n  lazyConnect: false,\n});\n\nredis.on('error', (err) => console.error('Redis error:', err));\n```\n\n## Cache-aside pattern\n\n```ts\nasync function getUser(userId: string) {\n  const cacheKey = `user:${userId}`;\n  const cached = await redis.get(cacheKey);\n  if (cached) return JSON.parse(cached);\n\n  const user = await db.user.findUnique({ where: { id: userId } });\n  if (user) {\n    await redis.setex(cacheKey, 3600, JSON.stringify(user)); // TTL: 1 hour\n  }\n  return user;\n}\n```\n\n`setex(key, seconds, value)` atomically sets the value and TTL in one command.\n\n## Invalidation on write\n\n```ts\nasync function updateUser(userId: string, data: Partial<User>) {\n  const updated = await db.user.update({ where: { id: userId }, data });\n  await redis.del(`user:${userId}`); // bust the cache on write\n  return updated;\n}\n```\n\nFor list caches (e.g., `users:page:1`), delete all affected keys or use tag-based invalidation.\n\n## Tag-based invalidation\n\n```ts\nasync function setWithTag(key: string, value: unknown, ttl: number, tag: string) {\n  const pipeline = redis.pipeline();\n  pipeline.setex(key, ttl, JSON.stringify(value));\n  pipeline.sadd(`tag:${tag}`, key);\n  pipeline.expire(`tag:${tag}`, ttl);\n  await pipeline.exec();\n}\n\nasync function invalidateTag(tag: string) {\n  const keys = await redis.smembers(`tag:${tag}`);\n  if (keys.length > 0) await redis.del(...keys);\n  await redis.del(`tag:${tag}`);\n}\n\n// Usage\nawait setWithTag('users:list', users, 3600, 'users');\n// On any user mutation:\nawait invalidateTag('users');\n```\n\n## Upstash for serverless / edge\n\n```bash\nnpm install @upstash/redis\n```\n\n```ts\nimport { Redis } from '@upstash/redis';\n\nconst redis = Redis.fromEnv(); // reads UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN\n\nexport async function getCached<T>(key: string, fetcher: () => Promise<T>, ttl = 3600): Promise<T> {\n  const cached = await redis.get<T>(key);\n  if (cached !== null) return cached;\n\n  const fresh = await fetcher();\n  await redis.setex(key, ttl, fresh);\n  return fresh;\n}\n```\n\nNo connection pooling needed — each call is an independent HTTPS request. Works on Cloudflare Workers, Vercel Edge, and Next.js Middleware.\n\n## TTL strategy\n\n| Data type | Recommended TTL | Notes |\n|---|---|---|\n| User profile | 1–4 hours | Invalidate on update |\n| Session data | 30 min (rolling) | Extend on each access |\n| API response (external) | 5–60 min | Depends on data freshness requirements |\n| Computed/aggregated stats | 1–24 hours | Recompute on cron |\n| Search results | 10–60 min | Tolerate slight staleness |\n| Config / feature flags | 5–15 min | Fast-refresh critical |\n\n## How to pick a caching strategy\n\n1. **Single server, persistent connections** → `ioredis` or `node-redis` with connection pool.\n2. **Serverless / edge (Vercel, Cloudflare)** → Upstash Redis.\n3. **Need per-request cache + Redis** → combine Next.js `unstable_cache` with Upstash.\n4. **Large key space with grouping** → tag-based invalidation.\n5. **Write-heavy data** → cache only final aggregates, not raw rows.\n\n## Common mistakes\n\n**No TTL on cached keys.** Memory fills up; Redis starts evicting random keys in `allkeys-lru` mode or errors in `noeviction` mode. Always set a TTL.\n\n**Caching mutable data without invalidation.** The classic stale cache bug: user changes their name, the old name shows for an hour. Bust the cache synchronously on write.\n\n**JSON serializing inside the cached function on every call.** Serialize once before storing; deserialize once after fetching. Avoid re-serializing hot-path objects.\n\n**Opening a new Redis connection per request in serverless.** In Next.js API Routes (not edge), reuse a module-level connection or use Upstash. A new `ioredis` connection per request will exhaust file descriptors quickly.\n\n## What to skip\n\n- **Caching every DB query** — cache reads that are slow or called at high frequency. Caching a 1 ms query that runs once per hour wastes memory.\n- **Redis as a primary database** — it is an excellent cache and message broker; as a primary data store it requires careful persistence configuration and backup discipline.\n- **Large objects in Redis** — values over ~1 MB stress serialization. Cache references (IDs) and let the client do a second cheap lookup if needed.\n\n## FAQ\n\n**Should I use `get`/`set` or `hget`/`hset`?**\nUse `get`/`set` with JSON for most objects — simpler, and most ORMs return plain objects. Use hashes only when you need to update individual fields without reading the full object.\n\n**How do I handle cache stampedes (thundering herd)?**\nUse a per-key lock (Redis `SET key value NX PX timeout`) to ensure only one request populates the cache on a miss while others wait or serve stale.\n\n**How much memory does Redis need?**\nA 512 MB Redis instance handles millions of small keys comfortably. Monitor `used_memory_human` and set `maxmemory` with `allkeys-lru` eviction policy to stay safe.\n\n**Is Upstash free?**\nThe free tier allows 10,000 commands/day. Paid plans start at ~$0.20 per 100K commands. For most hobby projects, the free tier is sufficient.\n\n## Where to go next\n\n- [How to cache with Redis in 2026](/blog/how-to-cache-with-redis-2026)\n- [How to rate limit an API in 2026](/blog/how-to-rate-limit-an-api-2026)\n- [How to set up Supabase in 2026](/blog/how-to-set-up-supabase-2026)","hue":312,"metaTitle":"How to Set Up Redis Caching in 2026 (Node + Edge)","metaDescription":"Set up Redis caching in 2026 with cache-aside, TTL strategy, tag-based invalidation, and Upstash for serverless. Covers Node.js, Next.","keywords":["how to set up redis caching 2026","redis cache aside pattern","redis ttl strategy","upstash redis serverless","cache invalidation 2026","node.js redis caching","next.js redis cache","redis cache tags","redis caching patterns 2026","how to cache with redis"],"tldr":["**Cache-aside (lazy loading) is the default pattern** — check cache first, on miss fetch from DB, write to cache; simple, battle-tested, and works for most read-heavy workloads.","**Always set a TTL** — unbounded keys fill memory and cause Redis to evict unpredictably; even a long TTL (24 h) is better than none.","**Use Upstash Redis for serverless and edge** — HTTP-based, no persistent connections, works on Cloudflare Workers and Vercel Edge Functions.","**Tag-based invalidation prevents stale cache bugs** — group related keys under a tag so a single `invalidate(tag)` clears them all on mutation.","**Skip:** caching everything by default — cache hot, expensive reads only; caching cheap queries wastes memory and adds code complexity for zero gain."],"readTime":"4 min read","wordCount":921},"related":[{"slug":"how-to-cache-with-redis-2026","title":"How to cache with Redis in 2026: patterns, TTLs, and invalidation","excerpt":"Redis caching in 2026 is faster to set up than ever, but the same mistakes persist: missing TTLs, stale data, and cache stampedes. This guide fixes all three.","category":"code","date":"March 11, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":307},{"slug":"caching-strategies-2026","title":"Caching strategies in 2026: which pattern to use and when","excerpt":"Cache-aside, write-through, read-through, and CDN edge caching solve different problems. Here is the 2026 decision guide with concrete tradeoffs.","category":"code","date":"April 13, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":322},{"slug":"how-to-cache-api-responses-2026","title":"How to cache API responses in 2026: HTTP headers, Redis, and CDN strategies","excerpt":"Caching is the single fastest way to reduce API latency and database load. This is the 2026 guide to HTTP caching, in-process caches, Redis, and CDN layers.","category":"code","date":"March 29, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":315}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-use-ai-for-code-review-2026","title":"How to use AI for code review in 2026","excerpt":"AI code review catches real bugs and style issues, but it still misses context that human reviewers catch — the skill is combining both without adding overhead.","category":"ai","date":"April 19, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":213},"older":{"slug":"how-to-set-up-supabase-2026","title":"How to set up Supabase in 2026","excerpt":"Supabase is a Postgres-backed BaaS with auth, storage, and realtime built in. This guide covers project setup, Row Level Security, and the JS client in 2026.","category":"code","date":"April 18, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":308}}