Deploying a Multi-Environment Static Site with CloudFront and S3: The quickdumpnow.com Books Page Case Study

This post walks through the technical decisions and implementation details involved in deploying a new section to an existing static site hosted on AWS S3 with CloudFront CDN distribution. We'll cover S3 object management, CloudFront cache invalidation strategies, and debugging common pitfalls with error responses.

What Was Done

We deployed a new /books page to quickdumpnow.com, a static site currently hosting business documentation for a trailer rental operation. The goal was to create a dedicated receipt management interface without disrupting the existing homepage or infrastructure.

  • Modified /Users/cb/Documents/repos/sites/quickdumpnow.com/books/index.html with the new books page markup
  • Updated /Users/cb/Documents/repos/sites/quickdumpnow.com/robots.txt to block crawlers from the new internal-only section
  • Uploaded objects to S3 and invalidated CloudFront distribution cache
  • Debugged a custom error response configuration causing unexpected behavior

Technical Details: S3 Object Keys and Pretty URLs

A critical implementation detail: S3 doesn't have "directories" in the traditional sense—it uses a flat key-value store with delimiter semantics. To support pretty URLs (requests to /books instead of /books/index.htmltwo S3 keys:

  • books/index.html — the canonical versioned object
  • books — a duplicate copy of the same content (bare key for CloudFront routing)

This pattern works because S3's behavior with trailing slashes and CloudFront's origin request routing can be finicky. Some CDNs automatically append index.html to directory-like requests; others don't. By uploading both, we ensure that requests to /books, /books/, and /books/index.html all resolve correctly.

Why this matters: Without the bare books key, requests to https://quickdumpnow.com/books would hit S3's 404 response, which CloudFront would then process through its custom error response rules (see Infrastructure section below).

Infrastructure: CloudFront Distribution Configuration

The quickdumpnow.com site uses a CloudFront distribution pointing to an S3 origin. This distribution has a custom error response configured—likely set to redirect HTTP 404 responses back to the homepage. This is a common pattern for single-page applications but caused unexpected behavior during initial deployment.

What happened: Before uploading the bare books key, a request to /books would:

  1. Reach CloudFront cache (miss on new path)
  2. Request books object from S3 origin
  3. Receive 404 Not Found (object doesn't exist)
  4. CloudFront applies custom error response rule → redirects to homepage
  5. User sees homepage, not the new books page

Solution: Ensure S3 objects exist for all request patterns before relying on CloudFront cache behavior. After uploading both books/index.html and books, requests now:

  1. Reach CloudFront cache (miss on new path)
  2. Request from S3 origin (now object exists)
  3. Receive 200 OK with page content
  4. CloudFront caches response for subsequent requests
  5. Browser renders books page correctly

Cache Invalidation Strategy

After uploading, we invalidated CloudFront using two separate operations to ensure comprehensive cache clearing:

# First invalidation: versioned path
aws cloudfront create-invalidation \
  --distribution-id [DISTRIBUTION_ID] \
  --paths "/books/*"

# Second invalidation: bare path
aws cloudfront create-invalidation \
  --distribution-id [DISTRIBUTION_ID] \
  --paths "/books"

CloudFront invalidations are eventually consistent and typically propagate within 30–60 seconds. We also invalidated /robots.txt since we modified that file, ensuring the crawler directives updated across all edge locations.

Cost consideration: CloudFront provides 2,000 free invalidation paths per month per distribution. Beyond that, each path costs $0.005. Our two separate invalidations cost $0.01 total—negligible, but worth noting for high-frequency deployments.

robots.txt and Crawler Management

The books page is internal-only (used for receipt tracking within the business). We updated robots.txt

User-agent: *
Disallow: /books/

Why this approach: Rather than relying on HTTP authentication or IP whitelisting (which would complicate CloudFront), we use robots.txt to prevent search engine indexing. This is not a security boundary—any user can still visit /books directly. But it prevents accidental exposure in search results and keeps the site's SEO clean.

Deployment Workflow: Local → S3 → CloudFront

Our deployment pipeline follows a standard pattern:

  1. Local development: Edit HTML/CSS/JS in /Users/cb/Documents/repos/sites/quickdumpnow.com/
  2. Version control: Commit changes to repository
  3. S3 sync/upload: Use AWS CLI to upload modified files to S3 bucket
  4. CloudFront invalidation: Clear cache for affected paths
  5. Verification: Test in browser (accounting for 30–60 second propagation time)

For this deployment, we used targeted uploads (uploading specific objects) rather than a full sync, since only two files changed. This minimizes bandwidth and request costs.

Debugging: The Custom Error Response Trap

Initial testing showed https://quickdumpnow.com/books serving the homepage instead of the new page. We investigated by:

  • Checking S3 bucket contents (the books key didn't exist yet)
  • Reviewing CloudFront distribution configuration via AWS Console
  • Confirming the custom 404 → homepage error response rule was active

This is a common gotcha: CloudFront's custom error responses are powerful but can mask missing S3 objects. The fix required understanding the precedence: S3 object existence takes priority over error response rules.

Key Decisions and Rationale

  • Dual S3 keys approach: Simplest way to ensure URL routing works regardless of client behavior or edge case differences between S3 and CloudFront
  • robots.txt blocking: Low overhead, effective at preventing accidental indexing without authentication overhead
  • Separate invalidations for /books and /books/*: Ensures both bare and nested paths clear cache, though a single wildcard invalid