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.envusingset -a / set +ato 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-Cacheheader)
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_IDenvironment 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.comcorrectly - Mobile responsiveness testing: Verify the proposal renders correctly on mobile devices (typical Charter customer journey includes phone browsing)