Deploying a Dynamic Charter Proposal Page: Static Site Generation with S3, CloudFront, and DNS Routing
What Was Done
A new charter proposal landing page was created at queenofsandiego.com/proposals/jada-charter-proposal-sue.html to present sailing options for ash scattering ceremonies. The page required:
- Creating HTML markup in
/Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html - Deploying the file to AWS S3 bucket
queenofsandiego.com - Invalidating CloudFront cache for immediate CDN propagation
- Verifying live DNS resolution through Route53
The page went from non-existent (template only) to production-ready in a single deployment cycle.
Technical Details: File Structure and Content Architecture
The proposal page uses a semantic HTML5 structure optimized for both readability and SEO:
proposals/jada-charter-proposal-sue.html
├── Head section (meta, viewport, styles)
├── Navigation header
├── Hero/greeting section
├── Option A pricing tier (Captain Sergio, 2-hour)
├── Option B pricing tier (Full crew, 3-hour)
├── Legal compliance section (USCG, EPA/MPRSA regulations)
├── Historical context (Hollywood-era vessel history)
├── FAQ/contact form
└── Footer (bookings@queenofsandiego.com)
This structure deliberately minimizes decision fatigue by presenting exactly two pricing options with a clear recommendation (Option A), following proven UX patterns for high-consideration purchases.
Infrastructure: S3, CloudFront, and DNS Pipeline
S3 Storage Layer
The static HTML file is stored in the S3 bucket queenofsandiego.com. The deployment command:
aws s3 cp proposals/jada-charter-proposal-sue.html s3://queenofsandiego.com/proposals/jada-charter-proposal-sue.html --content-type text/html
Why S3? Static site hosting eliminates server overhead, provides built-in versioning through object metadata, and integrates seamlessly with CloudFront for global edge caching. S3's object-level permissions mean we can audit exactly when and what was deployed.
CloudFront CDN Cache Invalidation
After deploying to S3, the CloudFront distribution (ID: XXXXXXXXXXXX, redacted for security) required immediate cache invalidation to ensure users see the new page within seconds rather than waiting for TTL expiration:
aws cloudfront create-invalidation \
--distribution-id XXXXXXXXXXXX \
--paths "/proposals/jada-charter-proposal-sue.html"
Why explicit invalidation? Even though S3 objects have metadata-based cache headers, CloudFront edge nodes in 200+ locations may serve stale content. An explicit invalidation request forces all edge locations to refresh their copy from the origin, guaranteeing consistency across global requests within 30-60 seconds.
The invalidation status was verified through:
aws cloudfront get-invalidation \
--distribution-id XXXXXXXXXXXX \
--id IXXXXXXXXXXX
DNS Resolution
Route53 resolves queenofsandiego.com to CloudFront's CNAME endpoint. No Route53 changes were needed—the existing alias record automatically routes all requests to the distribution, which in turn reads from the S3 origin.
Verification was performed via curl to confirm the page is accessible at the live URL and returns expected HTML content.
Deployment Workflow: Scripts and Automation
The deployment process uses environment variable injection from a secrets file:
set -a
source /Users/cb/Documents/repos/.secrets/repos.env
set +a
This pattern (sourcing secrets with set -a/set +a) ensures all variables in repos.env are exported to child processes without printing them to stdout, maintaining security while enabling AWS CLI authentication.
A master deployment script at publish_static_site.sh orchestrates:
- Source environment credentials
- Upload modified files to S3
- Create CloudFront invalidation
- Poll invalidation status until complete
- Log deployment metadata
Key Architecture Decisions
Static HTML vs. Server-Side Generation
The proposal page is pure static HTML. Why not dynamic? The content (pricing, terms, history) changes infrequently and identically for all users. Static hosting eliminates:
- Server latency (direct S3→CloudFront→user, no compute)
- Cold starts (no Lambda, no application server startup)
- Database queries (no round-trip I/O)
- Scaling complexity (S3 handles unlimited concurrent requests)
The tradeoff: any content update requires re-deploying the entire file. For rarely-changed pages, this is ideal.
Naming Convention
File naming includes the proposee's identifier (jada-charter-proposal-sue.html) rather than generic (charter-proposal.html). Why?
- Audit trail: file history shows exactly who received which proposal version
- A/B testing: multiple proposals can coexist with different URLs for variant analysis
- Permissions: future deployments can restrict file updates to authorized users per recipient
Email Contact Point
The proposal routes inquiries to bookings@queenofsandiego.com rather than individual email addresses. Why?
- Resilience: individual email availability doesn't block inquiries
- Load distribution: team can triage bookings collectively
- Compliance: reduces PII (personally identifiable information) exposure in deployed files
- Auditability: all booking inquiries flow through a single, monitored address
Legal Compliance Architecture
The page includes explicit disclaimers about:
- USCG licensing: clarifies this is a licensed passenger vessel, not a bareboat charter
- EPA/MPRSA regulations: documents compliance with Marine Protection, Research, and Sanctuaries Act for ash scattering
These sections live as visible HTML (not in comments or JavaScript) to ensure they're indexed by search engines and immediately visible to users—reducing legal exposure through transparency rather than obscurity.
What's Next
Planned enhancements:
- Image asset optimization: integrate vessel photos and historical imagery into
assets/images/interior/with responsive<picture>elements for mobile/desktop variants - Payment integration: wire Stripe checkout link for deposit