You just published an important update to your Jekyll blog, but visitors are still seeing the old cached version for hours. Manually purging Cloudflare cache through the dashboard is tedious and error-prone. This cache lag problem undermines the immediacy of static sites and frustrates both you and your audience. The solution lies in automating cache management using specialized Ruby gems that integrate directly with your Jekyll workflow.

In This Article

Understanding Cloudflare Cache Mechanics for Jekyll

Cloudflare caches static assets at its edge locations worldwide. For Jekyll sites, this includes HTML pages, CSS, JavaScript, and images. The default cache behavior depends on file type and cache headers. HTML files typically have shorter cache durations (a few hours) while assets like CSS and images cache longer (up to a year). This is problematic when you need instant updates across all cached content.

Cloudflare offers several cache purging methods: purge everything (entire zone), purge by URL, purge by tag, or purge by host. For Jekyll sites, understanding when to use each method is crucial. Purging everything is heavy-handed and affects all visitors. Purging by URL is precise but requires knowing exactly which URLs changed. The ideal approach combines selective purging with intelligent detection of changed files during the Jekyll build process.

Cloudflare Cache Behavior for Jekyll Files

File Type Default Cache TTL Recommended Purging Strategy
HTML Pages 2-4 hours Purge specific changed pages
CSS Files 1 month Purge on any CSS change
JavaScript 1 month Purge on JS changes
Images (JPG/PNG) 1 year Purge only changed images
WebP/AVIF Images 1 year Purge originals and variants
XML Sitemaps 24 hours Always purge on rebuild

Gem Based Cache Automation Strategies

Several Ruby gems can automate Cloudflare cache management. The most comprehensive is `cloudflare` gem:

# Add to Gemfile
gem 'cloudflare'

# Basic usage
require 'cloudflare'
cf = Cloudflare.connect(key: ENV['CF_API_KEY'], email: ENV['CF_EMAIL'])
zone = cf.zones.find_by_name('yourdomain.com')

# Purge entire cache
zone.purge_cache

# Purge specific URLs
zone.purge_cache(files: [
  'https://yourdomain.com/about/',
  'https://yourdomain.com/css/main.css'
])

For Jekyll-specific integration, create a custom gem or Rake task:

# lib/jekyll/cloudflare_purger.rb
module Jekyll
  class CloudflarePurger
    def initialize(site)
      @site = site
      @changed_files = detect_changed_files
    end
    
    def purge!
      return if @changed_files.empty?
      
      require 'cloudflare'
      cf = Cloudflare.connect(
        key: ENV['CLOUDFLARE_API_KEY'],
        email: ENV['CLOUDFLARE_EMAIL']
      )
      
      zone = cf.zones.find_by_name(@site.config['url'])
      urls = @changed_files.map { |f| File.join(@site.config['url'], f) }
      
      zone.purge_cache(files: urls)
      puts "Purged #{urls.count} URLs from Cloudflare cache"
    end
    
    private
    
    def detect_changed_files
      # Compare current build with previous build
      # Implement git diff or file mtime comparison
    end
  end
end

# Hook into Jekyll build process
Jekyll::Hooks.register :site, :post_write do |site|
  CloudflarePurger.new(site).purge! if ENV['PURGE_CLOUDFLARE_CACHE']
end

Implementing Selective Cache Purging

Selective purging is more efficient than purging everything. Implement a smart purging system:

1. Git-Based Change Detection

Use git to detect what changed between builds:

def changed_files_since_last_build
  # Get commit hash of last successful build
  last_build_commit = File.read('.last_build_commit') rescue nil
  
  if last_build_commit
    `git diff --name-only #{last_build_commit} HEAD`.split("\n")
  else
    # First build, assume everything changed
    `git ls-files`.split("\n")
  end
end

# Save current commit after successful build
File.write('.last_build_commit', `git rev-parse HEAD`.strip)

2. File Type Based Purging Rules

Different file types need different purging strategies:

def purge_strategy_for_file(file)
  case File.extname(file)
  when '.css', '.js'
    # CSS/JS changes affect all pages
    :purge_all_pages
  when '.html', '.md'
    # HTML changes affect specific pages
    :purge_specific_page
  when '.yml', '.yaml'
    # Config changes might affect many pages
    :purge_related_pages
  else
    :purge_specific_file
  end
end

3. Dependency Tracking

Track which pages depend on which assets:

# _data/asset_dependencies.yml
about.md:
  - /css/layout.css
  - /js/navigation.js
  - /images/hero.jpg

