价格博客
语言

Next.js + Upstash Redis: Caching Strategy di Helipod

Tim Helipod
发布于 7 分钟阅读

本文以印尼语撰写。

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 support@helipod.io

Baca juga:

Blog

相关文章

查看全部

准备好试用 Helipod 了吗?

立即部署你的应用。免费,无需信用卡。
WhatsApp