Redis caching dramatically reduces database load and API response times. Upstash provides serverless Redis that works perfectly with Next.js on Vercel.
Step 1: Install Upstash Redis
pnpm add @upstash/redis
Step 2: Configure Redis Client
// lib/redis.ts
import { Redis } from "@upstash/redis";
export const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
Step 3: Basic Cache Pattern
// lib/cache.ts
import { redis } from "./redis";
export async function cached<T>(
key: string,
fetcher: () => Promise<T>,
ttl: number = 3600 // seconds
): Promise<T> {
// Try cache first
const cached = await redis.get<T>(key);
if (cached !== null) {
return cached;
}
// Fetch fresh data
const data = await fetcher();
// Store in cache
await redis.set(key, data, { ex: ttl });
return data;
}
Step 4: Cache Database Queries
// lib/data.ts
import { cached } from "@/lib/cache";
import { db } from "@/db";
import { posts } from "@/db/schema";
import { eq } from "drizzle-orm";
export async function getPopularPosts() {
return cached(
"posts:popular",
async () => {
return db
.select()
.from(posts)
.orderBy(desc(posts.views))
.limit(10);
},
600 // Cache for 10 minutes
);
}
export async function getPostBySlug(slug: string) {
return cached(
`post:${slug}`,
async () => {
const [post] = await db
.select()
.from(posts)
.where(eq(posts.slug, slug))
.limit(1);
return post || null;
},
1800 // Cache for 30 minutes
);
}
Step 5: Cache Invalidation
// lib/cache.ts
import { redis } from "./redis";
export async function invalidate(key: string) {
await redis.del(key);
}
export async function invalidatePattern(pattern: string) {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(...keys);
}
}
// When updating a post
async function updatePost(slug: string, data: PostUpdate) {
await db.update(posts).set(data).where(eq(posts.slug, slug));
// Invalidate specific post cache and related lists
await invalidate(`post:${slug}`);
await invalidatePattern("posts:*");
}
Step 6: Cache API Responses
// app/api/products/route.ts
import { NextResponse } from "next/server";
import { cached } from "@/lib/cache";
export async function GET() {
const products = await cached(
"api:products",
async () => {
const res = await fetch("https://api.example.com/products");
return res.json();
},
300 // Cache for 5 minutes
);
return NextResponse.json(products);
}
Step 7: Stale-While-Revalidate Pattern
export async function swr<T>(
key: string,
fetcher: () => Promise<T>,
ttl: number = 3600,
staleTtl: number = 7200
): Promise<T> {
const cached = await redis.get<{ data: T; timestamp: number }>(key);
if (cached) {
const age = (Date.now() - cached.timestamp) / 1000;
if (age < ttl) {
// Fresh - return cached
return cached.data;
}
if (age < staleTtl) {
// Stale - return cached but revalidate in background
fetcher().then(async (data) => {
await redis.set(
key,
{ data, timestamp: Date.now() },
{ ex: staleTtl }
);
});
return cached.data;
}
}
// Miss or expired - fetch fresh
const data = await fetcher();
await redis.set(
key,
{ data, timestamp: Date.now() },
{ ex: staleTtl }
);
return data;
}
Step 8: Session Storage with Redis
// lib/session.ts
import { redis } from "./redis";
import { cookies } from "next/headers";
interface Session {
userId: string;
email: string;
role: string;
}
export async function getSession(): Promise<Session | null> {
const cookieStore = await cookies();
const sessionId = cookieStore.get("session_id")?.value;
if (!sessionId) return null;
return redis.get<Session>(`session:${sessionId}`);
}
export async function setSession(sessionId: string, data: Session) {
await redis.set(`session:${sessionId}`, data, {
ex: 60 * 60 * 24 * 7, // 7 days
});
}
export async function destroySession(sessionId: string) {
await redis.del(`session:${sessionId}`);
}
Step 9: Rate Limiter with Redis
export async function checkRateLimit(
key: string,
limit: number,
window: number
): Promise<{ allowed: boolean; remaining: number }> {
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, window);
}
return {
allowed: current <= limit,
remaining: Math.max(0, limit - current),
};
}
// Usage
const { allowed, remaining } = await checkRateLimit(
`ratelimit:${ip}`,
10, // 10 requests
60 // per 60 seconds
);
Step 10: Cache Monitoring
// app/api/admin/cache/route.ts
import { redis } from "@/lib/redis";
export async function GET() {
const info = await redis.info();
const dbSize = await redis.dbsize();
return NextResponse.json({
connectedClients: info.connected_clients,
usedMemory: info.used_memory_human,
totalKeys: dbSize,
});
}
Need High-Performance Architecture?
We build web applications with Redis caching, optimized databases, and scalable infrastructure. Contact us to discuss your project.