SitemapGenerator Cache Adapter

The sitemap_generator gem writes files to disk, which doesn't work on Kubernetes or Heroku. This adapter stores sitemaps in Rails.cache instead, making them work seamlessly with ephemeral storage and multi-pod deployments. Published as a gem for Rails teams running on modern infrastructure.

A cache-based storage adapter for the sitemap_generator gem that stores sitemaps in Rails.cache instead of the filesystem.

🎯 The Problem

The popular sitemap_generator gem writes sitemaps to disk by default. This works fine on traditional servers but breaks on modern infrastructure:

Kubernetes Deployments

Pods have ephemeral storage - files written during one request might not exist for the next. Sitemaps vanish when pods restart or scale.

Heroku and Serverless

Read-only or ephemeral filesystems mean sitemap files can’t be written at all, or disappear on dyno restart.

Multi-Pod Deployments

Even with persistent storage, each pod has its own filesystem. A sitemap generated on pod A isn’t visible to pod B serving the next request.

The standard workarounds (S3 adapter, shared NFS) add complexity and external dependencies for what should be a simple feature.

✨ The Solution

Store sitemaps in Rails.cache instead of the filesystem. When using a shared cache backend like Solid Cache (database-backed) or Redis, sitemaps are:

  • Persistent across pod restarts and deploys
  • Shared across all application instances
  • Simple - no external storage services needed

📦 Installation

Add to your Gemfile:

gem "sitemap_generator"
gem "sitemap_generator-cache_adapter"

Configure in config/sitemap.rb:

SitemapGenerator::Sitemap.default_host = "https://example.com"
SitemapGenerator::Sitemap.adapter = SitemapGenerator::CacheAdapter.new
SitemapGenerator::Sitemap.compress = false
SitemapGenerator::Sitemap.create_index = false

SitemapGenerator::Sitemap.create do
  add "/about", changefreq: "monthly"
  add "/contact", changefreq: "monthly"
end

Create a controller to serve sitemaps:

class SitemapsController < ApplicationController
  def show
    xml = SitemapGenerator::CacheAdapter.fetch("sitemap.xml") do
      load Rails.root.join("config", "sitemap.rb")
    end
    xml.present? ? render(xml: xml) : head(:not_found)
  end
end

Add the route:

get "sitemap.xml", to: "sitemaps#show", defaults: { format: :xml }

🔧 How It Works

The adapter implements sitemap_generator’s adapter interface with a single write method that stores data in the cache:

def write(location, raw_data)
  cache_key = "#{self.class.cache_key_prefix}:#{location.filename}"
  self.class.cache_store.write(cache_key, raw_data, expires_in: self.class.expires_in)
end

The fetch class method handles lazy regeneration:

def self.fetch(filename, &block)
  cache_store.fetch(cache_key, expires_in: expires_in) do
    yield if block_given?
    cache_store.read(cache_key)
  end
end

On cache miss, the block runs (loading config/sitemap.rb, which calls create), the sitemap gets written to cache, and subsequent requests serve from cache until expiration.

🎨 Features

Lazy Regeneration

Sitemaps regenerate automatically when the cache expires. No cron jobs or scheduled tasks needed - the first request after expiration triggers regeneration.

Configurable

SitemapGenerator::CacheAdapter.new(
  cache_key_prefix: "myapp:sitemap",  # Default: "sitemap_generator"
  expires_in: 12.hours,                # Default: 24.hours
  cache_store: Rails.cache             # Default: Rails.cache
)

Class Methods for Cache Management

SitemapGenerator::CacheAdapter.read("sitemap.xml")
SitemapGenerator::CacheAdapter.exist?("sitemap.xml")
SitemapGenerator::CacheAdapter.delete("sitemap.xml")
SitemapGenerator::CacheAdapter.clear_all

Multiple Sitemap Support

For large sites (50k+ URLs), the adapter handles multiple files and sitemap indexes - just update the controller to serve dynamic filenames.

📈 Why It Matters

Before: Workarounds involving S3 buckets, shared NFS mounts, or accepting that sitemaps don’t work properly on Kubernetes.

After: Sitemaps just work. Store them in the same cache backend you’re already using for everything else.

🔍 Technical Details

  • ~200 lines of Ruby (check the source)
  • Works with any Rails cache backend (Solid Cache, Redis, Memcached)
  • Compatible with sitemap_generator 6.x+
  • Supports Ruby 3.1+
  • Full test suite with RSpec
  • CI via GitHub Actions

🛠️ Development Story

Built in January 2026 while migrating Rails marketing sites to Kubernetes. The sites used sitemap_generator with the default file adapter, which broke immediately on ephemeral pod storage.

Rather than add S3 complexity for a few XML files, storing sitemaps in Solid Cache (already in use) was the obvious solution. The adapter took an afternoon to build, test, and publish - a focused tool solving a specific infrastructure problem.