DEV Community

Cover image for Multi-Region Shopify Infrastructure: The Complete Technical Guide
Asad Abdullah Zafar
Asad Abdullah Zafar

Posted on • Originally published at kolachitech.com

Multi-Region Shopify Infrastructure: The Complete Technical Guide

Every 10,000 km between your server and your user adds ~100ms of baseline latency. A Shopify app in US-East serving Singapore adds 300-400ms before your code runs.
Here are the five architectural decisions that determine whether your Shopify system serves the world or just one AWS region.

  1. Pick the Right Topology First
    Don't start with active-active. Most teams underestimate the operational complexity.

    Topology RTO RPO When to Use
    Active-Passive 2–10 min < 30s Default: resilience without write conflicts
    Active-Active (2) < 1 min Near-zero US + EU merchant split, compliance-driven
    Active-Active (3+) Seconds Near-zero Shopify Plus global stores
    Edge-Only (Oxygen) Built-in N/A Hydrogen storefronts

    Start active-passive. Upgrade when traffic or compliance forces your hand.

  2. GeoDNS with Health Check Failover
    Route 53 latency-based routing is the cleanest solution for AWS-hosted Shopify apps. evaluate_target_health = true is the critical setting — without it, DNS failover doesn't trigger on regional failure.

hclresource "aws_route53_record" "shopify_app_eu" {
  zone_id        = aws_route53_zone.main.zone_id
  name           = "api.yourshopifyapp.com"
  type           = "A"
  set_identifier = "eu-west-1"

  latency_routing_policy {
    region = "eu-west-1"
  }

  alias {
    name                   = aws_lb.eu_west.dns_name
    zone_id                = aws_lb.eu_west.zone_id
    evaluate_target_health = true  # Fails over on health check failure
  }
}
Enter fullscreen mode Exit fullscreen mode

For Cloudflare users, geo-steering with origin pools per continent achieves the same result without Route 53.

  1. Cross-Region Webhook Deduplication Shopify doesn't know your regional topology. The same webhook hits both your US and EU endpoints simultaneously when a global load balancer fronts both regions.
js// Upstash Global Redis: replicated across all regions
const acquired = await globalRedis.set(
  `webhook:dedup:${webhookId}`,
  process.env.DEPLOY_REGION,
  { nx: true, ex: 86400 }
);

if (!acquired) {
  // Another region claimed this webhook — skip
  return { status: 'duplicate' };
}

// This region won — process the webhook
await enqueueWebhookJob(topic, shop, payload);
Enter fullscreen mode Exit fullscreen mode

SET NX is atomic. Two regions racing on the same key cannot both proceed. The 24-hour TTL handles Shopify's retry window.

  1. GDPR Data Residency Routing EU merchant data cannot touch US infrastructure for storage or processing. Assign each merchant to a region at installation:
jsasync function assignMerchantRegion(shop, shopifyShopData) {
  const EU_COUNTRIES = new Set([
    'AT','BE','BG','CY','CZ','DE','DK','EE','ES','FI',
    'FR','GR','HR','HU','IE','IT','LT','LU','LV','MT',
    'NL','PL','PT','RO','SE','SI','SK'
  ]);

  const region = EU_COUNTRIES.has(shopifyShopData.country_code)
    ? 'eu-west-1'
    : 'us-east-1';

  await db.query(
    `INSERT INTO merchant_regions (shop, region, assigned_at)
     VALUES ($1, $2, NOW()) ON CONFLICT (shop) DO NOTHING`,
    [shop, region]
  );

  return region;
}
Enter fullscreen mode Exit fullscreen mode

Every subsequent write, webhook, and API call for that merchant routes through their assigned region's infrastructure. No EU personal data crosses to US.

  1. Composite Health Check for Automated Failover A health check that only tests HTTP 200 from the load balancer misses database failures. Test the full stack:
jsapp.get('/health/regional', async (req, res) => {
  const checks = await Promise.allSettled([
    primaryPool.query('SELECT 1'),
    replicaPool.query('SELECT 1'),
    redis.ping(),
    checkQueueWorkerHealth(),
    checkShopifyAPIConnectivity(),
  ]);

  const failures = checks
    .map((c, i) => ({ name: ['primary_db','replica','redis','queue','shopify'][i], ...c }))
    .filter(c => c.status === 'rejected');

  if (failures.length > 0) {
    return res.status(503).json({
      status: 'unhealthy',
      region: process.env.DEPLOY_REGION,
      failures: failures.map(f => ({ name: f.name, error: f.reason?.message })),
    });
  }

  res.status(200).json({ status: 'healthy', region: process.env.DEPLOY_REGION });
});
Enter fullscreen mode Exit fullscreen mode

Route 53 and Cloudflare both poll this endpoint. A 503 removes the region from routing automatically. No manual DNS intervention required.

Bonus: Hydrogen on Oxygen is Already Multi-Region
Oxygen distributes Hydrogen workers across 300+ Cloudflare edge locations automatically. The only multi-region concern is cache strategy — not routing:

js// CacheLong = resolves from Workers KV at every edge location
// Prevents distant edge nodes from making origin API calls on every request
const product = await storefront.query(PRODUCT_QUERY, {
  variables: { handle: params.handle },
  cache: CacheLong(), // max-age: 1hr, SWR: 23hrs
});
Enter fullscreen mode Exit fullscreen mode

A 95%+ cache hit rate on product data means edge nodes almost never cross-traverse to Shopify's origin. Your storefront is globally fast by default.

The Multi-Region Decision Checklist

Decision Default Choice Upgrade When
Topology Active-passive Compliance or traffic forces it
DNS routing Route 53 latency-based Already on Cloudflare
Database Primary + regional replicas Writes from multiple regions
Webhook dedup Upstash Global Redis NX Always required
Data residency Shop-level region assignment Serving EU merchants
Health checks Composite (DB + Redis + queue) Always required

Full guide with Terraform configs, GDPR routing implementation, Cloudflare geo-steering setup, and 5-step regional failover runbook: https://kolachitech.com/multi-region-shopify-infrastructure

Top comments (0)