blog/index.html:
  - /css/blog.css
  - /js/comments.js
  - /_posts/*.md

When an asset changes, purge all pages that depend on it.

Cache Warming Techniques for Better Performance

Purging cache creates a performance penalty for the next visitor. Implement cache warming:

  1. Pre-warm Critical Pages: After purging, automatically visit key pages to cache them.
  2. Staggered Purging: Purge non-critical pages at off-peak hours.
  3. Edge Cache Preloading: Use Cloudflare's Cache Reserve or Tiered Cache features.

Implementation with Ruby:

def warm_cache(urls)
  require 'net/http'
  require 'uri'
  
  threads = []
  urls.each do |url|
    threads   Thread.new do
      uri = URI.parse(url)
      Net::HTTP.get_response(uri)
      puts "Warmed: #{url}"
    end
  end
  
  threads.each(&:join)
end

# Warm top 10 pages after purge
top_pages = get_top_pages_from_analytics(limit: 10)
warm_cache(top_pages)

Monitoring Cache Efficiency with Analytics

Use Cloudflare Analytics to monitor cache performance:

# Fetch cache analytics via API
def cache_hit_ratio
  require 'cloudflare'
  cf = Cloudflare.connect(key: ENV['CF_API_KEY'], email: ENV['CF_EMAIL'])
  
  data = cf.analytics.dashboard(
    zone_id: ENV['CF_ZONE_ID'],
    since: '-43200', # Last 12 hours
    until: '0',
    continuous: true
  )
  
  {
    hit_ratio: data['totals']['requests']['cached'].to_f / data['totals']['requests']['all'],
    bandwidth_saved: data['totals']['bandwidth']['cached'],
    origin_requests: data['totals']['requests']['uncached']
  }
end

Ideal cache hit ratio for Jekyll sites: 90%+. Lower ratios indicate cache configuration issues.

Advanced Cache Scenarios and Solutions

1. A/B Testing with Cache Variants

Serve different content variants with proper caching:

# Use Cloudflare Workers to vary cache by cookie
addEventListener('fetch', event => {
  const cookie = event.request.headers.get('Cookie')
  const variant = cookie.includes('variant=b') ? 'b' : 'a'
  
  // Cache separately for each variant
  const cacheKey = `${event.request.url}?variant=${variant}`
  event.respondWith(handleRequest(event.request, cacheKey))
})

2. Stale-While-Revalidate Pattern

Serve stale content while updating in background:

# Configure in Cloudflare dashboard or via API
cf.zones.settings.cache_level.edit(
  zone_id: zone.id,
  value: 'aggressive'  # Enables stale-while-revalidate
)

3. Cache Tagging for Complex Sites

Tag content for granular purging:

# Add cache tags via HTTP headers
response.headers['Cache-Tag'] = 'post-123,category-tech,author-john'

# Purge by tag
cf.zones.purge_cache.tags(
  zone_id: zone.id,
  tags: ['post-123', 'category-tech']
)

Complete Automated Workflow Example

Here's a complete Rakefile implementation:

# Rakefile
require 'cloudflare'

namespace :cloudflare do
  desc "Purge cache for changed files"
  task :purge_changed do
    require 'jekyll'
    
    # Initialize Jekyll
    site = Jekyll::Site.new(Jekyll.configuration)
    site.process
    
    # Detect changed files
    changed_files = `git diff --name-only HEAD~1 HEAD 2>/dev/null`.split("\n")
    changed_files = site.static_files.map(&:relative_path) if changed_files.empty?
    
    # Filter to relevant files
    relevant_files = changed_files.select do |file|
      file.match?(/\.(html|css|js|xml|json|md)$/i) ||
      file.match?(/^_(posts|pages|drafts)/)
    end
    
    # Generate URLs to purge
    urls = relevant_files.map do |file|
      # Convert file paths to URLs
      url_path = file
        .gsub(/^_site\//, '')
        .gsub(/\.md$/, '')
        .gsub(/index\.html$/, '')
        .gsub(/\.html$/, '/')
      
      "#{site.config['url']}/#{url_path}"
    end.uniq
    
    # Purge via Cloudflare API
    if ENV['CLOUDFLARE_API_KEY'] && !urls.empty?
      cf = Cloudflare.connect(
        key: ENV['CLOUDFLARE_API_KEY'],
        email: ENV['CLOUDFLARE_EMAIL']
      )
      
      zone = cf.zones.find_by_name(site.config['url'].gsub(/https?:\/\//, ''))
      
      begin
        zone.purge_cache(files: urls)
        puts "✅ Purged #{urls.count} URLs from Cloudflare cache"
        
        # Log the purge
        File.open('_data/cache_purges.yml', 'a') do |f|
          f.write({
            'timestamp' => Time.now.iso8601,
            'urls' => urls,
            'count' => urls.count
          }.to_yaml.gsub(/^---\n/, ''))
        end
      rescue => e
        puts "❌ Cache purge failed: #{e.message}"
      end
    end
  end
  
  desc "Warm cache for top pages"
  task :warm_cache do
    require 'net/http'
    require 'uri'
    
    # Get top pages from analytics or sitemap
    top_pages = [
      '/',
      '/blog/',
      '/about/',
      '/contact/'
    ]
    
    puts "Warming cache for #{top_pages.count} pages..."
    
    top_pages.each do |path|
      url = URI.parse("https://yourdomain.com#{path}")
      
      Thread.new do
        3.times do |i|  # Hit each page 3 times for different cache layers
          Net::HTTP.get_response(url)
          sleep 0.5
        end
        puts "  Warmed: #{path}"
      end
    end
    
    # Wait for all threads
    Thread.list.each { |t| t.join if t != Thread.current }
  end
end

# Deployment task that combines everything
task :deploy do
  puts "Building site..."
  system("jekyll build")
  
  puts "Purging Cloudflare cache..."
  Rake::Task['cloudflare:purge_changed'].invoke
  
  puts "Deploying to GitHub..."
  system("git add . && git commit -m 'Deploy' && git push")
  
  puts "Warming cache..."
  Rake::Task['cloudflare:warm_cache'].invoke
  
  puts "✅ Deployment complete!"
end

Stop fighting cache issues manually. Implement the basic purge automation this week. Start with the simple Rake task, then gradually add smarter detection and warming features. Your visitors will see updates instantly, and you'll save hours of manual cache management each month.