Skip to content

Redis Caching — Using Redis for SSR Cache Storage

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Redis Caching. We cover key concepts, practical examples, and best practices to help you master this topic.

Redis caching for SSR stores rendered HTML fragments and full pages in memory, providing sub-millisecond read times, TTL-based expiration, cache invalidation patterns, and high availability for production SSR applications.

What You'll Learn

By the end of this tutorial, you will understand how to use Redis as an SSR cache store, implement cache-aside and write-through patterns, handle cache invalidation with Redis keys and patterns, manage cache TTLs, and configure Redis for high availability in SSR applications.

Why It Matters

SSR without Redis caching does not scale. Each request renders the same components and fetches the same data. Redis stores rendered HTML in memory, serving cached responses in 1-5ms instead of 100-500ms render times. For high-traffic SSR applications, Redis caching is essential for performance and cost.

Real-World Use

A news site serving 1 million page views per day used Redis caching for SSR. Each uncached page took 450ms to render. With Redis, cached pages served in 3ms. They reduced their server fleet from 20 to 4 instances, saving 80 percent on hosting costs while maintaining fast response times.

Redis Cache Patterns for SSR
    ┌──────────────────────────────────────────────────────────┐
    │           Redis Cache Patterns for SSR                   │
    ├──────────────────────────────────────────────────────────┤
    │                                                          │
    │  Cache-Aside (Lazy Loading):                             │
    │  1. Request comes in                                     │
    │  2. Check Redis for cached HTML                         │
    │  3. If found → return cached HTML                       │
    │  4. If not found → render page                          │
    │  5. Store rendered HTML in Redis                        │
    │  6. Return HTML to client                               │
    │                                                          │
    │  Write-Through:                                          │
    │  1. Content is created/updated                           │
    │  2. Render the page                                      │
    │  3. Store in Redis immediately                           │
    │  4. First request gets cached version                    │
    │                                                          │
    │  Cache Invalidation:                                     │
    │  1. Content changes                                      │
    │  2. Delete or update related cache keys                 │
    │  3. Next request renders fresh page                     │
    │  4. Cache is repopulated                                │
    │                                                          │
    └──────────────────────────────────────────────────────────┘

Think of Redis caching for SSR like a chef's mise en place. Before service begins, the chef preps ingredients (renders pages) and stores them in labeled containers (Redis keys). When an order comes in (request), the chef grabs the prepped ingredients (cached HTML) and assembles the dish in seconds instead of cooking from scratch.

Redis Cache Implementation

const { createClient } = require('redis');
const { renderToString } = require('react-dom/server');

class SSRCache {
    constructor() {
        this.client = createClient({
            url: process.env.REDIS_URL || 'redis://localhost:6379',
            socket: {
                reconnectStrategy: (retries) => Math.min(retries * 50, 1000)
            }
        });

        this.client.on('error', (err) => {
            console.error('Redis error:', err);
        });

        this.client.connect();
        this.defaultTTL = 300; // 5 minutes
    }

    // Cache-aside pattern
    async getOrRender(key, renderFn, ttl = this.defaultTTL) {
        try {
            // 1. Try cache
            const cached = await this.client.get(key);
            if (cached) {
                console.log(`Redis HIT: ${key}`);
                return { html: cached, source: 'cache' };
            }

            // 2. Cache miss — render
            console.log(`Redis MISS: ${key}`);
            const html = await renderFn();

            // 3. Store in cache (don't await — fire and forget)
            this.client.setEx(key, ttl, html).catch(err => {
                console.error('Redis set error:', err);
            });

            return { html, source: 'render' };
        } catch (error) {
            console.error('Cache error:', error);
            // Fallback to rendering
            const html = await renderFn();
            return { html, source: 'render' };
        }
    }

    // Invalidate single key
    async invalidate(key) {
        await this.client.del(key);
        console.log(`Invalidated: ${key}`);
    }

    // Invalidate by pattern
    async invalidatePattern(pattern) {
        const keys = await this.client.keys(`ssr:${pattern}:*`);
        if (keys.length > 0) {
            await this.client.del(keys);
            console.log(`Invalidated ${keys.length} keys: ${pattern}`);
        }
    }

    // Tag-based invalidation
    async invalidateByTag(tag) {
        const keys = await this.client.sMembers(`tag:${tag}`);
        if (keys.length > 0) {
            await this.client.del(keys);
            await this.client.del(`tag:${tag}`);
            console.log(`Invalidated ${keys.length} keys with tag: ${tag}`);
        }
    }

