BARUTeam, Domain & Bundle
Diskon 30%!
Detail
nextjsupstashrediscachingperformancehelipod

Next.js + Upstash Redis: Caching Strategy di Helipod

Tim Helipod

7 menit baca

Tutorial lengkap caching Next.js dengan Upstash Redis di Helipod. ISR, session, dan API response caching untuk performa optimal.

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

  1. Buka upstash.com
  2. Daftar akun gratis
  3. Klik Create Database
  4. Pilih Redis dan region terdekat
  5. Catat REST URL dan Token

Connect Upstash ke Helipod

  1. Buka project Helipod
  2. Masuk ke Settings → Integrations
  3. Cari Upstash dan klik Connect
  4. 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:

Siap coba Helipod?

Deploy aplikasi kamu sekarang. Gratis, tanpa kartu kredit.

Mulai Gratis →