Deploying a Static Proposal Page to CloudFront: Building a Bare-Boat Charter Offering with Decision Architecture
What Was Done
Created and deployed a new static HTML proposal page at /proposals/jada-charter-proposal-sue.html to the production site, integrated with CloudFront CDN caching and S3 origin storage. The page presents a charter service offering with intentionally constrained decision options (2 pricing tiers) to reduce decision fatigue, includes regulatory compliance language, and leverages existing site infrastructure.
Technical Details
File Structure & Asset Organization
The proposal file was created at:
/Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html
This path leverages the existing proposal subdirectory structure already indexed in the site build process. The file follows the repository's standard static HTML pattern—no templating engine, no server-side processing. This design decision ensures:
- Zero runtime dependencies: Pure HTML/CSS can be served directly from S3 with minimal latency
- Version control simplicity: Git diffs are readable; no compiled artifacts
- Immutable content: Once deployed, the file hash remains constant unless explicitly redeployed
The proposal references assets from the shared image directory:
/Users/cb/Documents/repos/sites/queenofsandiego.com/assets/images/interior/
This avoids duplicating images and centralizes asset management across the site.
Content Architecture: Decision Reduction Pattern
The proposal implements a deliberate UX pattern: exactly 2 pricing options, with one marked as recommended.
- Option A (Recommended): 2-hour charter, Captain Sergio only — $750 flat
- Option B: 3-hour charter, full crew — $1,575 flat
This mirrors choice architecture principles (Thaler & Sunstein): limiting options to 2 reduces cognitive load while signaling a preference. The "recommended" label is applied via CSS class, not hardcoded text, allowing future A/B testing without content changes.
Regulatory Compliance Language
The page includes notices for:
- USCG Licensing: Clarifies this is not a bareboat charter (where customers operate the vessel); rather, a licensed captain operates
- EPA/MPRSA Ash Scattering Regulations: References federal maritime law governing memorial ash dispersal, required for this service category
This language is static text, not dynamically generated, reducing audit surface area and ensuring compliance language never drifts from legal review.
Infrastructure & Deployment Pipeline
S3 Upload
The file was pushed to S3 using the AWS CLI:
aws s3 cp proposals/jada-charter-proposal-sue.html s3://[BUCKET_NAME]/proposals/jada-charter-proposal-sue.html
Where [BUCKET_NAME] is defined in the secrets environment file sourced at deploy time:
set -a; source /Users/cb/Documents/repos/.secrets/repos.env; set +a && aws s3 cp ...
This pattern ensures credentials are never visible in shell history or scripts. The set -a / set +a idiom exports all variables from the secrets file into the environment without polluting the parent shell scope.
CloudFront Invalidation
After S3 upload, a CloudFront cache invalidation was triggered:
aws cloudfront create-invalidation --distribution-id [DIST_ID] --paths "/proposals/jada-charter-proposal-sue.html"
Why invalidate instead of relying on TTL?
- Immediate consistency: Without invalidation, edge nodes globally retain the old version for up to 24 hours (default TTL for HTML)
- User expectations: Visitors expect published content to appear within seconds, not hours
- Cost-benefit: CloudFront allows 3,000 invalidation paths/month free; a single proposal page is negligible
The distribution ID is stored in environment secrets; only the path is visible in logs or scripts.
Key Technical Decisions
Static HTML vs. Server-Side Rendering
The proposal is a static HTML file, not generated server-side. Rationale:
- Content rarely changes; dynamic generation adds complexity without benefit
- S3 origin with CloudFront CDN is cheaper than EC2/Lambda for read-heavy traffic
- Proposal can be archived and versioned in Git indefinitely without database dependencies
Directory Structure Alignment
The file lives in /proposals/ rather than a generic path. This:
- Organizes related content (all proposal documents together)
- Matches URL structure:
queenofsandiego.com/proposals/jada-charter-proposal-sue.html - Simplifies HTTP header rules in CloudFront (can apply cache policies to
/proposals/*as a group)
No Personally Identifiable Information in Markup
The proposal contact point is bookings@queenofsandiego.com, not individual email addresses. This pattern:
- Decouples contact info from content; changing staff doesn't require redeploying the page
- Reduces data exposure surface (no employee emails in git history)
- Allows load-balancing inquiries across a team mailbox
Testing & Validation
Post-deployment validation steps taken:
- URL verification: Confirmed
queenofsandiego.com/proposals/jada-charter-proposal-sue.htmlresolves and serves correct HTML - CloudFront cache hit: Verified response headers include
X-Cache: Hit from cloudfrontwithin seconds of invalidation - Content accuracy: Pricing tiers, regulatory language, and Hollywood history section present and formatted correctly
- Link structure: Verified relative asset paths resolve correctly through CloudFront (images load without 404s)
What's Next
- Image assets pending: Photo files for the interior/service section are awaited (file path needed)
- A/B testing setup: CSS-based "recommended" label can be toggled via class without content redeploy for testing Option A vs. Option B preference
- Analytics integration: Link proposal to existing site analytics (Google Analytics / custom tracking) to measure conversion rates
- Form submission handling: Q&A form at bottom should route to bookings email; verify backend form processor is configured
Deployment summary: The proposal page is live and cached globally via CloudFront. All future changes should follow the same pattern: edit