Next.js sudah punya caching built-in, tapi dengan Upstash Redis, kamu bisa dapat caching yang lebih powerful dan scalable.
Artikel ini panduan lengkapnya.
Kenapa Next.js + Upstash?
| Fitur | Next.js Cache | + Upstash Redis |
|---|---|---|
| Storage | In-memory | ✅ Persistent |
| Scaling | Per-server | ✅ Global |
| TTL | Limited | ✅ Flexible |
| Session | Cookie-based | ✅ Server-side |
| ISR | Per-page | ✅ Fine-grained |
Langkah 1: Setup Upstash
Buat Akun Upstash
- Buka upstash.com
- Daftar akun gratis
- Klik Create Database
- Pilih Redis dan region terdekat
- Catat REST URL dan Token
Connect Upstash ke Helipod
- Buka project Helipod
- Masuk ke Settings → Integrations
- Cari Upstash dan klik Connect
- Paste REST URL dan Token
Langkah 2: Install Dependencies
npm install @upstash/redis @upstash/ratelimit
Langkah 3: Konfigurasi Redis
// 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!,
});
Environment Variables
# .env.local
UPSTASH_REDIS_REST_URL=https://xxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=xxxxxx
Langkah 4: Caching Patterns
1. API Response Caching
// app/api/products/route.ts
import { redis } from '@/lib/redis';
export async function GET() {
const cacheKey = 'products:all';
// Cek cache
const cached = await redis.get(cacheKey);
if (cached) {
return Response.json(cached);
}
// Fetch dari database
const products = await db.products.findMany();
// Simpan ke cache (5 menit)
await redis.set(cacheKey, JSON.stringify(products), { EX: 300 });
return Response.json(products);
}
2. ISR (Incremental Static Regeneration)
// app/products/[id]/page.tsx
import { redis } from '@/lib/redis';
// Revalidate setiap 60 detik
export const revalidate = 60;
async function getProduct(id: string) {
const cacheKey = `product:${id}`;
// Cek cache
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached as string);
}
// Fetch dari database
const product = await db.products.findById(id);
// Simpan ke cache
await redis.set(cacheKey, JSON.stringify(product), { EX: 3600 });
return product;
}
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProduct(params.id);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
3. Server-Side Session
// lib/session.ts
import { redis } from '@/lib/redis';
import { cookies } from 'next/headers';
import { SignJWT, jwtVerify } from 'jose';
const SESSION_TTL = 86400; // 24 jam
export async function createSession(userId: string) {
const sessionId = crypto.randomUUID();
// Simpan session ke Redis
await redis.set(`session:${sessionId}`, JSON.stringify({
userId,
createdAt: Date.now(),
expiresAt: Date.now() + SESSION_TTL * 1000,
}), { EX: SESSION_TTL });
// Set cookie
cookies().set('session', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: SESSION_TTL,
});
return sessionId;
}
export async function getSession() {
const sessionId = cookies().get('session')?.value;
if (!sessionId) return null;
const session = await redis.get(`session:${sessionId}`);
return session ? JSON.parse(session as string) : null;
}
export async function deleteSession() {
const sessionId = cookies().get('session')?.value;
if (sessionId) {
await redis.del(`session:${sessionId}`);
cookies().delete('session');
}
}
4. Rate Limiting
// lib/ratelimit.ts
import { redis } from '@/lib/redis';
import { Ratelimit } from '@upstash/ratelimit';
export const ratelimit = new Ratelimit({
redis: redis,
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 request per 10 detik
analytics: true,
});
// app/api/contact/route.ts
import { ratelimit } from '@/lib/ratelimit';
export async function POST(request: Request) {
const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success } = await ratelimit.limit(ip);
if (!success) {
return Response.json(
{ error: 'Too many requests' },
{ status: 429 }
);
}
// Proses request
const data = await request.json();
// ...
}
5. Cache Invalidation
// lib/cache.ts
import { redis } from '@/lib/redis';
export async function invalidateProduct(productId: string) {
// Hapus product cache
await redis.del(`product:${productId}`);
// Hapus products list cache
await redis.del('products:all');
// Invalidate by pattern
const keys = await redis.keys('product:*');
if (keys.length > 0) {
await redis.del(...keys);
}
}
Use Cases
1. E-commerce Product Cache
// app/api/products/[id]/route.ts
import { redis } from '@/lib/redis';
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const cacheKey = `product:${params.id}`;
// Multi-layer cache
const cached = await redis.get(cacheKey);
if (cached) {
return Response.json(cached);
}
// Fetch dari database
const product = await db.products.findById(params.id);
if (!product) {
return Response.json({ error: 'Not found' }, { status: 404 });
}
// Cache dengan TTL berbeda berdasarkan popularitas
const ttl = product.views > 1000 ? 3600 : 300; // 1 jam atau 5 menit
await redis.set(cacheKey, JSON.stringify(product), { EX: ttl });
return Response.json(product);
}
2. User Session Management
// app/api/auth/login/route.ts
import { createSession } from '@/lib/session';
export async function POST(request: Request) {
const { email, password } = await request.json();
// Validasi user
const user = await db.users.findByEmail(email);
if (!user || !await bcrypt.compare(password, user.password)) {
return Response.json({ error: 'Invalid credentials' }, { status: 401 });
}
// Buat session
await createSession(user.id);
return Response.json({ success: true });
}
3. Real-time Leaderboard
// app/api/leaderboard/route.ts
import { redis } from '@/lib/redis';
export async function GET() {
// Ambil top 10 dari sorted set
const leaderboard = await redis.zrange('leaderboard', 0, 9, {
REV: true,
WITHSCORES: true,
});
return Response.json(leaderboard);
}
export async function POST(request: Request) {
const { userId, score } = await request.json();
// Update score
await redis.zadd('leaderboard', score, userId);
return Response.json({ success: true });
}
4. Feature Flags
// lib/feature-flags.ts
import { redis } from '@/lib/redis';
export async function isFeatureEnabled(feature: string): Promise<boolean> {
const enabled = await redis.get(`feature:${feature}`);
return enabled === true;
}
export async function setFeatureFlag(feature: string, enabled: boolean) {
await redis.set(`feature:${feature}`, enabled);
}
Best Practices
1. Cache Key Naming
// ✅ Good
const cacheKey = `product:${productId}`;
const cacheKey = `user:${userId}:sessions`;
const cacheKey = `api:products:list:page:${page}`;
// ❌ Bad
const cacheKey = '123';
const cacheKey = 'data';
2. TTL Strategy
// Data yang jarang berubah
await redis.set(key, value, { EX: 86400 }); // 24 jam
// Data yang sering berubah
await redis.set(key, value, { EX: 300 }); // 5 menit
// Session
await redis.set(key, value, { EX: 86400 }); // 24 jam
3. Error Handling
import { redis } from '@/lib/redis';
async function getCachedData(key: string) {
try {
const cached = await redis.get(key);
return cached ? JSON.parse(cached as string) : null;
} catch (error) {
console.error('Redis error:', error);
return null; // Fallback ke database
}
}
4. Connection Pooling
// lib/redis.ts
import { Redis } from '@upstash/redis';
// Upstash sudah handle connection pooling
// Tidak perlu setup manual
export const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
Monitoring
// Cek cache hit rate
const info = await redis.info('stats');
const hits = parseInt(info.match(/keyspace_hits:(\d+)/)?.[1] || '0');
const misses = parseInt(info.match(/keyspace_misses:(\d+)/)?.[1] || '0');
const hitRate = hits / (hits + misses);
console.log(`Cache hit rate: ${(hitRate * 100).toFixed(2)}%`);
Checklist
- Buat akun Upstash
- Install Upstash SDK
- Konfigurasi Redis
- Implementasi caching
- Setup session management
- Implementasi rate limiting
- Setup monitoring
- Test performance
Kesimpulan
Next.js + Upstash Redis di Helipod memberikan caching yang powerful:
- ✅ Persistent caching
- ✅ Global scaling
- ✅ Flexible TTL
- ✅ Server-side session
- ✅ Rate limiting built-in
Butuh bantuan? Hubungi [email protected]
Baca juga: