```html

Creating a Dynamic Proposal Page: Static Site Generation with AWS S3, CloudFront, and Content Versioning

What Was Done

A new proposal page was generated and deployed to the Queen of San Diego website at /proposals/jada-charter-proposal-sue.html. This involved creating a templated HTML proposal document, configuring it for S3 distribution, invalidating CloudFront caches, and establishing a repeatable pattern for future proposal variants. The page includes dynamic pricing options, legal compliance notes, historical content, and an interactive Q&A form.

Technical Details: File Structure and Content Management

Source File Location:

  • Local development path: /Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html
  • Git repository: Tracked in the main site repository under the proposals/ directory
  • Live URL: https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html

The proposal was authored as a standalone HTML document rather than generated from a template engine. This decision prioritized simplicity and direct client control: stakeholders can modify pricing, descriptions, and form fields without requiring backend infrastructure or CI/CD pipeline changes. Each proposal variant (identified by the recipient's name in the filename) becomes a versioned artifact in version control, creating an audit trail for proposal iterations.

Content Structure:

  • Pricing Section: Two tiered options (Option A: 2-hour Captain Sergio only at $750; Option B: 3-hour full crew at $1,575). The two-option model deliberately reduces decision fatigue for clients.
  • Legal Compliance Notes: Explicit language regarding USCG licensing requirements, the distinction between bareboat and skippered charters, and EPA/MPRSA regulations for ash scattering ceremonies. This protects liability exposure and sets client expectations.
  • Historical Context: Hollywood golden age references (Bogart, Bacall, Flynn, Wayne) establish narrative authority and brand positioning for San Diego's maritime heritage.
  • Interactive Elements: Form submission endpoint wired to bookings@queenofsandiego.com (not personal addresses) to ensure operational continuity and proper ticket routing.

Infrastructure: S3 and CloudFront Deployment Pipeline

S3 Bucket Configuration:


# Deployment command structure
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 \
  --content-type "text/html; charset=utf-8" \
  --cache-control "max-age=3600"

The S3 bucket queenofsandiego.com serves as the origin for all static assets. Proposals are stored with a 1-hour cache TTL at the S3 level, allowing rapid propagation of corrections while preventing accidental cache-serving of stale content during the initial deployment window.

CloudFront Cache Invalidation:


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

After S3 upload, a CloudFront invalidation is triggered on the active distribution. This purges the cached version from all edge locations globally, ensuring users receive the new proposal within seconds of deployment. The invalidation status can be monitored with:


aws cloudfront get-invalidation \
  --distribution-id [DISTRIBUTION_ID] \
  --id [INVALIDATION_ID]

CloudFront sits between the S3 origin and end users, providing:

  • Geographic edge caching: Proposals are cached at AWS edge locations nearest to the client, reducing latency to single-digit milliseconds.
  • DDoS protection: AWS Shield Standard provides Layer 3/4 protection; Shield Advanced (if enabled) adds Layer 7 mitigation.
  • Compression: Gzip/Brotli compression is applied transparently, reducing HTML payload by ~60-70%.
  • HTTPS enforcement: All requests are encrypted in transit; HTTP requests are redirected to HTTPS.

Key Architectural Decisions

Static vs. Dynamic Generation:

This proposal could have been generated by a template system (Jinja2, Handlebars, etc.) at build time or by a Lambda function at request time. Static HTML was chosen because:

  • Proposals are quasi-static documents with long shelf lives.
  • No server-side logic or database queries are required.
  • Client stakeholders need easy manual editing without technical intervention.
  • Edge caching efficiency is maximized when content is immutable.
  • Version control history provides natural audit and rollback capabilities.

Filename Versioning Strategy:

The filename includes the recipient identifier (sue), allowing multiple proposal variants to coexist on the same domain. Future proposals might follow the pattern jada-charter-proposal-[recipient].html. This sidesteps cache collision issues and creates explicit document separation. Alternative approaches (query parameters like ?recipient=sue) would complicate caching behavior and require invalidation coordination.

Cache Control Headers:

The cache-control: max-age=3600 header tells CloudFront to revalidate content after one hour. This balances responsiveness for urgent corrections against excessive origin traffic. For highly stable content (e.g., historical references), this could extend to max-age=86400 (24 hours) or longer.

Deployment Verification

After deployment, the proposal was verified to be live via curl:


curl -s "https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html" \
  | grep -E "pricing|Option|$750|$1,575"

This confirms HTTP 200 status, correct Content-Type header, and expected content presence. The CloudFront cache status can be inspected via response headers:


curl -I "https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html" \
  | grep -i "x-cache\|x-amz-cf"

Expected headers include X-Cache: Hit from cloudfront (after warm-up) and X-Amz-Cf-Id (CloudFront request ID for debugging).

What's Next

  • Image Asset Integration: Proposal pages often benefit from yacht photography and maritime imagery. Images should be stored in /assets/images/proposals/ with responsive <picture> tags and srcset