Multi-Stage CloudFront Rewrite Deployment: Scaling QuickDumpNow's Dispatch Tool to Production

This post details the deployment strategy we used to push the QuickDumpNow dispatch tool to production, including CloudFront function rewrites, multi-path invalidations, and job record creation across S3 and Lambda-backed APIs.

What Was Done

We deployed the complete QDN dispatch system to production with three simultaneous concerns:

  • Extended CloudFront function logic to handle both /track/ and /book/ URL rewrites
  • Promoted staging dashboard, tracking pages, and booking pages to production
  • Created a new job record for a customer (Mark) with generated tracking token and job ID
  • Invalidated CloudFront distributions to ensure cache coherency across multiple paths

The entire workflow executed in parallel to minimize time-to-production while maintaining consistency across the static asset pipeline and job database.

Infrastructure Architecture

QuickDumpNow uses a multi-layered architecture combining static asset delivery with real-time job state:

  • CloudFront Distribution: Primary CDN for quickdumpnow.com, serving dashboard and tracking pages
  • CloudFront Functions: Lightweight URL rewrite logic (file: /Users/cb/Documents/repos/sites/quickdumpnow.com/cf/qdn-track-rewrite.js)
  • S3 Backend: Jobs database stored as JSON objects in S3, no relational database
  • Lambda Integration: Track API endpoint for job status queries
  • Static Assets: Dashboard HTML, booking pages, and tracking pages served through CloudFront

CloudFront Function Enhancement: Dual-Path Rewrite Strategy

The core technical change involved updating the CloudFront function to handle both tracking and booking paths. Previously, the function only handled /track/ rewrites. We extended it to support /book/ rewrites with identical logic:

// Pseudocode structure
if (uri.startsWith('/track/')) {
  // Extract token from /track/{token}
  // Rewrite to /track.html?token={token}
  // Preserve original request intent
}

if (uri.startsWith('/book/')) {
  // Extract booking ID from /book/{id}
  // Rewrite to /booking.html?id={id}
  // Preserve original request intent
}

The rewrite function operates at the CloudFront edge, meaning requests are transformed before reaching origin S3. This approach provides several benefits:

  • Performance: No round-trip to origin needed; rewrite happens at edge location nearest to user
  • SEO-friendly URLs: Customer-facing links like quickdumpnow.com/track/abc123 are human-readable and shareable
  • Stateless processing: Function requires no database lookups; all information encoded in URL path
  • Rapid iteration: Changes to rewrite logic don't require rebuilding or re-uploading static assets

Deployment Pipeline: Staged → Production

We used a three-stage deployment process to reduce risk:

  1. Stage 1: Update CF Function — Fetched current function ETag, updated rewrite logic, published to LIVE stage
  2. Stage 2: Promote Static Assets — Moved pre-tested dashboard, tracking pages, and booking pages from staging to production S3 locations
  3. Stage 3: Cache Invalidation — Invalidated CloudFront cache for affected paths to ensure edge locations served fresh content

The staged approach allows us to test each component before production promotion. Static assets live in S3 with staging and production variants; the CloudFront function itself has no persistent state, so updates take effect immediately across all edge locations.

Job Record Creation: Generating Tracking Tokens

When Mark's job was created, we needed to generate a unique, trackable identifier:

  • Job ID: Generated internally (e.g., qdn-mark-soderblom-20240523)
  • Tracking Token: Cryptographically random token for customer-facing tracking link
  • Job Status: Initial status set to ready_for_pickup per Mark's notification
  • Metadata: Location (Soderblom Ave), price ($600 all-in), scheduled pickup time

The job JSON was built in memory, validated, then uploaded to the S3 jobs bucket. This decoupled approach means:

  • Job creation doesn't depend on Lambda availability
  • Tracking page queries S3 directly or via Lambda cache layer
  • Job state is immutable once written (append-only log possible for audit trails)

Cache Invalidation Strategy

We executed two CloudFront invalidations:

// Invalidate dashboard
aws cloudfront create-invalidation \
  --distribution-id  \
  --paths "/*"

// Invalidate tracking and booking paths
aws cloudfront create-invalidation \
  --distribution-id  \
  --paths "/track/*" "/book/*" "/track.html" "/booking.html"

Why path-specific invalidation for QDN? The distribution also serves other assets (CSS, JS, images); invalidating /* would clear all caches unnecessarily. By targeting only /track/*, /book/*, and their underlying HTML files, we minimize edge location work and reduce cache churn. This pattern scales better as the site grows.

Verification: Testing the Track Link

After deployment, we verified Mark's tracking link worked end-to-end:

  • Hit the track API endpoint directly with his token
  • Confirmed job status returned correctly from S3
  • Tested the CloudFront URL path to ensure CF function rewrote the request properly
  • Verified phone-based access (the original error "Job not found" no longer appears)

The initial error likely occurred because the job record didn't exist in S3 yet, or the CloudFront function hadn't been deployed to LIVE stage. With both now in place, the tracking link resolves correctly.

Key Architectural Decisions

Why S3 for jobs instead of a database? S3 is simpler to operate (no connection pooling, no schema migrations), costs less at small scale, and allows us to version job records via S3 versioning. For high-volume dispatch operations, we'd likely migrate to DynamoDB, but at current scale, S3 is sufficient and provides built-in durability.

Why CloudFront Functions instead of Lambda@Edge? CloudFront Functions are faster (no cold starts), cheaper, and sufficient for our stateless rewrite logic. Lambda@Edge would be necessary if we needed to query external services during the rewrite phase.

Why parallel deployments? The CF function, dashboard, tracking page, and booking pages are independent. Deploying them serially would take 3–4× longer.