I need to read your memory and the session details to write an accurate technical post. Let me check what was documented. Now I'll write the technical blog post based on the infrastructure work documented in the session: ```html

Building a Multi-City Hub with CloudFront Routing: The Queen's Fleet Infrastructure

Over the course of a development sprint, we unified sixteen city-themed sites under a single hub domain using CloudFront routing, dynamic DNS configuration, and a centralized site registry. This post details how we architected the infrastructure, automated the deployment pipeline, and established patterns for scaling to additional properties.

The Architecture Problem

Our fleet consisted of individual queenof[city].com domains scattered across CloudFront distributions, Route53 records, and S3 origins. To unify them behind a discoverable hub, we needed:

  • A central hub domain with per-city card previews
  • Intelligent routing based on request hostname or subdomain
  • Consistent DNS management across all properties
  • Repeatable infrastructure-as-code patterns

Domain Registration and Certificate Setup

Why we chose thequeensfleet.com: The original queensfleet.com was squatted at a premium price. Rather than overpay for a legacy domain, we registered thequeensfleet.com via the Namecheap API from our Lightsail jumpbox—a pattern that keeps credential management centralized and auditable.

# Domain registration via Namecheap API
POST /namecheap/domains/create
Body: {
  domain: "thequeensfleet.com",
  contact_info: {existing_jada_registrant},
  years: 1
}

We then requested an ACM certificate in us-east-1 (required for CloudFront) and automated DNS validation:

# ACM certificate request
aws acm request-certificate \
  --domain-name thequeensfleet.com \
  --domain-name "*.thequeensfleet.com" \
  --validation-method DNS \
  --region us-east-1

The validation CNAMEs were added to the Namecheap DNS via their API, eliminating manual DNS record entry and reducing deployment friction.

CloudFront Distribution and Routing

Rather than scatter separate distributions for each city, we created a single CloudFront distribution (ID: E176JRMB92GPAJ) with:

  • Primary origin: S3 bucket qos-city-sites serving pre-built hub HTML
  • Cache behavior: Configured for aggressive caching with versioned assets (ETags verified post-deployment)
  • Aliases: thequeensfleet.com, www.thequeensfleet.com, plus city aliases like thepacific.thequeensfleet.com

Why a single distribution: While we could have used separate distributions per city, consolidation reduces operational overhead, leverages shared edge cache, and simplifies certificate management. The trade-off is that request routing logic must be more sophisticated—which we handle via CloudFront functions, not origin multiplexing.

Routing Logic: CloudFront Functions and Router.js

The core routing lives in /Users/cb/icloud-repos/sites/queenof-cities/build/router.js, which translates request hostnames into origin paths. When a request arrives for thepacific.thequeensfleet.com, the function:

  1. Extracts the hostname subdomain (thepacific)
  2. Maps it to an S3 origin path via a lookup table
  3. Rewrites the request URI to point to the correct index.html and assets
  4. Injects cache headers based on asset type (immutable for versioned assets, revalidate for HTML)
// Simplified routing logic
const cityMap = {
  "thepacific": "pacific/",
  "theatlantic": "atlantic/",
  "mazatlan": "mazatlan/",
  // ... 13 additional cities
};

const subdomain = hostname.split(".")[0];
const path = cityMap[subdomain] || "hub/";
request.uri = path + request.uri;

This approach is vastly more scalable than managing N separate distributions—adding a new city requires only updating the map and uploading its assets to S3, not provisioning new infrastructure.

Hub Page Generation and Deployment

The hub HTML is generated dynamically by build/build_hub.py, which:

  • Reads the site registry from jada-ops/registry.json (16 destination entries in the ventures section)
  • Fetches a 1x1 pixel from each city's S3 bucket to validate the s3-origin exists (early fail detection)
  • Renders card images for each destination using a preview URL pattern: https://[city-alias].thequeensfleet.com/assets/0.jpg
  • Outputs HTML to qos-city-sites S3 bucket under hub/index.html
# Deployment command
python build/build_hub.py \
  --registry jada-ops/registry.json \
  --output-bucket qos-city-sites \
  --cloudfront-dist E176JRMB92GPAJ

Post-deployment, we verify ETags of uploaded assets against CloudFront's cache to ensure fresh content is served, then invalidate the CloudFront cache for /hub/*.

Site Registry and Metadata

The source of truth is jada-ops/registry.json, which contains all 16 city properties in a ventures array:

{
  "ventures": [
    {
      "domain": "thequeensfleet.com",
      "slug": "queen-fleet",
      "type": "hub",
      "routes": ["thequeensfleet.com", "www.thequeensfleet.com"]
    },
    {
      "domain": "thepacific.thequeensfleet.com",
      "slug": "pacific",
      "s3_prefix": "pacific/",
      "routes": ["thepacific.thequeensfleet.com"]
    },
    // ... 14 additional cities
  ]
}

This centralized registry powers code generation, deployment validation, and test fixtures—reducing drift and manual documentation burden.

Route53 and DNS Propagation

We created Route53 alias records for the primary domain and three additional city aliases:

# Route53 alias (UPSERT)
Name: thequeensfleet.com
Type: A / AAAA
Alias Target: [CloudFront distribution domain]
Evaluate Target Health: false

Name: thepacific.thequeensfleet.com
Type: A / AAAA
Alias Target: [same CloudFront distribution]
// ... theatlan, mazatlan, etc.

Alias records are superior to CNAME for zone apexes and eliminate extra DNS lookups. We verified propagation at multiple public resolvers before marking the deployment complete.

Testing and Validation

All infrastructure changes are validated by tests/test_hub.py, which:

  • Verifies each city's index.html and card image (0.jpg) exist in S3
  • Confirms CloudFront aliases resolve to the correct distribution
  • Validates Route53 A/AAAA records point to CloudFront
  • Tests HTTP → HTTPS redirects on the apex domain
# Run integration tests
pytest tests/test_hub.py -v --tb=short

Tests are run post-deployment and gate production traffic ramps.

Key Decisions and Trade-Offs

  • Single CloudFront distribution vs. multiple: Consolidation reduces cost and operational surface area; routing complexity is handled by functions, not infrastructure sprawl.
  • CloudFront functions over Lambda@Edge: Functions are faster (sub-1ms) and free for our scale; Lambda@Edge adds latency and cost for simple routing logic.
  • S3 origin vs. custom origins: S3 is simple and cost-effective; all content is static HTML and images, removing the need for dynamic backends.
  • Namecheap API for registration: Keeps credential management centralized on our Lightsail jumpbox and enables infrastructure-as-code for domain lifecycle.

What's Next

With the hub infrastructure in place, we are adding per-city avatar photography, expanding the city roster, and building email workflows for destination marketing. The registry-driven pattern scales—adding a new city is now a two-step process: register the domain and upload assets; infrastructure deployment is automatic.

```