performance

Caching-Strategien für moderne Web-Apps: Redis, CDN und Browser-Cache

Praktischer Guide zu Caching: Browser Cache, CDN, Redis, Service Workers und Next.js Caching für dramatisch schnellere Web-Anwendungen.

Onur Cirakoglu
10 min read
#Caching#Performance#Redis#CDN#Service Worker
Caching Architektur Diagramm mit verschiedenen Cache-Layern

Effektives Caching kann Response-Zeiten von 3 Sekunden auf 50ms reduzieren - ein 60x Speedup! In diesem Guide zeigen wir, wie Sie Browser-Cache, CDN, Redis und Service Workers richtig einsetzen.

Die Caching-Hierarchie

User Request
    ↓
1. Browser Cache (0-10ms)
    ↓ (miss)
2. Service Worker (10-50ms)
    ↓ (miss)
3. CDN Cache (50-200ms)
    ↓ (miss)
4. Server Memory (Redis) (5-20ms)
    ↓ (miss)
5. Database (100-500ms)

Ziel: Requests so weit oben wie möglich beantworten.

Browser Caching

HTTP Cache Headers

// Next.js API Route
export async function GET() {
  const data = await getStaticData()
 
  return NextResponse.json(data, {
    headers: {
      // Cache 1 Stunde, revalidate im Background
      'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
    },
  })
}

Cache-Control Direktiven:

  • public - Auch Proxies dürfen cachen
  • private - Nur Browser cacht
  • no-cache - Revalidate vor Nutzung
  • no-store - Nie cachen
  • max-age=3600 - Browser cached 1h
  • s-maxage=3600 - CDN cached 1h
  • stale-while-revalidate=86400 - Background revalidation

Cache-Strategie nach Content-Typ

// Statische Assets
'Cache-Control': 'public, immutable, max-age=31536000'
 
// API Responses (häufig ändernd)
'Cache-Control': 'public, max-age=60, stale-while-revalidate=300'
 
// User-spezifische Daten
'Cache-Control': 'private, no-cache'
 
// Nie cachen
'Cache-Control': 'no-store'

Next.js App Router Caching

Fetch Caching

// Default: Cache unbegrenzt
const data = await fetch('https://api.example.com/data')
 
// Cache 1 Stunde
const posts = await fetch('https://api.example.com/posts', {
  next: { revalidate: 3600 }
})
 
// Nie cachen
const user = await fetch('https://api.example.com/user', {
  cache: 'no-store'
})
 
// Force cache (auch wenn expired)
const static = await fetch('https://api.example.com/config', {
  cache: 'force-cache'
})

Route Segment Config

// app/blog/page.tsx
export const revalidate = 3600 // Revalidate every hour
export const dynamic = 'force-static' // Force static at build
export const fetchCache = 'default-cache'
 
export default async function BlogPage() {
  const posts = await getPosts()
  return <PostList posts={posts} />
}

Programmatic Cache Revalidation

// app/actions/revalidate.ts
'use server'
 
import { revalidatePath, revalidateTag } from 'next/cache'
 
export async function updatePost(id: string) {
  await db.post.update({ where: { id }, data: { ... } })
 
  // Revalidate specific path
  revalidatePath(`/blog/${id}`)
 
  // Or revalidate by tag
  revalidateTag('posts')
}
// Fetch mit Tag
const posts = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] }
})

CDN Caching

Edge Network Configuration

// next.config.js
module.exports = {
  headers: async () => [
    {
      source: '/api/:path*',
      headers: [
        {
          key: 'Cache-Control',
          value: 'public, s-maxage=60, stale-while-revalidate=300',
        },
      ],
    },
  ],
}

Cloudflare Cache Rules

// Custom Cache-Control per Route
export async function GET(request: Request) {
  const data = await getData()
 
  return new Response(JSON.stringify(data), {
    headers: {
      'Cache-Control': 'public, max-age=3600',
      'CDN-Cache-Control': 'max-age=86400', // Cloudflare-specific
    },
  })
}

Cache Purging

// Purge Cloudflare Cache
async function purgeCache(urls: string[]) {
  await fetch('https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.CLOUDFLARE_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ files: urls }),
  })
}

Redis Caching

Setup Redis Client

// lib/redis.ts
import { Redis } from '@upstash/redis'
 
export const redis = new Redis({
  url: process.env.UPSTASH_REDIS_URL!,
  token: process.env.UPSTASH_REDIS_TOKEN!,
})

Cache Wrapper Pattern

// lib/cache.ts
import { redis } from './redis'
 
export async function withCache<T>(
  key: string,
  fetcher: () => Promise<T>,
  ttl: number = 3600
): Promise<T> {
  // Try to get from cache
  const cached = await redis.get<T>(key)
  if (cached) return cached
 
  // Fetch fresh data
  const fresh = await fetcher()
 
  // Store in cache
  await redis.setex(key, ttl, JSON.stringify(fresh))
 
  return fresh
}

Usage in API Routes

// app/api/posts/route.ts
import { withCache } from '@/lib/cache'
 