    // Tag a cache key for group invalidation
    async tagKey(key, tags) {
        for (const tag of tags) {
            await this.client.sAdd(`tag:${tag}`, key);
        }
    }
}

module.exports = new SSRCache();

Cache Invalidation Strategies

const cache = require('./ssrCache');

// On content update — invalidate related caches
app.post('/api/posts', async (req, res) => {
    const post = await db.posts.create(req.body);

    // Invalidate:
    // 1. Home page (shows latest posts)
    await cache.invalidate('ssr:/');

    // 2. Category pages
    await cache.invalidatePattern(`ssr:/category:${post.category}`);

    // 3. RSS feed
    await cache.invalidate('ssr:/feed.xml');

    // 4. Tag-based: invalidate all pages tagged with this post's category
    await cache.invalidateByTag(`category:${post.category}`);

    console.log('Cache invalidated for new post:', post.id);
    res.redirect(303, `/posts/${post.slug}`);
});

// Update product — invalidate product page and listing
app.put('/api/products/:id', async (req, res) => {
    const product = await db.products.update(req.params.id, req.body);

    // Invalidate product detail page
    await cache.invalidate(`ssr:/products/${product.slug}`);

    // Invalidate product listing pages
    await cache.invalidatePattern('ssr:/products');
    await cache.invalidatePattern('ssr:/category:');

    // Re-render and cache the product page immediately (write-through)
    const html = await renderToString(
        React.createElement(ProductPage, { product })
    );
    await cache.client.setEx(
        `ssr:/products/${product.slug}`,
        cache.defaultTTL,
        html
    );

    res.json({ success: true });
});

Common Mistakes

  1. No Redis persistence. By default, Redis stores data in memory. Without persistence (RDB/AOF), all cached HTML is lost on restart. Enable persistence for production.
  2. Cache key collisions. Different URLs that map to the same cache key cause content mixing. Include the full URL, query parameters, and language/locale in the cache key.
  3. Over-caching with too long TTL. Long TTLs serve stale content. Short TTLs increase server load. Balance based on how often content changes.
  4. Not handling Redis failures gracefully. If Redis is down, your application should still work (fallback to rendering). Implement circuit breakers and fallbacks.
  5. Large cache values. Redis works best with values under 10MB. Large HTML pages should be compressed (gzip) before storing in Redis.

Practice Questions

  1. What is the cache-aside pattern and how does it work with Redis?
  2. How do you implement cache invalidation by tag in Redis?
  3. Why should you compress HTML before storing in Redis?
  4. How do you handle Redis connection failures in production?
  5. What is the difference between invalidate and invalidatePattern?

Challenge: Implement a complete Redis caching layer for an SSR application: cache-aside pattern for all public pages, tag-based invalidation (tag pages by category and author), write-through caching for frequently accessed content, gzip compression for cached HTML, Redis persistence configuration (RDB), and graceful fallback when Redis is unavailable.

FAQ

Should I use Redis or Memcached for SSR caching?

Redis is generally preferred because it supports data structures (sets for tags), persistence, replication, and pub/sub for cache invalidation. Memcached is simpler but less feature-rich.

How much memory do I need for Redis SSR cache?

Depends on the number of pages and average HTML size. A typical page is 20-50KB gzipped. For 10,000 cached pages at 30KB each: 300MB. Add 50 percent overhead for Redis data structures.

How do I handle cache warmup after deployment?

After deployment, the cache is cold. Implement a warmup script that requests popular pages to populate the cache. Use a load testing tool or crawl the sitemap.

Does Redis cluster work for SSR caching?

Yes. Redis Cluster shards data across nodes. Use a consistent hashing strategy for cache keys to minimize cache misses when nodes are added or removed.

How do I monitor Redis cache performance?

Monitor: cache hit ratio, memory usage, evicted keys, connected clients, command latency. Use Redis INFO command, RedisInsight, or Datadog integration.

Mini Project

Set up Redis caching for an SSR blog: Redis cache-aside for all public pages with 10-minute TTL, tag-based invalidation by category and author, gzip compression of cached HTML, Redis persistence with AOF, cache warmup script that crawls the sitemap on deploy, and a dashboard showing cache hit ratio, memory usage, and TTFB improvement.

What's Next

You understand Redis caching. Now explore SSR Performance to optimize overall SSR rendering speed.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro