---
title: "Next.js + Upstash Redis: Caching Strategy di Helipod"
url: "https://helipod.io/blog/nextjs-upstash-caching-strategy"
date: "2026-06-17"
updated: "2026-06-17"
author: "Tim Helipod"
tags: ["nextjs", "upstash", "redis", "caching", "performance", "helipod"]
excerpt: "Tutorial lengkap caching Next.js dengan Upstash Redis di Helipod. ISR, session, dan API response caching untuk performa optimal."
---

# Next.js + Upstash Redis: Caching Strategy di Helipod

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](https://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

```bash
npm install @upstash/redis @upstash/ratelimit
```

## Langkah 3: Konfigurasi Redis

```typescript
// 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
# .env.local
UPSTASH_REDIS_REST_URL=https://xxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=xxxxxx
```

## Langkah 4: Caching Patterns

### 1. API Response Caching

```typescript
// 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)

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// ✅ 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

```typescript
// 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

```typescript
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

```typescript
// 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

```typescript
// 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](mailto:support@helipod.io)*

*Baca juga:*
- *[Integrasi Upstash Redis di Helipod](/blog/integrasi-upstash-redis-serverless)*
- *[Caching Strategies Redis](/blog/caching-strategies-redis-helipod)*