export async function GET() {
  const posts = await withCache(
    'posts:all',
    async () => {
      return await db.post.findMany()
    },
    3600 // 1 hour
  )
 
  return NextResponse.json(posts)
}

Cache Patterns

1. Cache-Aside (Lazy Loading)

async function getUser(id: string) {
  const cacheKey = `user:${id}`
 
  // Check cache
  let user = await redis.get(cacheKey)
 
  if (!user) {
    // Load from DB
    user = await db.user.findUnique({ where: { id } })
 
    // Store in cache
    await redis.setex(cacheKey, 3600, JSON.stringify(user))
  }
 
  return user
}

2. Write-Through

async function updateUser(id: string, data: any) {
  // Update DB
  const user = await db.user.update({
    where: { id },
    data,
  })
 
  // Update cache immediately
  await redis.setex(`user:${id}`, 3600, JSON.stringify(user))
 
  return user
}

3. Write-Behind (Async)

async function trackPageView(pageId: string) {
  // Increment counter in Redis immediately
  await redis.incr(`pageviews:${pageId}`)
 
  // Background job writes to DB every 5 minutes
  // (not shown here)
}

Service Worker Caching

Basic Service Worker

// public/sw.js
const CACHE_NAME = 'v1'
 
// Cache static assets on install
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll([
        '/',
        '/offline.html',
        '/images/logo.png',
      ])
    })
  )
})
 
// Serve from cache, fallback to network
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request)
    })
  )
})

Next.js PWA Integration

pnpm add next-pwa
// next.config.js
const withPWA = require('next-pwa')({
  dest: 'public',
  register: true,
  skipWaiting: true,
})
 
module.exports = withPWA({
  // Your Next.js config
})

Advanced Caching Strategies

// Workbox Strategies
import { registerRoute } from 'workbox-routing'
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'
 
// Images: Cache First
registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({ cacheName: 'images' })
)
 
// API: Network First
registerRoute(
  ({ url }) => url.pathname.startsWith('/api/'),
  new NetworkFirst({ cacheName: 'api' })
)
 
// CSS/JS: Stale While Revalidate
registerRoute(
  ({ request }) => request.destination === 'style' || request.destination === 'script',
  new StaleWhileRevalidate({ cacheName: 'static-resources' })
)

Caching Best Practices

1. Cache Invalidation Strategie

// Tag-based Invalidation
const CACHE_TAGS = {
  POSTS: 'posts',
  USER: (id: string) => `user:${id}`,
  PRODUCTS: 'products',
}
 
// Revalidate all posts
await revalidateTag(CACHE_TAGS.POSTS)
 
// Revalidate specific user
await revalidateTag(CACHE_TAGS.USER('123'))

2. Cache Warming

// Warm cache on deploy
async function warmCache() {
  const criticalPaths = [
    '/api/products',
    '/api/categories',
    '/api/featured',
  ]
 
  await Promise.all(
    criticalPaths.map(path =>
      fetch(`https://example.com${path}`)
    )
  )
}

3. Stale Content Handling

// Stale-While-Revalidate Pattern
async function getData() {
  const cacheKey = 'data:key'
 
  // Return stale data immediately
  const stale = await redis.get(cacheKey)
 
  // Revalidate in background
  revalidateInBackground(cacheKey)
 
  return stale
}
 
async function revalidateInBackground(key: string) {
  const fresh = await fetchFreshData()
  await redis.setex(key, 3600, JSON.stringify(fresh))
}

Performance Monitoring

// Track Cache Hit Rates
export async function GET(req: Request) {
  const cacheKey = 'data:key'
  const start = Date.now()
 
  const cached = await redis.get(cacheKey)
  const isCacheHit = !!cached
 
  // Log metrics
  await logMetric({
    route: req.url,
    cacheHit: isCacheHit,
    duration: Date.now() - start,
  })
 
  return NextResponse.json(cached || await fetchData())
}

Real-World Results

Bei unserem E-Commerce-Projekt:

Vor Caching:

  • API Response Time: 420ms
  • Page Load: 2.8s
  • Server Requests/min: 12,000

Nach Multi-Layer Caching:

  • API Response Time: 45ms (-89%)
  • Page Load: 0.9s (-68%)
  • Server Requests/min: 2,400 (-80%)
  • Cache Hit Rate: 94%

Cost Savings: -75% Server-Kosten

Zusammenfassung

Effektives Caching nutzt mehrere Layer:

Browser Cache für statische Assets ✅ CDN für geografisch verteilte Inhalte ✅ Redis für dynamische Daten ✅ Service Workers für Offline-Support ✅ Next.js Caching für optimale Integration

Resultat: 60-90% schnellere Response-Zeiten.

Caching-Architektur Beratung?

Als Performance-Spezialisten implementieren wir:

  • 🏗️ Multi-Layer Caching-Architektur
  • Redis/CDN Setup und Optimierung
  • 📊 Cache Performance Monitoring
  • 🔄 Invalidation-Strategien

Kostenlose Beratung

Aktualisiert: Mai 2024

#Caching#Performance#Redis#CDN#Service Worker