Cloudflare Redirect Migration Plan

This document outlines the strategy for migrating redirects from Rack::Rewrite (Rails middleware) to Cloudflare edge.

Current State

File: config/initializers/260_redirections.rb

Metric Count
Total r301 redirects 883
Lambda-based (dynamic) 3
Country-based (geo) 4
Host-based (subdomain) 14
Scheme-based (http→https) 4
Rewrite rules 5

Benefits of Edge Redirects

  1. Performance - Redirects happen at Cloudflare edge (< 10ms) vs origin (100-300ms)
  2. Reduced Origin Load - Redirect requests never hit Rails
  3. Better SEO - Faster redirects improve crawler efficiency
  4. Edge Caching - Redirects can be cached at edge
  5. Simplified Middleware - Less Rack middleware to process

Migration Categories

Category 1: Bulk Redirects (Simple Static)

~850 redirects → Cloudflare Bulk Redirects

These are simple path-to-path mappings with no dynamic logic:

r301 %r{/old-path}i, '/new-path'
r301 %r{/blog_posts}i, '/posts'

Cloudflare Feature: Bulk Redirects

  • Up to 50,000 redirects per list
  • O(1) lookup performance
  • No expression evaluation cost

Format: CSV/JSON with source → target mappings

Category 2: Dynamic Redirect Rules

~20 redirects → Cloudflare Redirect Rules

Pattern-based redirects with capture groups:

r301 %r{/products/line/towel-warmer-(studio|sierra|barcelona)}, '/products/line/towel-warmer-classic-$1'

Cloudflare Feature: Dynamic Redirect Rules

  • Uses expression language
  • Supports regex-like patterns via concat() and regex_replace()
  • 125 rules per zone (Business plan)

Category 3: Geo-Based Redirects

4 redirects → Cloudflare Redirect Rules with ip.geoip.country

Country-conditional redirects:

r301(/(.*)/i, '/en-CA$1', host: TLD, country: 'CA')
r301(/(.*)/i, '/en-US$1', host: TLD)

Cloudflare Feature: Redirect Rules with geo conditions

(http.host eq "warmlyyours.com" and ip.geoip.country eq "CA")
 concat("/en-CA", http.request.uri.path)

Category 4: Host/Subdomain Redirects

14 redirects → Cloudflare Redirect Rules

Subdomain routing:

r301 %r{/(.*)}i, 'https://www.warmlyyours.com/en-US/$1', host: "scan.warmlyyours.com"

Cloudflare Feature: Redirect Rules with host matching

(http.host eq "scan.warmlyyours.com")
→ https://www.warmlyyours.com/en-US/${http.request.uri.path}

Category 5: Must Stay in Rack (Complex Logic)

~5 rules - Keep in Rails

Lambda-based logic that requires server-side processing:

r301 %r{/{2,}}, lambda { |_match, rack_env|
  # Complex double-slash fixing logic
}

These include:

  • Double-slash normalization (lines 32-44)
  • Legacy image URL translation (lines 52-55)
  • Complex locale validation

Implementation Plan

Phase 1: Preparation (Week 1)

  1. Export Current Redirects

    rake cloudflare:rules:export_all
    
  2. Generate Redirect Inventory
    Create a rake task to parse 260_redirections.rb and categorize redirects:

    rake redirects:analyze
    rake redirects:export_bulk_csv
    rake redirects:export_dynamic_rules
    
  3. Set Up Bulk Redirect Lists

    • Create www-static-redirects list in Cloudflare
    • Import CSV with static mappings

Phase 2: Geo Redirects (Week 2)

