Deploying a Multi-Domain Static Site with CloudFront Custom Error Handling and Google Apps Script Integration
This session focused on deploying a new receipts management page to quickdumpnow.com while simultaneously integrating a Google Apps Script-based port logging system. The work involved S3 object management, CloudFront cache invalidation patterns, and API-driven spreadsheet updates across multiple business domains.
What Was Done
- Created and deployed
/books/index.htmlto enable receipt tracking for a trailer rental business - Updated
robots.txtto block search engine indexing of the new /books path - Deployed to S3 and invalidated CloudFront distribution caches
- Diagnosed and worked around CloudFront custom error response behavior
- Integrated with Google Apps Script to automate port sheet logging via the Sheets API
- Added charter payment entry ($1,845.72) to the Port Log spreadsheet programmatically
Technical Details: S3 and CloudFront Deployment
The quickdumpnow.com static site is hosted on S3 with CloudFront as the CDN. The site structure mirrors typical multi-section deployments:
/Users/cb/Documents/repos/sites/quickdumpnow.com/
├── books/
│ └── index.html
├── robots.txt
└── [other site files]
The books directory was created to segment the receipts functionality from the main landing page. This organizational pattern follows REST conventions where logical resources are isolated into their own URL paths.
S3 Upload Strategy
A critical discovery during deployment was that CloudFront's custom error responses (likely configured to serve the homepage for 404s) were interfering with pretty URLs. To work around this:
- The
books/index.htmlfile was uploaded to two S3 keys:s3://quickdumpnow-bucket/books/index.html— standard directory structures3://quickdumpnow-bucket/books— bare key for direct access
robots.txtwas updated to include blocking directives for the /books path to prevent indexing of the receipts page
Upload commands (sanitized):
aws s3 cp books/index.html s3://quickdumpnow-bucket/books/index.html \
--content-type "text/html" \
--cache-control "max-age=3600"
aws s3 cp books/index.html s3://quickdumpnow-bucket/books \
--content-type "text/html" \
--cache-control "max-age=3600"
aws s3 cp robots.txt s3://quickdumpnow-bucket/robots.txt \
--content-type "text/plain"
CloudFront Cache Invalidation
After S3 uploads, CloudFront invalidations were issued to purge cached responses:
aws cloudfront create-invalidation \
--distribution-id \
--paths "/books" "/books/*" "/robots.txt"
Invalidations propagate globally within 30-60 seconds. This timing is critical when deploying to production — clients may see stale content briefly during propagation.
Infrastructure: CloudFront Custom Error Responses
Investigation of the CloudFront distribution revealed custom error response configuration:
- Root Cause: The distribution was configured with a 404 error response that redirects to the homepage (likely the S3 index.html)
- Symptom: Requests to
/booksinitially returned the homepage instead of thebooks/index.htmlcontent - Resolution: Uploading to the bare
booksS3 key, combined with CloudFront invalidation, ensured the correct object was served
This is a common pattern for single-page applications where all 404s should route to a JavaScript router. However, for multi-section static sites, it requires careful S3 object key planning to avoid collisions.
Google Apps Script Integration for Port Logging
Parallel to the S3 deployment, work began on automating port sheet logging. The system uses Google Apps Script as a backend with the Google Sheets API for data persistence.
Architecture Overview
- Source of Truth: Google Sheet named "Port Log" with tabs tracking charter activity
- Integration Point: Google Apps Script project stored in the ExpenseTracker GAS file
- API Access: OAuth 2.0 token obtained to call Sheets API v4 directly from local scripts
- Data Model: Port Log sheet contains headers and append-only charter entry rows
Port Log Schema
Initial inspection of the Port Log revealed the following structure:
Headers (Row 1):
- Date
- Charter Name / Description
- Amount
- Status
- Notes
Example Entry:
- 2024-01-15
- Joseph Zurek charter
- $1,845.72
- Completed
- Single payment
Programmatic Entry Insertion
Rather than manual spreadsheet entry, the workflow was automated via the Sheets API. The process involved:
- Authentication: Retrieving a valid OAuth 2.0 access token for the service account
- Sheet ID Resolution: Querying the spreadsheet to locate the correct sheet ID for the "Port Log" tab (critical because sheet names and IDs can diverge)
- Row Deletion: Removing any test rows from previous iterations to maintain data cleanliness
- Data Append: Appending the charter entry using the
appendValuesmethod
Key API calls (pseudocode):
# List all sheets to find Port Log tab ID
GET https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}
# Query existing rows
GET https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/values/'Port Log'!A:E
# Append new entry
POST https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/values/'Port Log'!A:E:append
{
"values": [
["2024-01-14", "Joseph Zurek charter", 1845.72, "Completed", "Single payment"]
]
}
Why Apps Script Over Direct Database?
Google Sheets was chosen as the persistent store because:
- Non-technical stakeholders (like Maria) can access and audit entries directly
- No database infrastructure to maintain or secure
- Built-in versioning and audit trail via Google Sheets' activity log
- Familiar interface for business users
Key Decisions and Trade-offs
S3 Object Key Duplication
Uploading the same HTML file to both books/index.html and books