Upstash Redis와 Next.js: 완전 가이드 (2026)
Source: Dev.to
Redis는 빠릅니다. 하지만 서버리스 스택에 Redis를 직접 호스팅하는 것은 악몽과도 같습니다—콜드 스타트, 연결 풀 고갈, 그리고 서버리스 함수가 계속 두드리는 영구 서버 관리 등. Upstash는 이러한 문제를 HTTP 기반 Redis API로 해결합니다. 이 API는 0으로 스케일링되고, 요청당 요금이 부과되며, Next.js App Router와 네이티브하게 작동합니다.
이 가이드에서는 실제 운영 환경에서 중요한 패턴들을 다룹니다: 적절한 TTL을 가진 캐시 어사이드, SWR (stale‑while‑revalidate), 세션 스토리지, 그리고 pub/sub. 실제 코드와 실제 트레이드오프를 제공합니다.
전체 기사와 모든 코드 예시는 stacknotice.com에서 확인하세요.
왜 전통적인 Redis 인스턴스보다 Upstash를 선택해야 할까?
표준 Redis는 지속적인 TCP 연결을 사용합니다. 서버리스 함수는 지속적인 연결을 유지하지 않으며—각 호출마다 새로운 연결을 열 수 있습니다. 규모가 커지면 ECONNREFUSED 혹은 최대 연결 오류가 발생하고, 이는 디버깅이 번거롭고 비용이 많이 듭니다.
Upstash의 @upstash/redis 클라이언트는 HTTP/REST를 통해 통신합니다. 연결 풀도 없고, 연결 제한에 대한 고민도 없습니다. 각 요청은 무상태(stateless)이며, 이는 Next.js 서버 컴포넌트와 라우트 핸들러에 딱 맞습니다.
다른 장점들:
요청당 요금 — 한 번도 히트되지 않은 캐시는 비용이 $0
전역 복제 — Vercel 엣지 지역 어디서든 낮은 지연시간
네이티브 Edge Runtime 지원 — Next.js 미들웨어에서도 동작
무료 티어 — 하루 10,000명령, 신용카드 필요 없음
설정
npm install @upstash/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!,
})
패턴 1: Cache‑Aside
// lib/cache.ts
import { redis } from './redis'
export async function withCache(
key: string,
fetcher: () => Promise<any>,
options: { ttl?: number; prefix?: string } = {}
): Promise<any> {
const { ttl = 300, prefix = 'cache' } = options
const cacheKey = `${prefix}:${key}`
const cached = await redis.get(cacheKey)
if (cached !== null) return cached
const data = await fetcher()
await redis.setex(cacheKey, ttl, JSON.stringify(data))
return data
}
서버 컴포넌트에서 사용하기:
// app/products/page.tsx
export default async function ProductsPage() {
const products = await withCache(
'products:all',
() => db.query.products.findMany({ where: eq(products.active, true) }),
{ ttl: 60 * 5 }
)
return <div>{/* render products */}</div>
}
패턴 2: 키 네임스페이싱
// lib/cache-keys.ts
export const CacheKeys = {
userProfile: (userId: string) => `user:${userId}:profile`,
products: () => 'products:all',
productById: (id: string) => `product:${id}`,
pricingPlans: () => 'pricing:plans',
}
// 결정적인 무효화
async function invalidateUserCache(userId: string) {
await redis.del(CacheKeys.userProfile(userId))
}
패턴 3: Stale‑While‑Revalidate
SWR은 천둥벌레 문제를 해결합니다 — 오래된 데이터를 즉시 제공하고 백그라운드에서 새로 고칩니다:
export async function withSWRCache(
key: string,
fetcher: () => Promise<any>,
options: { freshTtl: number; staleTtl: number }
): Promise<any> {
const entry = await redis.get(key)
if (entry !== null) {
const age = (Date.now() - entry.cachedAt) / 1000
if (age < options.freshTtl) {
return entry.value // 아직 신선함
}
// 오래된 데이터 반환 + 백그라운드 재검증
revalidate(key, fetcher, options.staleTtl)
return entry.value
}
// 캐시가 없으면 직접 가져와서 저장
const data = await fetcher()
await redis.setex(key, options.freshTtl, JSON.stringify(data))
return data
}
패턴 4: 세션 스토리지
import { randomBytes } from 'crypto'
const SESSION_TTL = 60 * 60 * 24 // 1일
export async function createSession(data: any): Promise<string> {
const sessionId = randomBytes(32).toString('hex')
const session: Session = { ...data, createdAt: Date.now() }
await redis.setex(`session:${sessionId}`, SESSION_TTL, JSON.stringify(session))
return sessionId
}
export async function getSession(sessionId: string): Promise<Session | null> {
return redis.get(`session:${sessionId}`)
}
패턴 5: Rate Limiting
import { Ratelimit } from '@upstash/ratelimit'
export const apiRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '10 s'),
analytics: true,
})
// 라우트 핸들러 안에서:
const { success, limit, remaining } = await apiRateLimit.limit(ip)
if (!success) {
return Response.json({ error: 'Too many requests' }, { status: 429 })
}
패턴 6: Distributed Locks
export async function acquireLock(resource: string, ttl: number): Promise<string | null> {
const lockId = crypto.randomUUID()
const result = await redis.set(`lock:${resource}`, lockId, { nx: true, ex: ttl })
return result === 'OK' ? lockId : null
}
무엇을 캐시하고 무엇을 캐시하면 안 될까
캐시할 대상: 데이터베이스 쿼리 결과, 외부 API 응답, 계산·집계된 데이터, 피처 플래그 상태
캐시하면 안 되는 대상: 인증 검사, 금융 거래, 실시간 재고, 사용자 ID에 스코프되지 않은 개인 데이터
Upstash 요금제
| Tier | Price | Commands/day |
|---|---|---|
| Free | $0 | 10,000 |
| Pay-as-you-go | $0.20 / 100K | Unlimited |
| Pro | $10 / month | Unlimited |
전체 패턴, 코드 예시, 미들웨어 수준 캐싱이 포함된 완전 가이드는 stacknotice.com/blog/upstash-redis-nextjs-complete-guide-2026에서 확인하세요.
