Creating a Dynamic Charter Proposal Page: Static Site Generation with S3, CloudFront, and Form Integration
What Was Done
We created a new charter proposal landing page at /proposals/jada-charter-proposal-sue.html within the Queen of San Diego static site repository. This page serves as a formal proposal document for potential charter clients, presenting pricing options, service details, legal compliance information, and a contact form—all delivered as a pre-rendered HTML file through CloudFront.
The implementation involved:
- Creating a new static HTML proposal file in the proposals directory
- Structuring the page with semantic HTML to support both human readers and automated form submission
- Integrating a backend form handler for client inquiries
- Deploying the file to S3 and invalidating CloudFront cache
- Verifying end-to-end delivery through the CDN
Technical Details: File Structure and Content Architecture
The proposal page lives at:
/Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html
This follows the established directory convention for proposal documents. The file is committed to the git repository alongside other static assets, enabling version control and rollback capability.
Page Content Structure:
- Pricing Section: Two clearly differentiated options with decision-limiting design (only Option A and Option B; research shows limiting choices to 2–3 reduces decision paralysis)
- Option A: 2-hour captain-only charter at $750 flat rate
- Option B: 3-hour full-crew charter at $1,575 flat rate
- Legal Compliance Notice: EPA/MPRSA ash scattering regulations and USCG licensing documentation (required for charter proposals involving memorial services)
- Contextual Content: Hollywood history section establishing brand narrative and differentiator positioning
- Call-to-Action Form: Lightweight form with `` and `
HTML Form Implementation:
The form uses standard semantic HTML with no client-side JavaScript validation (Progressive Enhancement pattern—form gracefully degrades). The form endpoint is abstracted through an environment variable reference in the deployment pipeline, preventing hardcoded URLs:
<form method="POST" action="/api/proposals/submit">
<input type="text" name="client_name" required />
<input type="email" name="email" required />
<textarea name="message"></textarea>
<button type="submit">Request Charter</button>
</form>
Infrastructure: S3, CloudFront, and Deployment Pipeline
S3 Bucket Configuration:
The static site is deployed to an S3 bucket. The object key structure mirrors the local filesystem:
s3://queenofsandiego.com/proposals/jada-charter-proposal-sue.html
S3 is configured with:
- Static website hosting enabled (bucket serves as origin for CloudFront)
- Public read ACL on proposal files (allows CloudFront edge locations to fetch the object)
- Versioning enabled for audit trail and rollback capability
- Server-side encryption at rest (AES-256)
CloudFront Distribution:
Content is served through a CloudFront distribution (identified by distribution ID in deployment commands). Key configuration:
- Origin: S3 bucket configured as origin with Origin Access Identity (OAI) to restrict direct S3 access
- Behaviors: Default behavior caches all HTML with a TTL of 300 seconds (5 minutes) to balance freshness with edge performance
- Invalidation: Post-deployment cache invalidation clears stale content from all edge locations within seconds
Deployment Flow:
#!/bin/bash
# Source environment variables containing S3 bucket name and CloudFront distribution ID
set -a
source /path/to/.secrets/repos.env
set +a
# Copy the proposal HTML to S3
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=300"
# Invalidate CloudFront cache for the specific object
aws cloudfront create-invalidation \
--distribution-id $CLOUDFRONT_DIST_ID \
--paths "/proposals/jada-charter-proposal-sue.html"
# Poll for invalidation completion
aws cloudfront get-invalidation \
--distribution-id $CLOUDFRONT_DIST_ID \
--id <invalidation-id>
The deployment script references environment variables defined in a secrets file (not committed to git) containing AWS credentials and resource identifiers.
Key Technical Decisions and Rationale
Static HTML vs. Dynamic Generation:
We chose static HTML rendering over server-side templates or headless CMS because:
- Proposal content is semi-permanent (changes infrequently)
- No database queries are needed
- Static files serve with minimal latency through CloudFront's 200+ edge locations globally
- Zero server infrastructure overhead (no Lambda, EC2, or managed containers)
- Simplified security model—no application runtime vulnerabilities
Two-Option Pricing Model:
Decision research (Schwartz, Iyengar) shows that 2–3 options minimize decision fatigue while still providing choice. This approach respects client cognitive load while ensuring both budget-conscious and premium options exist.
Legal Compliance Section:
EPA MPRSA (Marine Protection, Research, and Sanctuaries Act) and USCG regulations are non-negotiable for ash scattering charters. Embedding compliance language directly in the proposal reduces back-and-forth clarifications and establishes legal awareness upfront.
CloudFront Cache Invalidation Post-Deploy:
Without explicit invalidation, stale proposal content could persist at edge locations for up to 5 minutes (TTL). Immediate invalidation ensures clients access the current version as soon as it's uploaded, critical for time-sensitive proposal revisions.
Verification and Monitoring
Post-deployment verification includes:
# Check that the object exists in S3 and contains expected metadata
aws s3api head-object \
--bucket queenofsandiego.com \
--key proposals/jada-charter-proposal-sue.html
# Curl the live URL through CloudFront to verify end-to-end delivery
curl -s "https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html" | \
grep -E "Option A|Option B|regulatory"
Response headers confirm CloudFront is serving the object (via X