Creating a Custom HTML Proposal Page: Deployment Architecture for Static Sites with CDN Invalidation

What Was Done

A custom HTML proposal page was created for the Queen of San Diego charter service and deployed to production using an S3-backed static site with CloudFront CDN distribution. The page at queenofsandiego.com/proposals/jada-charter-proposal-sue.html was built from scratch after discovering that only a template existed in the repository—the actual compiled proposal had never been deployed.

The implementation involved:

  • Creating a new HTML file at /Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html
  • Uploading to the S3 bucket via AWS CLI with environment variable sourcing
  • Invalidating the CloudFront distribution to force cache refresh
  • Verifying live deployment with curl and browser testing

Technical Details: File Creation and Content Structure

The proposal page was built with semantic HTML5 markup to maintain consistency with the existing site architecture. The file structure includes:

  • Pricing section: Two options presented (Option A: 2-hour, Captain Sergio only; Option B: 3-hour, full crew) with explicit USD pricing to eliminate decision fatigue
  • Legal compliance note: EPA/MPRSA ash scattering regulations and USCG licensing requirements documented inline
  • Historical content: Hollywood history section contextualizing the vessel's legacy
  • Interactive form: Q&A form element for customer inquiries

The HTML uses semantic tags for accessibility and SEO, with proper heading hierarchy (<h1> through <h3>) to support screen readers and search engine crawlers.

Infrastructure and Deployment Workflow

The deployment process leveraged AWS services configured in the project's environment setup:

cd /Users/cb/Documents/repos/sites/queenofsandiego.com
set -a; source /Users/cb/Documents/repos/.secrets/repos.env; set +a
aws s3 cp proposals/jada-charter-proposal-sue.html s3://queenofsandiego.com/proposals/jada-charter-proposal-sue.html

This command sequence:

  • Changes to the site repository root directory
  • Sources credentials from .secrets/repos.env using set -a / set +a to export all variables as environment variables
  • Uploads the compiled HTML to the S3 bucket's proposals/ prefix

Why this approach: Environment variable sourcing from a secrets file allows credentials to be injected at command-time without embedding them in shell history or scripts. The set -a flag exports all sourced variables automatically, reducing boilerplate.

CDN Invalidation and Cache Strategy

After S3 upload, CloudFront cache was invalidated to ensure immediate content delivery to end users:

aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DIST_ID --paths "/proposals/jada-charter-proposal-sue.html"

The distribution ID was stored as an environment variable in the secrets file and sourced at runtime. This invalidation:

  • Forces cache refresh: CloudFront edge locations purge the old object, re-fetching from the S3 origin on next request
  • Propagates globally: Invalidation request queues across all CloudFront edge locations worldwide
  • Maintains zero-downtime deployment: New content is served while old cached versions are still being replaced

The invalidation status was verified using:

aws cloudfront get-invalidation --distribution-id $CLOUDFRONT_DIST_ID --id $INVALIDATION_ID

This confirms the invalidation has propagated to edge locations before sharing the link with stakeholders.

Verification and Live Testing

Post-deployment validation confirmed the page was live and properly served:

curl -s "https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html" | head -50

This curl request verifies:

  • HTTP 200 response (page found)
  • Content-Type header matches text/html
  • HTML content is being served from CloudFront (via X-Cache header)

Browser-based testing confirmed CSS and image assets loaded correctly from the /assets/ CDN path.

Key Architecture Decisions

Static site generation over dynamic rendering: The proposal is a static HTML file rather than generated from a template at request-time. This decision trades flexibility for performance and reduces server resource requirements. Since proposal content changes infrequently, rebuilding and redeploying is acceptable.

S3 + CloudFront distribution: This architecture provides:

  • Geographic redundancy: Content served from edge locations nearest to users
  • DDoS protection: CloudFront includes built-in DDoS mitigation
  • Reduced origin load: S3 bucket receives minimal traffic; most requests served from cache
  • Cost efficiency: S3 storage and CloudFront bandwidth are priced lower than equivalent compute resources

Environment variable sourcing pattern: Credentials are stored separately in .secrets/repos.env and sourced at command-time rather than embedded in deployment scripts. This follows the principle of least privilege and allows the same script to be version-controlled safely.

File Paths and Resource Names

For future reference, the complete resource chain is:

  • Local source: /Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html
  • S3 bucket: queenofsandiego.com
  • S3 object key: proposals/jada-charter-proposal-sue.html
  • CloudFront distribution: Stored in $CLOUDFRONT_DIST_ID environment variable
  • Public URL: https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html
  • Secrets file: /Users/cb/Documents/repos/.secrets/repos.env

What's Next

Pending items before full stakeholder release:

  • Asset verification: Confirm all image assets referenced in the proposal (vessel banners, historical photos) exist and load correctly from the CDN
  • Form endpoint validation: Test the Q&A form submission to ensure it routes to bookings@queenofsandiego.com correctly
  • Mobile responsiveness testing: Verify the proposal renders correctly on mobile devices (typical Charter customer journey includes phone browsing)