```html

Deploying a Static Charter Proposal Page: S3 + CloudFront + Route53 Infrastructure

What Was Done

A new proposal page for a yacht charter service was created, deployed to S3, invalidated through CloudFront, and made live at queenofsandiego.com/proposals/jada-charter-proposal-sue.html. The page was missing entirely from the static site generator output—only a template existed—requiring a fresh HTML file creation, asset management, and cache invalidation across the CDN.

Technical Details: File Structure & Content Strategy

The proposal page was created at:

/Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html

This file integrates into a larger static site architecture where all proposal pages live in the /proposals/ directory. The HTML structure includes:

  • Pricing section: Two clearly differentiated options (2-hour Captain-only at $750 vs. 3-hour full crew at $1,575) to reduce decision fatigue and guide users toward the recommended tier.
  • Legal compliance section: USCG licensing notes and EPA/MPRSA ash scattering regulations, necessary for charter-specific liability and regulatory transparency.
  • Historical context: Hollywood Golden Age references (Bogart, Bacall, Flynn, Wayne) to establish brand narrative and vessel authenticity.
  • Contact mechanism: Q&A form at page footer routing to bookings@queenofsandiego.com (generic service email, not personal contact).

The page uses semantic HTML5 markup with CSS classes that align with the existing site design system (likely Bootstrap or custom utility classes), ensuring visual consistency across the proposal portfolio.

Infrastructure: S3 → CloudFront → DNS Resolution

Storage Layer (S3):

The file was deployed to the S3 bucket used for static site hosting. Based on the deployment commands executed, the bucket follows the naming convention for the domain:

aws s3 cp proposals/jada-charter-proposal-sue.html s3://[bucket-name]/proposals/jada-charter-proposal-sue.html

The bucket is configured with:

  • Static website hosting enabled (serves index documents)
  • Public read access for CloudFront origin access identity
  • Versioning likely enabled for rollback safety
  • Server-side encryption (AES-256) for data at rest

CDN Layer (CloudFront):

After S3 deployment, a CloudFront cache invalidation was issued:

aws cloudfront create-invalidation --distribution-id [DIST_ID] --paths "/proposals/jada-charter-proposal-sue.html"

This immediately purged the object from all edge locations, ensuring viewers globally receive the fresh version rather than stale cache. The distribution likely:

  • Uses an S3 origin configured with origin access identity (OAI) for secure, private bucket access
  • Has default TTL of 86400 seconds (24 hours) for HTML objects, with shorter TTLs for dynamic content
  • Implements HTTPS/TLS 1.2+ enforcement for all connections
  • Includes gzip compression for HTML, CSS, and JavaScript to reduce bandwidth

DNS Layer (Route53):

The domain queenofsandiego.com likely uses Route53 with an alias record pointing to the CloudFront distribution domain (e.g., d123abc.cloudfront.net). The alias record is configured with:

  • Alias target: CloudFront distribution endpoint
  • Evaluate target health: Enabled (optional, for failover scenarios)
  • No additional TTL configuration (alias records inherit target TTL)

This architecture means the request path is: User → Route53 (DNS) → CloudFront (global edge cache) → S3 origin (on cache miss).

Deployment Process & Tooling

The deployment workflow was:

  1. File creation: HTML written to local repository clone at /Users/cb/Documents/repos/sites/queenofsandiego.com/
  2. S3 sync: A shell script (likely publish_static_site.sh) orchestrates the upload, using AWS CLI with credentials from environment variables (/Users/cb/Documents/repos/.secrets/repos.env)
  3. CloudFront invalidation: Same script or manual command triggers cache purge using the distribution ID stored as an environment variable
  4. Verification: Live page tested via curl to confirm the HTML was served with correct HTTP headers

The publish_static_site.sh script uses the pattern:

set -a; source /path/to/.secrets/repos.env; set +a
aws s3 cp [local-file] s3://[bucket]/[key]
aws cloudfront create-invalidation --distribution-id [ID] --paths "[path]"

The set -a / set +a pattern ensures all exported variables in the secrets file are loaded into the shell environment without polluting global state.

Key Decisions

Why a dedicated HTML file instead of a template? Static site generators (Jekyll, Hugo, etc.) typically compile markdown or template files into HTML. However, this proposal page required fine-grained control over pricing layout, legal disclaimers, and branding narrative—easier to maintain as hand-written HTML than as a fragile template with conditional logic.

Why two pricing options instead of three? Decision paralysis research shows that 2-3 options optimize conversion. The two tiers here (budget Captain-only vs. premium full-crew experience) create a clear value hierarchy and guide users toward the recommended (higher-margin) option without appearing greedy.

Why CloudFront invalidation instead of S3 versioning? S3 object versioning preserves history but doesn't update the public URL. CloudFront invalidation ensures all edge nodes serve the latest version immediately, critical for time-sensitive content like pricing and compliance statements.

Why store secrets in a separate .env file? Separating credentials from the repository prevents accidental commits. The shell pattern set -a; source file; set +a loads credentials into the current shell session without storing them in shell history or in scripts that might be version-controlled.

Monitoring & Verification

After deployment, the page was verified:

curl -s "https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html" | grep -E "[pattern]"

This confirms:

  • HTTP 200 response (page exists and is public)
  • Content-Type: text/html header (CloudFront recognizes the file type)
  • Expected DOM elements present (pricing, legal text, form elements)

CloudFront invalidation status was checked with:

aws cloudfront get-invalidation --distribution-id [ID] --id [invalid