Priority: These currently check geo on every request in Rails.

  1. Create Redirect Rules for TLD geo routing:

    Rule 1: CA visitors on TLD → /en-CA
    Expression: (http.host eq "warmlyyours.com" and ip.geoip.country eq "CA" and not starts_with(http.request.uri.path, "/en-"))
    Target: concat("https://www.warmlyyours.com/en-CA", http.request.uri.path)
    
    Rule 2: All other TLD visitors → /en-US
    Expression: (http.host eq "warmlyyours.com" and not starts_with(http.request.uri.path, "/en-"))
    Target: concat("https://www.warmlyyours.com/en-US", http.request.uri.path)
    
  2. Deploy to Staging First

    # Apply rules to warmlyyours.ws
    rake cloudflare:redirects:deploy[staging]
    
  3. Test with curl:

    # Test CA geo (use Cloudflare header override or VPN)
    curl -sI "https://warmlyyours.ws/floor-heating" -H "CF-IPCountry: CA"
    # Expected: 301 → /en-CA/floor-heating
    
    curl -sI "https://warmlyyours.ws/floor-heating" -H "CF-IPCountry: US"
    # Expected: 301 → /en-US/floor-heating
    

Phase 3: Host Redirects (Week 2)

  1. Create Redirect Rules for subdomain routing:

    Rule: scan.* → www with locale
    Expression: (http.host contains "scan.")
    Target: https://www.warmlyyours.com/en-US${http.request.uri.path}
    
  2. Test scan.warmlyyours.com routing

Phase 4: Bulk Static Redirects (Week 3)

  1. Generate CSV from Ruby:

    # lib/tasks/redirects.rake
    desc "Export static redirects as CSV for Cloudflare Bulk Redirects"
    task export_bulk_csv: :environment do
      # Parse 260_redirections.rb
      # Filter simple path→path redirects
      # Output: data/cloudflare_rules/bulk_redirects.csv
    end
    
  2. Upload to Cloudflare:

    • Dashboard: Account → Bulk Redirects → Create list
    • Or via API:
    curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account_id}/rules/lists" \
      -H "Authorization: Bearer $TOKEN" \
      -d '{"name":"www-static-redirects","kind":"redirect"}'
    
  3. Create Bulk Redirect Rule:

    Rule: Enable www-static-redirects list
    Expression: (http.host eq "www.warmlyyours.com")
    

Phase 5: Remove Rack Redirects (Week 4)

  1. Update 260_redirections.rb:

    • Comment out migrated redirects
    • Add header noting Cloudflare handles them
    • Keep only Category 5 (complex logic)
  2. Verify in Production:

    # Test sample redirects
    curl -sI https://www.warmlyyours.com/blog_posts | grep -i location
    # Expected: 301 to /posts (served by Cloudflare, not origin)
    
  3. Monitor for 30 days:

    • Check Cloudflare analytics for redirect traffic
    • Verify no 404s for migrated paths
    • Compare response times

Cloudflare Rule Limits

Plan Redirect Rules Bulk Redirects
Free 10 20
Pro 25 500
Business 125 5,000
Enterprise 500+ 50,000

Our Plan: Business (warmlyyours.com)

Rollback Plan

  1. Immediate: Disable Cloudflare Redirect Rules via Dashboard
  2. Rails fallback: The Rack::Rewrite rules remain in code (commented)
  3. Re-enable: Uncomment Rack::Rewrite rules in 260_redirections.rb

Files to Create

File Purpose
lib/tasks/redirects.rake Analyze & export redirects
data/cloudflare_rules/bulk_redirects.csv Static redirect mappings
data/cloudflare_rules/redirect_rules.yml Dynamic rule definitions
app/services/cloudflare_redirect_service.rb API for managing redirects

Testing Strategy

Before Migration

# Capture current behavior
curl -sI https://www.warmlyyours.com/blog_posts > before_blog.txt
curl -sI https://www.warmlyyours.com/towel-warmers > before_towel.txt
# ... for each major redirect category

After Migration

# Compare responses
diff before_blog.txt after_blog.txt
# Verify same redirect target, but via Cloudflare

Headers to Check

  • CF-Ray - Confirms Cloudflare processed request
  • X-Redirect-By: Cloudflare - (if we add custom header)
  • Server: cloudflare - Cloudflare served response

Metrics to Track

  1. Redirect Latency - Should drop from ~200ms to ~10ms
  2. Origin Requests - Should decrease for redirect paths
  3. Error Rate - Monitor for unexpected 404s
  4. Cache Hit Ratio - Redirects should be cached

References